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.Active%20instance%20before%20ActionDraft%20after%20Action
    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.

Sara Sampaio

Sara Sampaio

Author Since: March 10, 2022

0 0 votes
Article Rating
Subscribe
Notify of
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x