Friday 13 October 2023

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.

1. Create a root entity for Shipment request and an entity for items.
2. 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 );
      
}​

3. 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 ;
}

4. 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.​

5. 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;
}​

6. Generate the service binding for this service definition to check preview.

7. 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 ;
}​

8. Now the application is ready for usage.

SAP ABAP Certification, SAP ABAP Tutorial and Materials, SAP ABAP Guides, SAP ABAP Career, SAP ABAP Skills, SAP ABAP Jobs

9. Note that

i. Unmanaged implementation is only supported and hence implement CRUD methods in behaviors' bool.
ii. Late numbering is only possible.
iii. Draft is not possible on custom entities.
iv. If transactional capabilities are required, OData V2 is only possible and ODataV4 does not support non-draft create operation.

No comments:

Post a Comment