Wednesday, 2 February 2022

ABAP Restful Application Programming : Enabling draft feature to the custom implemented Actions

Introduction:

Enabling Draft is most common feature in current projects irrespective of Managed/Unmanaged scenario in fiori applications.

In short, lets see what the draft is.

Draft-enabled applications allow the end user to store changed data in the backend and continue at a later point in time or from a different device, even if the application terminates unexpectedly. This kind of scenario needs to support a stateless communication and requires a replacement for the temporary in-memory version of the business entity that is created or edited. This temporary version is kept on a separate database table and is known as draft data. Drafts are isolated in their own persistence and do not influence existing business logic until activated.                                                          

Problem statement:

Standard RAP framework takes care of creation/modification of draft records for all standard operations (CREATE / UPDATE) but it is developer’s responsibility to implement draft for all custom actions in the applications. In this blog post, we will see how we can implement draft for custom actions.

Challenge:

When we use “MODIFY ENTITIES”, it will update the records of the current instance and commit to the database .We need them to be updated in draft records but not actual records.

Solution:

Before we use “MODIFY ENTITIES”, we need to check if the current instance is active then we need to create Draft instance for it. This can be achieved by executing “EDIT” on active instance. An “EDIT”  action creates a new draft document automatically by copying the corresponding active instance data to the draft table. Immediately EDIT triggers an exclusive lock for the active instance. This lock is maintained until the durable lock phase of the draft ends, which is either when the draft is activated, or when the durable lock expires after a certain time.

This EDIT action has a parameter “preserve_changes” whose default value is false and system overwrites the draft instance if already exists, but we must make sure not to lose the draft information. Hence, we need to fill ‘true” to the parameter “preserve_changes”.

While defining the Action, the key point to make sure to return the entity but not $self. After draft instance is created, we must send the draft instance as output while the active instance is input to the action.

Implementation steps:

( Focus of the blog post is from Step #7  and if you are familiar with basic steps then skip until step #6 )

1. Create a table with underlying fields

@EndUserText.label : 'Purchase contract'

@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE

@AbapCatalog.tableCategory : #TRANSPARENT

@AbapCatalog.deliveryClass : #A

@AbapCatalog.dataMaintenance : #RESTRICTED

define table zrk_t_pur_con {

  key client           : abap.clnt not null;

  key con_uuid         : sysuuid_x16 not null;

  object_id            : zrk_pur_con_id;

  description          : zrk_description;

  buyer                : zrk_buyer_id;

  supplier             : zrk_sup_no;

  sup_con_id           : zrk_sup_con_id;

  comp_code            : zrk_company_code;

  stat_code            : zrk_stat_code;

  fiscl_year           : zrk_fiscal_year;

  valid_from           : zrk_valid_from;

  valid_to             : zrk_valid_to;

  created_by           : abp_creation_user;

  created_at           : abp_creation_tstmpl;

  last_changed_by      : abp_locinst_lastchange_user;

  last_changed_at      : abp_lastchange_tstmpl;

  locl_last_changed_at : abp_locinst_lastchange_tstmpl;

}​

2. Create an interface view for data modeling

@AccessControl.authorizationCheck: #CHECK

@EndUserText.label: 'ZRK_I_PUR_CON_UD'

define root view entity ZRK_I_PUR_CON_UD as select from zrk_t_pur_con

{

    key con_uuid as ConUuid,

    object_id as ObjectId,

    description as Description,

    buyer as Buyer,

    supplier as Supplier,

    sup_con_id as SupConId,

    comp_code as CompCode,

    stat_code as StatCode,

    fiscl_year as FisclYear,

    valid_from as ValidFrom,

    valid_to as ValidTo,

    created_by as CreatedBy,

    created_at as CreatedAt,

    last_changed_by as LastChangedBy,

    last_changed_at as LastChangedAt,

    locl_last_changed_at as LoclLastChangedAt

}

3. Create a projection view to expose in the UI service

@AccessControl.authorizationCheck: #CHECK

@EndUserText.label: 'Project for unmanaged draft'

@Metadata.allowExtensions: true

define root view entity ZRK_C_PUR_CON_UD 

provider contract transactional_query

as projection on ZRK_I_PUR_CON_UD

{

    key ConUuid,

    ObjectId,

    Description,

    Buyer,

    Supplier,

    SupConId,

    CompCode,

    StatCode,

    FisclYear,

    ValidFrom,

    ValidTo,

    CreatedBy,

    CreatedAt,

    LastChangedBy,

    LastChangedAt,

    LoclLastChangedAt

}

4. Enrich UI with metadata extension

@Metadata.layer: #CORE

@UI: {

  headerInfo: {

    typeName: 'Purchase Contract',

    typeNamePlural: 'Purchase Contracts',

    description: {

    type: #STANDARD,

    value: 'Description'

        },

        title: {

//    type: #STANDARD,

    value: 'ObjectId'

        }

    }

}

annotate entity ZRK_C_PUR_CON_UD

    with 

{

  @UI.facet: [ {

          id: 'Header',

          type: #HEADERINFO_REFERENCE,

          label: 'Header',

          purpose: #HEADER,

          position: 10,

          targetQualifier: 'Header'

  },

  {     id: 'General',

        type: #IDENTIFICATION_REFERENCE,

        purpose: #STANDARD,

        label: 'General',

        position: 20 ,

        targetQualifier: 'General'},


  {     id: 'Validities',

        type: #IDENTIFICATION_REFERENCE,

        label: 'Validities',

        position: 30 ,

        targetQualifier: 'Validities'}

   ]

  @UI.hidden: true

  @UI.lineItem: [{

      position: 10 ,

      type: #FOR_ACTION,

      label: 'Forward',

      dataAction: 'Forward'

  }]

    @UI.identification: [{

      position: 10 ,

      type: #FOR_ACTION,

      label: 'Forward',

      dataAction: 'Forward'

  }]

  ConUuid;

  @UI:{ lineItem: [{ position: 10 }] , identification: [{ position: 10 , qualifier: 'General'}]}

  @UI.selectionField: [{ position: 10 }]

  ObjectId;

  @UI.selectionField: [{ position: 20 }]

  @UI:{ lineItem: [{ position: 20 }] , identification: [{ position: 20 , qualifier: 'General'}]}

  Description;

  @UI.selectionField: [{ position: 30 }]

  @UI:{ lineItem: [{ position: 30 }] , identification: [{ position: 30 , qualifier: 'General'}]}

  @Consumption.valueHelpDefinition: [{ 

      entity: {

          name: 'ZRK_I_BUYER',

          element: 'BuyerId'

      }

   }]  

  Buyer;

  @UI.selectionField: [{ position: 40 }]

  @UI:{ lineItem: [{ position: 40 }] , identification: [{ position: 40 ,qualifier: 'General'}]}

  @Consumption.valueHelpDefinition: [{ entity: {

      name: 'ZRK_I_SUPPLIER',

      element: 'SupNo'

  } ,

        useForValidation: true

  }]  

  Supplier;

  @UI:{ lineItem: [{ position: 50 }] , identification: [{ position: 50 ,qualifier: 'General'}]}

  @Consumption.valueHelpDefinition: [{ entity: {

      name: 'ZRK_I_SUP_CON',

      element: 'SupConId'

  } ,

  additionalBinding: [{

      localElement: 'Supplier',

      localConstant: '',

      element: 'SupNo',

      usage: #FILTER_AND_RESULT 

  }] , 

        useForValidation: true

  }]   

  SupConId;

  @UI.selectionField: [{ position: 50 }]

  @UI:{ lineItem: [{ position: 50 }] , identification: [{ position: 55 ,qualifier: 'General'}]}

  @Consumption.valueHelpDefinition: [{ entity: {

      name: 'ZRK_I_COMP_CODE',

      element: 'CompCode'

  } ,

        useForValidation: true

  }]   

  CompCode;  

  @UI:{ lineItem: [{ position: 60 }] , identification: [{ position: 60 ,qualifier: 'Header'}]}

  StatCode;

  @UI:{ lineItem: [{ position: 70 }] , identification: [{ position: 70 , qualifier: 'Validities' }]}

  ValidFrom;

  @UI:{ lineItem: [{ position: 80 }] , identification: [{ position: 80 , qualifier: 'Validities' }]}

  ValidTo;

  @UI:{ lineItem: [{ position: 80 }] , identification: [{ position: 90 , qualifier: 'Validities' }]}

  @Consumption.valueHelpDefinition: [{ 

      entity: {

          name: 'ZRK_I_FISCAL_YEAR',

          element: 'fiscal_year'

      }

   }]   

  FisclYear;

  @UI:{ lineItem: [{ position: 90 }] , identification: [{ position: 100, qualifier: 'General' , label: 'Created By' }]}

  CreatedBy; 

  @UI.hidden: true

  CreatedAt;

  @UI.hidden: true

  LastChangedBy;

  @UI.hidden: true

  LastChangedAt;

  @UI.hidden: true

  LoclLastChangedAt;

}​

5. Create a behavior definition “with Draft ”

unmanaged implementation in class zbp_rk_i_pur_con_ud unique;

with draft;

define behavior for ZRK_I_PUR_CON_UD alias PurCon

//late numbering

draft table zrk_dt_pur_con_u

lock master total etag LoclLastChangedAt

authorization master ( instance )

etag master LoclLastChangedAt

{

  field ( numbering : managed ) ConUuid;

  field ( readonly ) ObjectId , CreatedBy;

  create;

  update;

  delete;

  //draft action Edit;

  determination set_pc_num on modify { create; }

}​

6. Then create implementation class and apply your logic for basic operations.

7. Define custom action “Forward”

Input parameter : Buyer to select from F4 help

Result parameter : As explained above, we have to return the entity but not $self.

action Forward parameter ZRK_I_FWD_BUYER result [1] ZRK_I_PUR_CON_UD ;​

8. Its time to implement “Action” and refer to below snippet

◉ Get the user input to be updated into local variable

◉ Prepare the draft instance for all active instances from list of records that user selected for Action by executing EDIT

◉ Copy “keys” into local table and modify the property “%is_draft” to “if_abap_behv=>mk-on” so that further processing happens on draft instances but not active instances anymore.

◉ Then READ the entities with latest instances and MODIFY the entities to reflect the changes on draft instances

◉ Pass the result back to UI with draft instance information.

  METHOD Forward.

*/.. Get new buyer information

    READ TABLE keys ASSIGNING FIELD-SYMBOL(<fs_key>) INDEX 1.

    IF sy-subrc EQ 0.

      DATA(lv_new_buyer) = <fs_key>-%param-Buyer.

    ENDIF.

*/..Create a draft instance for all active instance

*/.. There could be multiple records mixed with draft/active when multi-select is enabled.

    MODIFY ENTITIES OF zrk_i_pur_con_ud IN LOCAL MODE

    ENTITY PurCon

    EXECUTE edit FROM

    VALUE #( FOR <fs_active_key> IN keys WHERE ( %is_draft = if_abap_behv=>mk-off )

                                            ( %key = <fs_active_key>-%key

                                              %param-preserve_changes = 'X'

                                            ) )

          REPORTED DATA(edit_reported)

          FAILED DATA(edit_failed)

          MAPPED DATA(edit_mapped).

    DATA(lt_temp_keys) = keys.

    LOOP AT lt_temp_keys ASSIGNING FIELD-SYMBOL(<fs_temp_keys>).

        <fs_temp_keys>-%is_draft = if_abap_behv=>mk-on.

    ENDLOOP.

*/.. Read the existing Data

    READ ENTITIES OF zrk_i_pur_con_ud IN LOCAL MODE

    ENTITY PurCon

    FIELDS ( Buyer )

    WITH CORRESPONDING #( lt_temp_keys )

    RESULT DATA(lt_buyer).

*/.. Then modify the draft instance but not active instance

    MODIFY ENTITIES OF zrk_i_pur_con_ud IN LOCAL MODE

    ENTITY PurCon

    UPDATE FIELDS ( Buyer )

    WITH VALUE #( FOR <fs_rec_draft> IN lt_buyer ( %tky = <fs_rec_draft>-%tky

                                             %is_draft = '01'

                                             Buyer = lv_new_buyer ) )

                                   REPORTED edit_reported

                                   FAILED edit_failed

                                   MAPPED DATA(lt_updated).

*/.. Read the data to send back to UI. / Optional - This is to check if the values are updated ?

    READ ENTITIES OF zrk_i_pur_con_ud IN LOCAL MODE

    ENTITY PurCon

    ALL FIELDS

    WITH CORRESPONDING #( lt_temp_keys )

    RESULT DATA(lt_buyer_updated).

*/.. Pass the data to UI.

    result = CORRESPONDING #( lt_buyer_updated ).

  ENDMETHOD.​

9. Project the behavior definition, Define the “Service Definition” and generate “Service Binding”.

projection;

use draft;

define behavior for ZRK_C_PUR_CON_UD alias PurCon

{

use create;

use update;

use delete;

use action Forward ;

}​

10. Preview the application to test.

Scenario #1 : Take an example of active instance ( PC1 ). As we see, Draft is created and buyer details are updated after “Action” is triggered.

SAP Fiori for SAP S/4HANA, SAP ABAP RESTful Application Programming Model, SAP BTP, SAP ABAP Environment, SAP S/4HANA, SAP ABAP Career, SAP ABAP Skills, SAP ABAP Prep

SAP Fiori for SAP S/4HANA, SAP ABAP RESTful Application Programming Model, SAP BTP, SAP ABAP Environment, SAP S/4HANA, SAP ABAP Career, SAP ABAP Skills, SAP ABAP Prep

Scenario #2: Take an example of draft instance ( PC2 ) , the existing Draft itself is updated with buyer details after “Action” triggered

SAP Fiori for SAP S/4HANA, SAP ABAP RESTful Application Programming Model, SAP BTP, SAP ABAP Environment, SAP S/4HANA, SAP ABAP Career, SAP ABAP Skills, SAP ABAP Prep

SAP Fiori for SAP S/4HANA, SAP ABAP RESTful Application Programming Model, SAP BTP, SAP ABAP Environment, SAP S/4HANA, SAP ABAP Career, SAP ABAP Skills, SAP ABAP Prep

Source: sap.com

No comments:

Post a Comment