Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
Ramjee_korada
Active Contributor

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 ( for more details on input for actions, please refer blog )
    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.
    Scenario #2 :Take an example of draft instance ( PC2 ) , the existing Draft itself is updated with buyer details after "Action" triggered


 

Conclusion:


we have seen and understood how to create draft instances from active instances and update the details on draft but not actual instance. So that end user can review it and decide to Save / Discard them in object page.
11 Comments
Labels in this area