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
Andre_Fischer
Product and Topic Expert
Product and Topic Expert
0 Kudos
Hi Ramjee,

thank you very much for your blog post.

It helped me with an issue I had with a draft enabled application where I use an instanance based action to create additional child items.

Only after having performed the following chang
//action ( features : instance ) addChild2 parameter /DMO/I_RAP_GEN_PARAM_ADD_CHILD result [1] $self;
action ( features : instance ) addChild2 parameter /DMO/I_RAP_GEN_PARAM_ADD_CHILD result [1] /DMO/I_RAPGENERATORBONODE;

I am able to refresh my object page without getting the following error.

Unable to load the data.Error:


Number of specified key properties does not match number of key properties of type 'com.sap.gateway.srvd.dmo.rapgeneratorbo.v0001.RAPGeneratorBOType';


expected number of key properties '2'


Kind regards,

Andre

 

 

 
Ramjee_korada
Active Contributor
Hi Andre,

Thanks for your feedback and Glad that your issue is also resolved.

I was facing the same error and had to debug further to conclude solution. Most of the tutorials talks about $SELFf and we did not realize that key got changed.

So I mentioned this step as key point to remember.

Always, you are the first one to give best answers for all our questions.

 

Best wishes,

Ramjee Korada.
maheshpalavalli
Active Contributor
Nice one Ramjee!!

 
mohit_dev
Contributor
Good Job Ramjee!!
stefan-keuker
Participant
0 Kudos
Hi Ramjee,

I run into a lock with the 'MODIFY execute EDIT' as the entity from which a draft should be created is already locked by the RAP Framework when the action is triggered from the UI.

How did you get around that? Thanks for commenting,

Stefan.
Ramjee_korada
Active Contributor
0 Kudos
Hi Stefan,

Since it is custom action, I don't expect the framework to lock the entity.

What is your input in your test case? is it active entity ? or draft entity ?

May be you can explain more ( some screen shots of the logic ) about the problem to understand .

Best wishes,

Ramjee Korada

 

 

better.
stefan-keuker
Participant
0 Kudos
Hi Ramjee,

I'm using a managed scenario and in a managed scenario according to this help article about the action runtime the framework sets the entity lock when the action is executed. Then according to this help article for the edit action the edit action triggers an exclusive lock on the active entity instance.

So in the managed scenario both the action triggered lock and the edit triggered lock try to lock the same entity. Which is rejected.

I see you are using an unmanaged implementation. Did you implement the 'FOR LOCK' method?

Thanks for your response.

Stefan.
akoethe
Explorer
0 Kudos
Hi Ramjee,

thanks a lot for this post.

We have created an action based on this concept which works well. Now, we would like to do somehow the opposite. We would like to change our entity, save this change, and then create the draft instance and display.

We have an action "REWORK_DATA" which is only available in the display mode and when the field STATE of our root entity has the value "fixed".

This action changes the Field "STATE" to "Rework" using modify Entities, afterwards we use execute EDIT. The result parameter of the action returns the initial key (not draft) with the new key (draft) as %param.

Our problem now is that the displayed data of our Child entity depend on this field STATE in a quite complicated way. If  the state has the value "Fixed" we read from one database, if the state has the value "Rework" we mix up data from two databases.

The saved data are as we expect, but the draft instance has the wrong values for the child entity. The values have not been refreshed after the change of our State field. They are shown as they have been in the state Fixed.

Is there a way to assure that the Draft instance has they correct values?

Thanks a lot in advance!!

 

Best regards,

Alexandra
Ramjee_korada
Active Contributor
0 Kudos
Hi Alex,

It seems complicated and confusing. The only difference in draft and active instance is "IsActiveEntity".

Active : (ConUuid=5aa49b80-5db7-1edd-92f5-7bcd575b16df,IsActiveEntity=true)
Draft : (ConUuid=5aa49b80-5db7-1edd-92f5-7bcd575b16df,IsActiveEntity=false)

Is the issue of refreshing the item table automatically after an action on Header . Is it possible to connect over call ?

You can send me an invite -

koradaramjee@gmail.com or

https://www.linkedin.com/in/ramjee-korada-5a3a644b/

 

Best wishes,

Ramjee Korada
danielang
Discoverer
0 Kudos
Hey 🙂
If someone else is running into that problem on a managed instance, I want to share my solution:

It is possible do disable the default locking mechanism when an action is executed like that:
action (lock : none) yourActionName result [1] $self;

Now within this action it is possible to call the Edit action.
BTW. it is highly recommended to supply a content id (%cid parameter).

Kind regards,
Daniel Lang
stefan-keuker
Participant
0 Kudos
Hi Daniel,

good idea., guess then you can do the enqueue in the action implementation yourself. I ended up helping myself to use an unmanaged lock (lock master unmanaged) in the managed implementation. In the implementation I do enqueue but ignore existing locks held by the current application user.

Stefan.
Labels in this area