This post talks about adding fields to existing custom services in ABAP RESTful Application Programming. This will help you understand the steps that are required to add more fields without missing any steps.

This post refers to a RAP service created in the earlier post – Managed with unmanaged save.

Let us add two fields to the carrier entity.

  1. Data Complete – We will set this field if the name and currency are entered. Note that carrier id will always be available.
  2. The number of connections – This will be the number of connections created for the selected carrier.

The steps that we need to follow are as below

  1. Data Dictionary – Add fields to the base table and draft table. If the draft is not enabled, then the draft part can be ignored.
  2. CDS Entities – Add fields to the CDS Entity and also at the consumption/projection CDS Entity.
  3. Metadata Extension – Add fields in the metadata extension based on where the fields need to appear.
  4. Behavior Definition – Add fields in behavior definition mapping, also add field level code in case any properties like read-only need to be set up
  5. Update Save Method – This is required in case of unmanaged scenarios or managed scenarios with unmanaged save.

1. Add fields to the base table zjp_carrier and draft table zjp_d_carrier

@EndUserText.label : 'Flight Reference Scenario: Carrier'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zjp_carrier {

  key client            : abap.clnt not null;
  key carrier_id        : /dmo/carrier_id not null;
  name                  : /dmo/carrier_name;
  currency_code         : /dmo/currency_code;
  is_complete           : abap.char(1);
  no_of_connections     : abap.int1;
  local_created_by      : abp_creation_user;
  local_created_at      : abp_creation_tstmpl;
  local_last_changed_by : abp_locinst_lastchange_user;
  local_last_changed_at : abp_locinst_lastchange_tstmpl;
  last_changed_at       : abp_lastchange_tstmpl;

}

The draft table has fields matching with CDS Entities.

@EndUserText.label : 'Draft table for entity ZJP_I_CARRIER'
@AbapCatalog.enhancement.category : #EXTENSIBLE_ANY
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zjp_d_carrier {

  key mandt          : mandt not null;
  key carrierid      : /dmo/carrier_id not null;
  name               : /dmo/carrier_name;
  currencycode       : /dmo/currency_code;
  iscomplete         : abap.char(1);
  noofconnections    : abap.int1;
  localcreatedby     : abp_creation_user;
  localcreatedat     : abp_creation_tstmpl;
  locallastchangedby : abp_locinst_lastchange_user;
  locallastchangedat : abp_locinst_lastchange_tstmpl;
  lastchangedat      : abp_lastchange_tstmpl;
  "%admin"           : include sych_bdl_draft_admin_inc;

}

2. Add the fields to CDS Entities ZJP_I_CARRIER and ZJP_C_CARRIER

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Carrier Entity'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.usageType:{
    serviceQuality: #X,
    sizeCategory: #S,
    dataClass: #MIXED
}

define root view entity ZJP_I_CARRIER
  as select from zjp_carrier
  composition [0..*] of zjp_i_connection as _Connection
{
  key carrier_id            as CarrierId,
      name                  as Name,
      currency_code         as CurrencyCode,
      is_complete           as IsComplete,
      no_of_connections     as NoOfConnections,
      @Semantics.user.createdBy: true
      local_created_by      as LocalCreatedBy,
      @Semantics.systemDateTime.createdAt: true
      local_created_at      as LocalCreatedAt,
      @Semantics.user.lastChangedBy: 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,
      _Connection
}
@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata.allowExtensions: true
@EndUserText.label: 'Projection Entity for Carrier'
@ObjectModel.semanticKey: [ 'CarrierId' ]
@Search.searchable: true

define root view entity ZJP_C_CARRIER
  provider contract transactional_query
  as projection on ZJP_I_CARRIER
{
  key CarrierId,
      @Search.defaultSearchElement: true
      Name,
      CurrencyCode,
      IsComplete,
      NoOfConnections,
      LocalCreatedBy,
      LocalCreatedAt,
      LocalLastChangedBy,
      LocalLastChangedAt,
      LastChangedAt,
      /* Associations */
      _Connection : redirected to composition child zjp_c_connection
}

3. Add the fields to the metadata extension ZJP_C_CARRIER

@Metadata.layer: #CORE

@UI: {
    headerInfo: {
        typeName: 'Carrier',
        typeNamePlural: 'Carrier',
        title: {
            type: #STANDARD, value: 'Name'
        },
        description: {
            value: 'CarrierID'
        }
    },
    presentationVariant: [{
        sortOrder: [{
            by: 'Name',
            direction: #ASC
        }],
        visualizations: [{
            type: #AS_LINEITEM
        }]
    }]
}

annotate view ZJP_C_CARRIER with
{

  @UI.facet: [

       {
          label: 'Carrier Details',
          id: 'CarrierInfo',
          type: #COLLECTION,
          position: 10
       },
       {
          label: 'Carrier',
          id: 'Carrier',
          type: #IDENTIFICATION_REFERENCE,
          purpose: #STANDARD,
          parentId: 'CarrierInfo',
          position: 10
       },
      {
          id: 'Connection',
          purpose: #STANDARD,
          type: #LINEITEM_REFERENCE,
          label: 'Connections',
          position: 20,
          targetElement: '_Connection'
      }
    ]
  @UI.lineItem: [{ position: 10 }]
  @UI.identification: [{ position: 10 }]
  CarrierId;
  @UI.lineItem: [{ position: 20 }]
  @UI.identification: [{ position: 20 }]
  Name;
  @UI.lineItem: [{ position: 30 }]
  @UI.identification: [{ position: 30 }]
  CurrencyCode;
  @UI.lineItem: [{ position: 32 }]
  @UI.identification: [{ position: 32 }]
  @EndUserText.label: 'Info. complete?'
  IsComplete;
  @UI.lineItem: [{ position: 34 }]
  @UI.identification: [{ position: 34 }]
  @EndUserText.label: 'No. of Connections'
  NoOfConnections;
  @UI.lineItem: [{ position: 40 }]
  @UI.identification: [{ position: 40 }]
  LocalCreatedBy;
  @UI.lineItem: [{ position: 50 }]
  @UI.identification: [{ position: 50 }]
  LocalCreatedAt;
  @UI.lineItem: [{ position: 60 }]
  @UI.identification: [{ position: 60 }]
  LocalLastChangedBy;
  @UI.lineItem: [{ position: 70 }]
  @UI.identification: [{ position: 70 }]
  LocalLastChangedAt;
  @UI.lineItem: [{ position: 80 }]
  @UI.identification: [{ position: 80 }]
  LastChangedAt;
}

4. Add the fields to the Behavior definition ZJP_I_CARRIER mapping

  ...  
  
  mapping for zjp_carrier control zjp_x_carrier corresponding
  {
    CarrierId = carrier_id;
    Name = name;
    CurrencyCode = currency_code;
    IsComplete = is_complete;
    NoOfConnections = no_of_connections;
    LocalCreatedBy = local_created_by;
    LocalCreatedAt = local_created_at;
    LocalLastChangedBy = local_last_changed_by;
    LocalLastChangedAt = local_last_changed_at;
    LastChangedAt = last_changed_at;
  }

  ...

Additionally, the control structure zjp_x_carrier should also have these two new fields. This step may not be needed for read-only fields.

zjp_x_carrier

@EndUserText.label : 'zjp_x_carrier'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
define structure zjp_x_carrier {

  carrier_id            : xsdboolean;
  name                  : xsdboolean;
  currency_code         : xsdboolean;
  is_complete           : xsdboolean;
  no_of_connections     : xsdboolean;
  local_created_by      : xsdboolean;
  local_created_at      : xsdboolean;
  local_last_changed_by : xsdboolean;
  local_last_changed_at : xsdboolean;
  last_changed_at       : xsdboolean;

}

5. Add fields in the save_modified method

Go to the class zbp_jp_i_carrier from the behavior definition and locate the method save_modified in Local Types. If you miss this step, the changes will not be saved.

  METHOD save_modified.

    DATA : carriers    TYPE STANDARD TABLE OF zjp_carrier,
           carriers_u  TYPE STANDARD TABLE OF zjp_carrier,
           controls    TYPE STANDARD TABLE OF zjp_x_carrier,
           lr_carriers TYPE RANGE OF zjp_carrier-carrier_id.

    "Implement Logic for all possible Save Operations - Create / Update / Delete

    IF create IS NOT INITIAL.
      carriers = CORRESPONDING #( create-carrier MAPPING FROM ENTITY ).
      INSERT zjp_carrier FROM TABLE @carriers.
    ENDIF.

    IF update IS NOT INITIAL.
      carriers = CORRESPONDING #( update-carrier MAPPING FROM ENTITY ).
      controls = CORRESPONDING #( update-carrier MAPPING FROM ENTITY USING CONTROL ).

      SELECT * FROM zjp_carrier
        FOR ALL ENTRIES IN @carriers
        WHERE carrier_id = @carriers-carrier_id
        INTO TABLE @DATA(carriers_o).
      IF sy-subrc EQ 0.
        carriers_u =
          VALUE #(
            FOR i = 1 WHILE i LE lines( carriers )
            LET
              control =
                VALUE #( controls[ i ] OPTIONAL )
              carrier   =
                VALUE #( carriers[ i ] OPTIONAL )
              carrier_o =
                VALUE #( carriers_o[ carrier_id = carrier-carrier_id ] OPTIONAL )
            IN
            ( carrier_id            = carrier-carrier_id
              name                  = COND #( WHEN control-name IS NOT INITIAL
                                              THEN carrier-name
                                              ELSE carrier_o-name )
              currency_code         = COND #( WHEN control-currency_code IS NOT INITIAL
                                              THEN carrier-currency_code
                                              ELSE carrier_o-currency_code )
              is_complete           = COND #( WHEN control-is_complete IS NOT INITIAL
                                              THEN carrier-is_complete
                                              ELSE carrier_o-is_complete )
              no_of_connections     = COND #( WHEN control-no_of_connections IS NOT INITIAL
                                              THEN carrier-no_of_connections
                                              ELSE carrier_o-no_of_connections )
              local_created_by      = carrier_o-local_created_by
              local_created_at      = carrier_o-local_created_at
              local_last_changed_by = COND #( WHEN control-local_last_changed_by IS NOT INITIAL
                                              THEN carrier-local_last_changed_by
                                              ELSE carrier_o-local_last_changed_by )
              local_last_changed_at = COND #( WHEN control-local_last_changed_at IS NOT INITIAL
                                              THEN carrier-local_last_changed_at
                                              ELSE carrier_o-local_last_changed_at )
              last_changed_at       = COND #( WHEN control-last_changed_at IS NOT INITIAL
                                              THEN carrier-last_changed_at
                                              ELSE carrier_o-last_changed_at )

             ) ).

      ELSE.
        carriers_u = carriers.
      ENDIF.
      UPDATE zjp_carrier FROM TABLE @carriers_u.
    ENDIF.

    IF delete IS NOT INITIAL.
      lr_carriers = VALUE #( FOR ls_carrier_id IN delete-carrier
                           ( sign = 'I' option ='EQ' low = ls_carrier_id-carrierid )
                           ).

      DELETE FROM zjp_carrier WHERE carrier_id IN @lr_carriers.
    ENDIF.


  ENDMETHOD.

Test the application

Once everything is active the fields should be displayed on the application even though the values are blank.

This way, fields can be added to existing custom services. The next post will talk about how these fields can be determined using behavior definition determinations.

Visit ABAP RESTful Application Programming Model to explore all articles on ABAP RAP Model.


If you like the content, please subscribe…

Join 4,016 other subscribers

Discovering ABAP YouTube Channel