INTRODUCTION: -
While working on a RAP application for a Store Audit scenario, I encountered a requirement where the Audit Status had to be determined only after the user saved the transaction. Initially, I expected a traditional RAP side effect to refresh the Status field automatically. However, I soon realized that standard side effects are triggered during the interaction phase, whereas my business logic was executed during the late save phase. As a result, the updated status was not immediately reflected in the SAP Fiori UI.
While exploring possible solutions, I came across Event-Driven RAP Side Effects, a feature that allows the backend to notify the UI when business events occur after the save operation. Instead of relying on field changes, the application refreshes the affected fields when a business event is raised during the late save phase. This provides a clean and efficient way to keep the UI synchronized with backend-calculated values without requiring users to manually refresh the application.
In this blog, I'll demonstrate how I implemented this feature in a managed RAP application with draft support using a Store Audit example. Whenever the Tender Amount or Currency is modified and the transaction is saved, the backend determines the appropriate Audit Status and raises a business event named AuditStatusChanged. RAP then automatically refreshes the Status field on the SAP Fiori UI.
Define a Database table: -
@EndUserText.label : 'Database table for store details'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zmuk_dt_store {
key mandt : mandt not null;
key retailstoreid : abap.numc(10) not null;
key businessdate : abap.dats not null;
@Semantics.amount.currencyCode : 'zmuk_dt_store.currency'
tenderamount : abap.curr(7,2);
currency : abap.cuky;
status : abap.char(15);
lastchanged : timestampl;
} Interface View: -
@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Interface for store details'
@Metadata.ignorePropagatedAnnotations: true
define root view entity ZMUK_I_STORE as select from zmuk_dt_store
{
key retailstoreid as Retailstoreid,
key businessdate as Businessdate,
@Semantics.amount.currencyCode : 'currency'
tenderamount as Tenderamount,
currency as Currency,
status as Status,
lastchanged as Lastchanged
} Projection view: -
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Projection for store details'
@Metadata.ignorePropagatedAnnotations: true
@Metadata.allowExtensions: true
define root view entity ZMUK_C_STORE
provider contract transactional_query
as projection on ZMUK_I_STORE
{
key Retailstoreid,
key Businessdate,
@Semantics.amount.currencyCode : 'currency'
Tenderamount,
Currency,
Status,
Lastchanged
} Behavior definition: -
Since the status is updated during save, we enable Additional Save.
This is mandatory because event-driven side effects can only be raised during the Late Save Phase.
managed implementation in class zbp_muk_i_store unique;
strict ( 2 );
with draft;
define behavior for ZMUK_I_STORE alias StoreDet
persistent table zmuk_dt_store
draft table zmuk_dt_store_d
lock master total etag Lastchanged
authorization master ( global )
with additional save
{
create;
update;
delete;
field ( readonly ) Retailstoreid, Businessdate;
draft action Edit;
draft action Activate optimized;
draft action Discard;
draft action Resume;
draft determine action Prepare;
determination DetermineAuditStatus on save { field Tenderamount, Currency; }
event AuditStatusChanged for side effects;
side effects { event AuditStatusChanged affects field Status; }
mapping for zmuk_dt_store
{
Retailstoreid = retailstoreid;
Businessdate = businessdate;
Tenderamount = tenderamount;
Currency = currency;
Status = status;
Lastchanged = lastchanged;
}
} Behavior definition of Projection: -
Events must also be exposed in the projection layer.
Then expose the event.
projection;
strict ( 2 );
use draft;
use side effects;
define behavior for ZMUK_C_STORE alias StoreDet
{
use create;
use update;
use delete;
use action Edit;
use action Activate;
use action Discard;
use action Resume;
use action Prepare;
use event AuditStatusChanged;
} Draft database table: -
@EndUserText.label : 'Draft table for entity ZMUK_I_STORE'
@AbapCatalog.enhancement.category : #EXTENSIBLE_ANY
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zmuk_dt_store_d {
key mandt : mandt not null;
key retailstoreid : abap.numc(10) not null;
key businessdate : abap.dats not null;
@Semantics.amount.currencyCode : 'zmuk_dt_store_d.currency'
tenderamount : abap.curr(7,2);
currency : abap.cuky;
status : abap.char(15);
lastchanged : timestampl;
"%admin" : include sych_bdl_draft_admin_inc;
} Behavior implementation: -
The event is raised during the late save phase in the behavior implementation class (using the additional save implementation).
Class lsc_zmuk_i_store and lhc_StoreDet.
This local handler class contains the actual implementation logic for:
- Determination for DetermineAuditStatus.
- Additional Save: This is mandatory because event-driven side effects can only be raised during the Late Save Phase.
Determination of AuditStatus.
METHOD DetermineAuditStatus.
READ ENTITIES OF zmuk_i_store IN LOCAL MODE
ENTITY StoreDet
FIELDS ( Tenderamount Currency Status ) WITH CORRESPONDING #( keys )
RESULT DATA(lt_stores).
DATA lt_update TYPE TABLE FOR UPDATE zmuk_i_store.
LOOP AT lt_stores INTO DATA(ls_store).
IF ls_store-Currency = 'USD' AND ls_store-Tenderamount > 1000.
APPEND VALUE #( %tky = ls_store-%tky
status = 'Pending Review' )
TO lt_update.
ELSE.
APPEND VALUE #( %tky = ls_store-%tky
status = 'Approved' )
TO lt_update.
ENDIF.
ENDLOOP.
IF lt_update IS NOT INITIAL.
MODIFY ENTITIES OF zmuk_i_store IN LOCAL MODE
ENTITY StoreDet
UPDATE FIELDS ( Status ) WITH lt_update.
ENDIF.
ENDMETHOD. Additional Save - Save_Modified: -
METHOD save_modified.
IF update-storedet IS NOT INITIAL.
DATA lt_events TYPE TABLE FOR EVENT zmuk_i_store~AuditStatusChanged.
LOOP AT update-storedet INTO DATA(ls_store).
APPEND INITIAL LINE TO lt_events ASSIGNING FIELD-SYMBOL(<fs_event>).
<fs_event>-%tky = CORRESPONDING #( ls_store-%key ).
ENDLOOP.
IF lt_events IS NOT INITIAL.
RAISE ENTITY EVENT zmuk_i_store~AuditStatusChanged
FROM lt_events.
ENDIF.
ENDIF.
ENDMETHOD. Metadata Extension: -
@Metadata.layer: #CUSTOMER
annotate entity ZMUK_C_STORE with
{
@UI.facet: [{
position: 10,
label: 'General Information',
purpose: #STANDARD,
type: #IDENTIFICATION_REFERENCE }]
@UI.identification: [{ position: 10, label: 'Store ID' }]
@UI.lineItem: [{ label: 'Store ID' }]
Retailstoreid;
@UI.identification: [{ position: 20, label: 'Business Date' }]
@UI.lineItem: [{ label: 'Business Date' }]
Businessdate;
@UI.identification: [{ position: 30, label: 'Amount' }]
@UI.lineItem: [{ label: 'Amount' }]
Tenderamount;
@UI.identification: [{ position: 40, label: 'Currency' }]
@UI.lineItem: [{ label: 'Currency' }]
Currency;
@UI.identification: [{ position: 60, label: 'Status' }]
@UI.lineItem: [{ label: 'Status' }]
Status;
} Service Definition: -
@EndUserText.label: 'Servive def for store details'
define service ZMUK_UI_STORE {
expose ZMUK_C_STORE as StoreDetails;
} Service Binding: -
Service binding binds your service definition to a specific protocol (in this case, OData V4 - UI).
RESULT: -
Initial records.
Click on any of the record and click on edit.
Changing the amount to '2500' and currency field to 'USD' and click on Save.
- Backend calculates and the event AuditStatusChanged is raised.
- Without refreshing the application, the Status field automatically changes to Pending Review.
Restrictions: -
- Event-driven side effects require with additional save (managed) or with unmanaged save.
- Events can only be raised during the late save phase.
- Events must be exposed using use event in the projection behavior.
- Enable side effects using use side effects in the projection.
- If both draft and non-draft entities are exposed in the same service binding, event-driven side effects are not supported.
- Newly created list items are not refreshed by event-driven side effects targeting an entire list until they exist in the database.
Conclusion: -
Event-driven RAP side effects provide an elegant way to synchronize backend-calculated data with the SAP Fiori UI after the save process. In the Store Audit scenario, the AuditStatusChanged event ensures that whenever Tender Amount or Currency changes lead to a new audit result, the Status field is automatically refreshed for all active users. This pattern is especially valuable for collaborative business applications where multiple users work on the same data and consistency is essential.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.