In Part 1 of this series we built the foundations — custom tables, a Change Document Object in SCDO, and a clean ABAP wrapper class. Now we take it further. In this second and final part, we expose the same customer object through a full RAP Business Object — CDS views, projection views with Fiori annotations, and a Behaviour Definition.
Recommend reading the previous blog: How to Implement SAP Change Document. We will need some objects created in the last blog.
Note: The native changedocuments annotation in the BDEF requires S/4HANA 2022 or later. If you are on an earlier release, you will need to implement change documents manually in the saver class.
I will initially create a RAP application without a Change Document, then amend the relevant objects to enable the Change Document. If you already have a RAP application and would like to enable change document, you need to create a Change Document Object and then jump to Enabling Change Document Integration .
Checklist of Objects
| Object Type | Object Name | Purpose |
|---|---|---|
| Table | ZZCUSTOMER | Header Table |
| Table | ZZCUSTOMER_BANK | Item Table |
| Change Document Object | ZZCUSTOMER | To log Changes |
| Interface Views | ZZI_Customer, ZZI_Bank | |
| Behaviour Definition | ZZI_CUSTOMER | |
| Projection View | ZZC_Customer, ZZC_Bank | |
| Metadata Extension | ZZC_CUSTOMER, ZZC_BANK | UI Annotation |
| Behaviour Definition (Projection) | ZZC_CUSTOMER | |
| Service Definition | ZZCUSTOMER_MGMT | |
| Service Binding | ZZUI_CUSTOMER_O4 |
Creating the usual List and Object Page RAP Application for tables ZZCUSTOMER and ZZCUSTOMER_BANK.
Interface View ZZI_Customer
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Customer'
@Metadata.ignorePropagatedAnnotations: true
define root view entity ZZI_Customer
as select from zzcustomer
composition [0..*] of ZZI_Bank as _Bank
{
key zzcustomer.customer_id as CustomerId,
zzcustomer.name as Name,
zzcustomer.street as Street,
zzcustomer.city as City,
zzcustomer.postal_code as PostalCode,
zzcustomer.country as Country,
@Semantics.user.createdBy: true
local_created_by as LocalCreatedBy,
@Semantics.systemDateTime.createdAt: true
local_created_at as LocalCreatedAt,
@Semantics.user.localInstanceLastChangedBy: true
local_last_changed_by as LocalLastChangedBy,
@Semantics.systemDateTime.localInstanceLastChangedAt: true
local_last_changed_at as LocalLastChangedAt,
@Semantics.systemDateTime.lastChangedAt: true
last_changed_at as LastChangedAt,
_Bank // Make association public
}
Interface View ZZI_Bank
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Customer Bank'
@Metadata.ignorePropagatedAnnotations: true
define view entity ZZI_Bank
as select from zzcustomer_bank
association to parent ZZI_Customer as _Customer on $projection.CustomerId = _Customer.CustomerId
{
key zzcustomer_bank.bankid as BankId,
zzcustomer_bank.customer_id as CustomerId,
zzcustomer_bank.bank_country as BankCountry,
zzcustomer_bank.bank_key as BankKey,
zzcustomer_bank.bank_account as BankAccount,
zzcustomer_bank.currency as Currency,
@Semantics.user.createdBy: true
local_created_by as LocalCreatedBy,
@Semantics.systemDateTime.createdAt: true
local_created_at as LocalCreatedAt,
@Semantics.user.localInstanceLastChangedBy: true
local_last_changed_by as LocalLastChangedBy,
@Semantics.systemDateTime.localInstanceLastChangedAt: true
local_last_changed_at as LocalLastChangedAt,
@Semantics.systemDateTime.lastChangedAt: true
last_changed_at as LastChangedAt,
_Customer // Make association public
}
Behaviour Definition ZZI_CUSTOMER
Before you activate below, please create draft tables zzcustomer_d and zzcus_bank_d using the quick fix in ADT. Also create class zbp_zi_customer.
managed implementation in class zbp_zi_customer unique;
strict ( 2 );
with draft;
define behavior for ZZI_Customer alias Customer persistent table zzcustomer
draft table zzcustomer_d
lock master total etag LastChangedAt
etag master LastChangedAt
authorization master ( instance )
{
create ( authorization : none );
update ( precheck );
delete;
field ( readonly, numbering : managed ) CustomerId;
field ( readonly ) LocalCreatedBy, LocalCreatedAt, LocalLastChangedBy, LocalLastChangedAt, LastChangedAt ;
field ( mandatory ) Name, Street, City, PostalCode, Country;
association _Bank { create; with draft; }
draft action Edit;
draft action Resume;
draft action Discard;
draft action Activate optimized;
draft determine action Prepare;
mapping for zzcustomer
{
CustomerId = customer_id;
Name = name;
Street = street;
City = city;
PostalCode = postal_code;
Country = country;
LocalCreatedBy = local_created_by;
LocalCreatedAt = local_created_at;
LocalLastChangedBy = local_last_changed_by;
LocalLastChangedAt = local_last_changed_at;
LastChangedAt = last_changed_at;
}
}
define behavior for ZZI_Bank alias Bank persistent table zzcustomer_bank
draft table zzcus_bank_d
lock dependent by _Customer
authorization dependent by _Customer
{
update;
delete;
field ( readonly, numbering : managed ) BankId;
field ( readonly ) CustomerId, LocalCreatedBy, LocalCreatedAt, LocalLastChangedBy, LocalLastChangedAt, LastChangedAt;
field ( mandatory ) BankCountry, BankKey, BankAccount, Currency;
association _Customer { with draft; }
mapping for zzcustomer_bank
{
BankId = bankid;
CustomerId = customer_id;
BankCountry = bank_country;
BankKey = bank_key;
BankAccount = bank_account;
Currency = currency;
LocalCreatedBy = local_created_by;
LocalCreatedAt = local_created_at;
LocalLastChangedBy = local_last_changed_by;
LocalLastChangedAt = local_last_changed_at;
LastChangedAt = last_changed_at;
}
}
Projection View ZZC_Customer
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Customer Projection'
@Metadata.ignorePropagatedAnnotations: true
@Metadata.allowExtensions: true
@Search.searchable: true
@ObjectModel.semanticKey: ['Name']
define root view entity ZZC_Customer
provider contract transactional_query
as projection on ZZI_Customer
{
key CustomerId,
@Search.defaultSearchElement: true
@Search.fuzzinessThreshold: 0.8
Name,
Street,
City,
PostalCode,
Country,
@Semantics: { user.createdBy: true }
LocalCreatedBy,
@Semantics: { systemDateTime.createdAt: true }
LocalCreatedAt,
@Semantics: { user.localInstanceLastChangedBy: true }
LocalLastChangedBy,
@Semantics.systemDateTime.localInstanceLastChangedAt: true
LocalLastChangedAt,
@Semantics: { systemDateTime.lastChangedAt: true }
LastChangedAt,
_Bank : redirected to composition child ZZC_Bank
}
Projection View ZZC_BANK
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Bank Projection'
@Metadata.ignorePropagatedAnnotations: true
@Metadata.allowExtensions: true
@Search.searchable: true
define view entity ZZC_Bank
as projection on ZZI_Bank
{
key BankId,
CustomerId,
BankCountry,
BankKey,
@Search.defaultSearchElement: true
@Search.fuzzinessThreshold: 0.8
BankAccount,
Currency,
@Semantics: { user.createdBy: true }
LocalCreatedBy,
@Semantics: { systemDateTime.createdAt: true }
LocalCreatedAt,
@Semantics: { user.localInstanceLastChangedBy: true }
LocalLastChangedBy,
@Semantics.systemDateTime.localInstanceLastChangedAt: true
LocalLastChangedAt,
@Semantics: { systemDateTime.lastChangedAt: true }
LastChangedAt,
_Customer: redirected to parent ZZC_Customer
}
Behaviour Definition (Projection) ZZC_CUSTOMER
projection;
strict ( 2 );
use draft;
define behavior for ZZC_Customer
{
use create;
use update;
use delete;
use action Edit;
use action Resume;
use action Discard;
use action Activate;
use action Prepare;
use association _Bank { create; with draft; }
}
define behavior for ZZC_Bank
{
use update;
use delete;
use association _Customer { with draft; }
}
Metadata Extention ZZC_CUSTOMER
@Metadata.layer: #CORE
@UI: {
headerInfo: {
typeName: 'Customer',
typeNamePlural: 'Customers',
title: { type: #STANDARD, value: 'Name' }
},
presentationVariant: [{
sortOrder: [{ by: 'Name', direction: #DESC }],
visualizations: [{type: #AS_LINEITEM}]
}]
}
annotate entity ZZC_Customer with
{
@UI.facet: [
{
id: 'CustomerDetails',
purpose: #STANDARD,
type: #IDENTIFICATION_REFERENCE,
label: 'Customer Details',
position: 10
},
{
id : 'idAdmin' ,
type: #FIELDGROUP_REFERENCE ,
label : 'Admin',
targetQualifier: 'fgAdmin' ,
position: 20
},
{
id: 'Banks',
purpose: #STANDARD,
type: #LINEITEM_REFERENCE,
label: 'Banks',
position: 30,
targetElement: '_Bank'
}
]
@UI.hidden: true
CustomerId;
@UI.lineItem: [{ position: 10, importance: #HIGH }]
@UI.identification: [{ position: 10 }]
@UI.selectionField: [{ position: 10 }]
Name;
@UI.lineItem: [{ position: 20, importance: #HIGH }]
@UI.identification: [{ position: 20 }]
@UI.selectionField: [{ position: 20 }]
Street;
@UI.lineItem: [{ position: 30, importance: #HIGH }]
@UI.identification: [{ position: 30 }]
@UI.selectionField: [{ position: 30 }]
City;
@UI.identification: [{ position: 40 }]
PostalCode;
@UI.lineItem: [{ position: 50, importance: #HIGH }]
@UI.identification: [{ position: 50 }]
@UI.selectionField: [{ position: 50 }]
@Consumption.valueHelpDefinition: [{ entity: { name: 'I_CountryVH', element: 'Country' } }]
Country;
@UI.lineItem: [{ position: 60, importance: #HIGH }]
@UI.selectionField: [{ position: 60 }]
@UI.fieldGroup: [{ qualifier: 'fgAdmin' , position: 10 }]
@Consumption.valueHelpDefinition: [{ entity: { name: 'I_CreatedByUser', element: 'UserName'} }]
LocalCreatedBy;
@UI.lineItem: [{ position: 70, importance: #HIGH }]
@UI.selectionField: [{ position: 70 }]
@UI.fieldGroup: [{ qualifier: 'fgAdmin' , position: 20 }]
LocalCreatedAt;
@UI.fieldGroup: [{ qualifier: 'fgAdmin' , position: 30 }]
@Consumption.valueHelpDefinition: [{ entity: { name: 'I_CreatedByUser', element: 'UserName'} }]
LocalLastChangedBy;
@UI.fieldGroup: [{ qualifier: 'fgAdmin' , position: 50 }]
LocalLastChangedAt;
}
Metadata Extension ZZC_BANK
@Metadata.layer: #CORE
@UI: {
headerInfo: {
typeName: 'Bank',
typeNamePlural: 'Banks',
title: { type: #STANDARD, value: 'BankAccount' }
}
}
annotate entity ZZC_Bank with
{
@UI.facet: [{
id: 'BankDetails',
purpose: #STANDARD,
type: #IDENTIFICATION_REFERENCE,
label: 'Bank Details',
position: 10
}]
@UI.hidden: true
BankId;
@UI.lineItem: [{ position: 10, importance: #HIGH }]
@UI.identification: [{ position: 10 }]
@Consumption.valueHelpDefinition: [{ entity: { name: 'I_CountryVH', element: 'Country' } }]
BankCountry;
@UI.lineItem: [{ position: 20, importance: #HIGH }]
@UI.identification: [{ position: 20 }]
BankKey;
@UI.lineItem: [{ position: 30, importance: #HIGH }]
@UI.identification: [{ position: 30 }]
BankAccount;
@UI.lineItem: [{ position: 40, importance: #HIGH }]
@UI.identification: [{ position: 40 }]
@Consumption.valueHelpDefinition: [{ entity: { name: 'I_CurrencyStdVH', element: 'Currency'} }]
Currency;
}
Service Definition ZZCUSTOMER_MGMT
@EndUserText.label: 'Customer Service Definition'
define service ZZCUSTOMER_MGMT {
expose ZZC_Customer as Customer;
expose ZZC_Bank as Bank;
}
Service Binding ZZUI_CUSTOMER_O4
Create service binding using OData V4 – UI and publish it. Preview application on entity Customer, you should see a working Fiori App.
Enabling Change Documents Integration
Create Change Document Object ZZCUSTOMER as described here.
Change Behaviour Definition ZZI_CUSTOMER.
- Define
changedocuments master ( zzcustomer )on the root entityCustomer. Wherezzcustomeris name of Change Document Object. - Define
changedocuments dependentfor the dependentBankentity. - Add
changedocuments ( create : key, update : data, delete : none );in both entities.
managed implementation in class zbp_zi_customer unique;
strict ( 2 );
with draft;
define behavior for ZZI_Customer alias Customer persistent table zzcustomer
draft table zzcustomer_d
changedocuments master ( zzcustomer )
lock master total etag LastChangedAt
etag master LastChangedAt
authorization master ( instance )
{
create ( authorization : none );
update ( precheck );
delete;
field ( readonly, numbering : managed ) CustomerId;
field ( readonly ) LocalCreatedBy, LocalCreatedAt, LocalLastChangedBy, LocalLastChangedAt, LastChangedAt ;
field ( mandatory ) Name, Street, City, PostalCode, Country;
changedocuments ( create : key, update : data, delete : none );
association _Bank { create; with draft; }
draft action Edit;
draft action Resume;
draft action Discard;
draft action Activate optimized;
draft determine action Prepare;
mapping for zzcustomer
{
CustomerId = customer_id;
Name = name;
Street = street;
City = city;
PostalCode = postal_code;
Country = country;
LocalCreatedBy = local_created_by;
LocalCreatedAt = local_created_at;
LocalLastChangedBy = local_last_changed_by;
LocalLastChangedAt = local_last_changed_at;
LastChangedAt = last_changed_at;
}
}
define behavior for ZZI_Bank alias Bank persistent table zzcustomer_bank
draft table zzcus_bank_d
changedocuments dependent
lock dependent by _Customer
authorization dependent by _Customer
{
update;
delete;
field ( readonly, numbering : managed ) BankId;
field ( readonly ) CustomerId, LocalCreatedBy, LocalCreatedAt, LocalLastChangedBy, LocalLastChangedAt, LastChangedAt;
field ( mandatory ) BankCountry, BankKey, BankAccount, Currency;
changedocuments ( create : key, update : data, delete : none );
association _Customer { with draft; }
mapping for zzcustomer_bank
{
BankId = bankid;
CustomerId = customer_id;
BankCountry = bank_country;
BankKey = bank_key;
BankAccount = bank_account;
Currency = currency;
LocalCreatedBy = local_created_by;
LocalCreatedAt = local_created_at;
LocalLastChangedBy = local_last_changed_by;
LocalLastChangedAt = local_last_changed_at;
LastChangedAt = last_changed_at;
}
}
Create or change data in RAP Fiori Application, you should see change documents created in CDHDR and CDPOS tables.
Displaying SAP Change Document Data in RAP Fiori Application
To display change document data in the Fiori Application do following changes:
- Change Interface View
ZZI_Customerto include a fieldChangeDocObject. Add association toI_ChangeDocument_2usingChangeDocObjectClass = 'ZZCUSTOMER'and new fieldChangeDocObject. We have to add this new fieldChangeDocObjectto convertcustomer_idwhich is is in RAW to character, to make it compatible withI_ChangeDocument_2-ChangeDocObject. Expose the association_ChangeDocument. Add annotation@AccessControl.privilegedAssociations: [ '_ChangeDocument' ]as CDSI_ChangeDocument_2is Privileged view. - Change Behaviour Definition
ZZI_CUSTOMERto add fieldChangeDocObjectasreadonly. - Change Projection View
ZZC_CUSTOMERadd fieldChangeDocObject, redirect association_ChangeDocumenttoC_ChangeDocument_2. Add annotation@AccessControl.privilegedAssociations: [ '_ChangeDocument' ]. - Change metadata extension
ZZC_Customerto add new facet of type#LINEITEM_REFERENCEwithtargetElement: '_ChangeDocument'. - Change Service Definition
ZZCUSTOMER_MGMTto expose viewC_ChangeDocument_2.
Changes in Interface View ZZI_Customer
@AccessControl.privilegedAssociations: [ '_ChangeDocument' ]
define root view entity ZZI_Customer
as select from zzcustomer
association of one to many I_ChangeDocument_2 as _ChangeDocument on _ChangeDocument.ChangeDocObjectClass = 'ZZCUSTOMER'
and _ChangeDocument.ChangeDocObject = $projection.ChangeDocObject
{
key zzcustomer.customer_id as CustomerId,
cast( bintohex( zzcustomer.customer_id ) as cdobjectv ) as ChangeDocObject,
_ChangeDocument,
_Bank // Make association public
}
Change in Behaviour Definition ZZI_CUSTOMER
field ( readonly ) ChangeDocObject;
Changes in Projection View ZZC_CUSTOMER
@AccessControl.privilegedAssociations: [ '_ChangeDocument' ]
define root view entity ZZC_Customer
as projection on ZZI_Customer
{
key CustomerId,
ChangeDocObject,
_ChangeDocument : redirected to C_ChangeDocument_2,
_Bank : redirected to composition child ZZC_Bank
}
Change in metadata extension ZZC_Customer
annotate entity ZZC_Customer with
{
@UI.facet: [
{
id: 'CustomerDetails',
purpose: #STANDARD,
type: #IDENTIFICATION_REFERENCE,
label: 'Customer Details',
position: 10
},
{
id : 'idAdmin' ,
type: #FIELDGROUP_REFERENCE ,
label : 'Admin',
targetQualifier: 'fgAdmin' ,
position: 20
},
{
id: 'Banks',
purpose: #STANDARD,
type: #LINEITEM_REFERENCE,
label: 'Banks',
position: 30,
targetElement: '_Bank'
} ,
{
id: 'ChangeDocs',
purpose: #STANDARD,
type: #LINEITEM_REFERENCE,
label: 'Change Documents',
position: 40,
targetElement: '_ChangeDocument'
}
]
}
Change in Service Definition ZZCUSTOMER_MGMT
@EndUserText.label: 'Customer Service Definition'
define service ZZCUSTOMER_MGMT {
expose ZZC_Customer as Customer;
expose ZZC_Bank as Bank;
expose C_ChangeDocument_2 as ChangeDocument;
}
Activate all changes and you should see a new facet on object page with Change document records.
One Reply to “SAP Change Documents in RAP – CDS Views, BDEF and Change Document Annotation”