Technical Articles
SAP ABAP RAP : Custom Entities with compositions relationship in a Fiori Elements App
Introduction:
ABAP Restful Application Programming is an efficient and cloud-compatible development model that enables rapid creation of Fiori apps.
This programming model facilitates both Managed and Unmanaged Implementation approaches, although the core data source must originate from a CDS view or a table within the same system in both scenarios. However, the non-key fields can be calculated on the fly using virtual elements.
When retrieving data from a remote source through an API or performing complex calculations, managing CDS view entities becomes challenging. Further complexities arise when compositions are involved.
Aim of this blog post is to demonstrate the abilities of custom entities in RAP and create a Fiori application with composition relationships.
Implementation:
The basic implementation steps of a custom entity can be found in the documentation and not demonstrated here in detail.
Lets take a Business data model for Shipment request and its items.
- Create a root entity for Shipment request and an entity for items.
- Enrich the metadata for UI in both the entities.
@EndUserText.label: 'Shipment Request' @ObjectModel.query.implementedBy: 'ABAP:ZRK_CL_CE_SHIPMENT_REQ' @UI.headerInfo: { typeName: 'Shipment Request', typeNamePlural: 'Shipment Requests', title: { type: #STANDARD, value: 'ShipReqNo' }, description: { type: #STANDARD, value: 'Description' } } define root custom entity ZRK_CE_I_ShipReq // with parameters parameter_name : parameter_type { @UI.facet : [{ id : 'General', purpose : #STANDARD, parentId : '', position : 10, isPartOfPreview: true, label : 'General', type : #COLLECTION, targetQualifier: 'General' }, { id : 'BasicInfo', purpose : #STANDARD, parentId : 'General', position : 10, isPartOfPreview: true, label : 'Basic Info', type : #FIELDGROUP_REFERENCE, targetQualifier: 'QFBasicInfo' }, { id : 'SenderAddress', purpose : #QUICK_VIEW, parentId : 'General', position : 20, isPartOfPreview: true, label : 'Sender Address', type : #FIELDGROUP_REFERENCE, targetQualifier: 'QFSenderAddress' }, { id : 'Items', purpose : #STANDARD, position : 30, label : 'Items', type : #LINEITEM_REFERENCE, targetElement: '_ShipReqItems' }] @EndUserText.label: 'Shipment Req.No.' @UI.selectionField: [{position: 10 }] @UI.lineItem : [{ position: 10 }] @UI.identification: [{ position: 10 }] key ShipReqNo : zrk_ship_req; @EndUserText.label: '' @UI.lineItem : [{ position: 20 }] @UI.identification: [{ position: 20 }] @UI.fieldGroup: [ { type: #STANDARD, position: 10 , qualifier: 'QFBasicInfo' } ] Description : /dmo/description; @EndUserText.label: 'Submission Date' @Consumption.filter.selectionType: #INTERVAL @UI.selectionField: [{position: 40 }] @UI.lineItem : [{ position: 30 }] @UI.identification: [{ position: 30 }] @UI.fieldGroup: [ { type: #STANDARD, position: 20 , qualifier: 'QFBasicInfo' } ] SubmissionDate : abap.dats; @EndUserText.label: 'Name' @UI.lineItem : [{ position: 40 }] @UI.identification: [{ position: 40 }] @UI.fieldGroup: [ { type: #STANDARD, position: 10 , qualifier: 'QFSenderAddress' } ] SenderName : abap.char( 40 ); @EndUserText.label: 'Company' @UI.identification: [{ position: 10 }] @UI.fieldGroup: [ { type: #STANDARD, position: 20 , qualifier: 'QFSenderAddress' } ] SenderCompany : abap.char( 40 ); @EndUserText.label: 'Street No' @UI.lineItem : [{ position: 50 }] @UI.identification: [{ position: 50 }] @UI.fieldGroup: [ { type: #STANDARD, position: 30 , qualifier: 'QFSenderAddress' } ] SenderStreetNo : abap.char( 40 ); @EndUserText.label: 'City' @UI.lineItem : [{ position: 60 }] @UI.selectionField: [{position: 20 }] @UI.identification: [{ position: 60 }] @UI.fieldGroup: [ { type: #STANDARD, position: 40 , qualifier: 'QFSenderAddress' } ] SenderCity : abap.char( 20 ); @EndUserText.label: 'Zip Code' @UI.identification: [{ position: 70 }] @UI.fieldGroup: [ { type: #STANDARD, position: 50 , qualifier: 'QFSenderAddress' } ] SenderZipCode : abap.numc( 5 ); @EndUserText.label: 'Country' @UI.lineItem : [{ position: 60 }] @UI.selectionField: [{position: 30 }] @UI.identification: [{ position: 80 }] @UI.fieldGroup: [ { type: #STANDARD, position: 60 , qualifier: 'QFSenderAddress' } ] SenderCountry : abap.char( 20 ); LocalLastChangedOn : abp_locinst_lastchange_tstmpl; }
@EndUserText.label: 'Shipment Request Item' @ObjectModel.query.implementedBy: 'ABAP:ZRK_CL_CE_SHIPMENT_REQ' @UI.headerInfo: { typeName: 'Shipment Request Item', typeNamePlural: 'Shipment Request items', title: { type: #STANDARD, value: 'ShipReqItemNo' }, description: { type: #STANDARD, value: 'Description' } } define custom entity ZRK_CE_I_ShipReqItem { @UI.facet : [{ id : 'General', purpose : #STANDARD, position : 10, label : 'General', type : #IDENTIFICATION_REFERENCE }] @UI.hidden : true key ShipReqNo : zrk_ship_req; @UI.lineItem : [{ position: 10 }] key ShipReqItemNo : abap.numc( 3 ); @UI.lineItem : [{ position: 20 }] @UI.identification : [{ position: 10 }] Description : /dmo/description; @UI.lineItem : [{ position: 30 }] @UI.identification : [{ position: 20 }] PackageSize : abap.char( 2 ); @UI.lineItem : [{ position: 40 }] @UI.identification : [{ position: 30 }] PackageQuantity : abap.numc( 2 ); @UI.lineItem : [{ position: 50 }] @UI.identification : [{ position: 40 }] ShipmentStatus : abap.char(15); @UI.lineItem : [{ position: 60 }] @UI.identification : [{ position: 50 }] DispatchDate : abap.dats; @UI.lineItem : [{ position: 70 }] @UI.identification : [{ position: 60 }] DeliveryDate : abap.dats; @UI.lineItem : [{ position: 80 }] @UI.identification : [{ position: 70 }] RecipientName : abap.char( 40 ); @UI.identification : [{ position: 80 }] RecipientCompany : abap.char( 40 ); @UI.identification : [{ position: 90 }] RecipientStreetNo : abap.char( 40 ); @UI.identification : [{ position: 100 }] RecipientCity : abap.char( 20 ); @UI.identification : [{ position: 110 }] RecipientZipCode : abap.numc( 5 ); @UI.identification : [{ position: 120 }] RecipientCountry : abap.char( 20 ); }
- Edit the entities as below for Composition and Parent relationship. This is a very important steps as it is different syntax .
define root custom entity ZRK_CE_I_ShipReq // with parameters parameter_name : parameter_type { ... ... ... _ShipReqItems : composition [0..*] of ZRK_CE_I_ShipReqItem ; }
define custom entity ZRK_CE_I_ShipReqItem { ... ... ... _ShipReq : association to parent ZRK_CE_I_ShipReq on $projection.ShipReqNo = _ShipReq.ShipReqNo ; }
- Implement the logic to read the data from API / AMDP / Complex queries
CLASS zrk_cl_ce_shipment_req DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. INTERFACES if_rap_query_provider. PRIVATE SECTION. ENDCLASS. CLASS zrk_cl_ce_shipment_req IMPLEMENTATION. METHOD if_rap_query_provider~select. CASE io_request->get_entity_id( ). WHEN 'ZRK_CE_I_SHIPREQ'. " Core logic is wrapped as it is not agenda for this blog zrk_cl_ce_manage_shipreq=>get_instance( )->get_shipment_requests( EXPORTING io_request = io_request IMPORTING et_shipreq_resp = DATA(lt_shipreq_resp) ). IF io_request->is_data_requested( ). io_response->set_data( lt_shipreq_resp ). ENDIF. IF io_request->is_total_numb_of_rec_requested( ). io_response->set_total_number_of_records( lines( lt_shipreq_resp ) ). ENDIF. WHEN 'ZRK_CE_I_SHIPREQITEM'. " Core logic is wrapped as it is not agenda for this blog zrk_cl_ce_manage_shipreq=>get_instance( )->get_shipment_request_items( EXPORTING io_request = io_request IMPORTING et_shipreqitem_resp = DATA(lt_shipreqitem_resp) ). IF io_request->is_data_requested( ). io_response->set_data( lt_shipreqitem_resp ). ENDIF. IF io_request->is_total_numb_of_rec_requested( ). io_response->set_total_number_of_records( lines( lt_shipreqitem_resp ) ). ENDIF. ENDCASE. ENDMETHOD. ENDCLASS.
- Expose these entities in service definition.
@EndUserText.label: 'SD for Shipment Req' define service ZRK_UI_CE_ShipReq { expose ZRK_CE_I_ShipReq as ShipReq; expose ZRK_CE_I_ShipReqItem as ShipReqItem; }
- Generate the service binding for this service definition to check preview.
- Create the Behavior definition to add transactional operations such as CRUD operations and other features.
unmanaged implementation in class zrk_bp_ce_i_shipreq unique; strict ( 2 ); define behavior for ZRK_CE_I_ShipReq alias ShipReq late numbering lock master authorization master ( instance ) etag master LocalLastChangedOn { field ( readonly) ShipReqNo ; create ; update; delete; association _ShipReqItems { create ; } } define behavior for ZRK_CE_I_ShipReqItem alias ShipReqItem late numbering lock dependent authorization master ( instance ) //etag dependent { field ( readonly) ShipReqNo , ShipReqItemNo ; update; delete; association _ShipReq ; }
- Now the application is ready for usage.
- Note that
- Unmanaged implementation is only supported and hence implement CRUD methods in behaviour bool.
- Late numbering is only possible.
- Draft is not possible on custom entities.
- If transactional capabilities are required, OData V2 is only possible and ODataV4 does not support non-draft create operation.
For more information on RAP, please follow on community page and developers.com
Hello, thanks for the blog.
For us it doesn't jump into the CASE for item. (WHEN 'ZRK_CE_I_SHIPREQITEM'.)
Except when we query the filer.
METHOD if_rap_query_provider~select.
IF io_request->is_data_requested( ).
TRY.
"get and add filters
DATA(lt_filter) = io_request->get_filter( )->get_as_ranges( ). " get_filter_conditions( ).
CATCH cx_rap_query_filter_no_range INTO DATA(lx_no_sel_option).
ENDTRY.
ENDIF.
DATA(lv_top) = io_request->get_paging( )->get_page_size( ).
DATA(lv_skip) = io_request->get_paging( )->get_offset( ).
DATA(lt_fields) = io_request->get_requested_elements( ).
DATA(lt_sort) = io_request->get_sort_elements( ).
DATA(lv_entity) = io_request->get_entity_id( ).
"check if filter condition is for a single read
TRY.
DATA(lt_rngshipreqno) = lt_filter[ name = 'SHIPREQNO' ] range.
IF lt_rngshipreqno[ 1 ]-sign = 'I' AND lt_rngshipreqno[ 1 ]-option = 'EQ'
AND lt_rngshipreqno[ 1 ]-low IS NOT INITIAL.
lv_entity = 'ZRK_CE_I_SHIPREQITEM'.
ENDIF.
CATCH cx_sy_itab_line_not_found." into cx.
ENDTRY.
CASE lv_entity.
WHEN 'ZRK_CE_I_SHIPREQ'.
...
Unfortunately, it is then not possible to display more than one item in the list. Or is there a solution?
Hi Ivan,
Thanks for the feedback. It is possible to show multiple items on the item table. It's not a restriction.
Have you mentioned below on child entity ?
Best wishes,
Ramjee Korada
Hi Ramjee
Our problem was that the class method never called with the Detail/Item entity. Thats how it works:
Thanks a lot.
Best regards
Ivan
Hi Ivan,
I see that debugger is getting triggered separately for item table. If the issue still persists, you can send an invite in Linkedin to check together.
Best wishes,
Ramjee Korada.
how can i close assocation? i dont want to show items of row.
Sorry. I did not understand. what do you mean by 'Close Association'