Skip to Content
Technical Articles
Author's profile photo Ramjee Korada

ABAP Restful Application Programming – Unmanaged Draft for non-guid key based Legacy objects.

Introduction :

In this blog post, we will see one of the common problems and possible solutions in the Unmanaged Draft scenario in ABAP RAP. As we know, the keys are automatically drawn for draft records. Imagine as a case, we have a legacy data model (EKKO, VBAK) whose keys are not GUID based.

Problem statement:

 

If we develop a RAP application based on it. Application will not let any user create another draft in the whole system. If it is based on GUID Keys, then we could have marked the key as a numbering field to draw automatic keys. In this case, the application already consumed default keys ( ‘     ‘ for CHAR, ’00..0’ for NUMC ) and returned the error below.


​Error analysis is indicated as below :
The application has indicated exception “CX_CSP_ACT_INTERNAL” as the reason for the
termination:
A duplicate primary key ” was detected when inserting data into persistence ‘ZRKT_DT_EKKO’.
The exception is related to the previous exception “CX_SY_OPEN_SQL_DB” that occurred in
program “CL_CSP_SQL_ACCESS=============CP”, in line 12 of include “CL_CSP_SQL_ACCESS=============CM003”. The reason for this exception
was:
A data record is to be inserted even though a data record with the same primary key or same unique secondary key exists.

Technical representation of the business requirement:

  1. Unmanaged Implementation
  2. Draft handling
  3. Late numbering ( number can only be generated by BAPI during saving )
  4. Non-Guid key based legacy tables ( Ex: EKKO , VBAK )
  5. Number range to be consumed for actual documents but not for draft documents

​Solution option:

This looks to be a limitation in RAP as this question has not been answered for a year . I hope SAP will publish a perfect solution soon. Meanwhile we can apply the solution below to achieve the functionality with RAP –

Key points :

  1. ” Draft table does not need to have the same keys as base table but need to have same as base/interface view”. 
  2. Append additional structure with GUID field to the base table.
Below are the steps:
  1. Check the base table is Extensible.
  2. If yes, then append a structure with temp_guid field
  3. This would be a non-key field in the existing structure so that existing entries or the legacy data model is not impacted.
  4. ​Adjust base/interface and projection view with this new field as key so that it becomes key for your application data model.
  5. Adjust Behavior definition with new field with numbering
  6. Alter the draft table with this adjustment.
  7. Implement the methods for Create/Update/Delete/Read being unmanaged scenarios.
  8. On SAVE during Create, New key can be generated with a number range object and the developer has full control on persisting the actual records into existing tables.
​Implementation:
  1. Base table is extensible
    @EndUserText.label : 'Proc doc'
    @AbapCatalog.enhancement.category : #EXTENSIBLE_CHARACTER_NUMERIC
    @AbapCatalog.tableCategory : #TRANSPARENT
    @AbapCatalog.deliveryClass : #A
    @AbapCatalog.dataMaintenance : #RESTRICTED
    define table zrkt_ekko {
      key client           : abap.clnt not null;
      key object_id        : zrk_pur_con_id not null;
      description          : zrk_description;
      buyer                : zrk_buyer_id;
      supplier             : zrk_sup_no;
      sup_con_id           : zrk_sup_con_id;
      send_via             : zrk_send_via_code;
      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. Append the structure having temp_guid to base table
    @EndUserText.label : 'Strcuture include for ZRKT_EKKO'
    @AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
    extend type zrkt_ekko with zzrkt_exten_incld {
      zztemp_guid : sysuuid_x16;
    
    }​
  3. This would be a non-key field in the existing structure so that existing entries or the legacy data model is not impacted.
  4. ​Adjust base/interface view with this new field as key so that it becomes key for your application data model.
    @AccessControl.authorizationCheck: #CHECK
    @EndUserText.label: '##GENERATED Test proc doc'
    define root view entity ZRKT_I_PROC_DOC
      as select from zrkt_proc_doc as ProcDoc
    {
      key zztemp_guid as TempGuid,
      object_id as ObjectID,
      description as Description,
      buyer as Buyer,
      supplier as Supplier,
      sup_con_id as SupConID,
      send_via as SendVia,
      comp_code as CompCode,
      stat_code as StatCode,
      fiscl_year as FisclYear,
      valid_from as ValidFrom,
      valid_to as ValidTo,
      @Semantics.user.createdBy: true
      created_by as CreatedBy,
      @Semantics.systemDateTime.createdAt: true
      created_at as CreatedAt,
      @Semantics.user.localInstanceLastChangedBy: true
      last_changed_by as LastChangedBy,
      @Semantics.systemDateTime.lastChangedAt: true
      last_changed_at as LastChangedAt,
      @Semantics.systemDateTime.localInstanceLastChangedAt: true
      locl_last_changed_at as LoclLastChangedAt
      
    }​
    @AccessControl.authorizationCheck: #CHECK
    @Metadata.allowExtensions: true
    @EndUserText.label: 'Projection View for ZRKT_I_EKKO'
    define root view entity ZRKT_C_EKKO
      as projection on ZRKT_I_EKKO
    {
      key TempGuid, 
      objectid,
      description,
      buyer,
      supplier,
      supconid,
      sendvia,
      compcode,
      statcode,
      fisclyear,
      validfrom,
      validto,
      locllastchangedat
      
    }
  5. Adjust Behavior definition with new field with numbering
    unmanaged implementation in class ZRKT_BP_EKKO unique;
    strict;
    with draft;
    
    define behavior for ZRKT_I_EKKO alias Ekko
    //persistent table zrkt_ekko
    draft table ZRKT_DT_EKKO
    etag master LoclLastChangedAt
    lock master total etag LastChangedAt
    authorization master( global )
    
    {
    
     field ( readonly ) ObjectID;
     field( numbering : managed , readonly) TempGuid; 
    
      field ( readonly ) CreatedAt,
                         CreatedBy,
                         LastChangedAt,
                         LoclLastChangedAt,
                         LastChangedBy;
    
      create;
      update;
      delete;
    
      draft action Edit;
      draft action Activate;
      draft action Discard;
      draft action Resume;
      draft determine action Prepare;
    
    }​
  6. Alter/recreate the draft table with this adjustment.
    @EndUserText.label : 'Draft table for entity ZRKT_I_EKKO'
    @AbapCatalog.enhancement.category : #EXTENSIBLE_ANY
    @AbapCatalog.tableCategory : #TRANSPARENT
    @AbapCatalog.deliveryClass : #A
    @AbapCatalog.dataMaintenance : #RESTRICTED
    define table zrkt_dt_ekko {
      key mandt         : mandt not null;
      key tempguid      : sysuuid_x16 not null;
      objectid          : zrk_pur_con_id;
      description       : zrk_description;
      buyer             : zrk_buyer_id;
      supplier          : zrk_sup_no;
      supconid          : zrk_sup_con_id;
      sendvia           : zrk_send_via_code;
      compcode          : zrk_company_code;
      statcode          : zrk_stat_code;
      fisclyear         : zrk_fiscal_year;
      validfrom         : zrk_valid_from;
      validto           : zrk_valid_to;
      createdby         : abp_creation_user;
      createdat         : abp_creation_tstmpl;
      lastchangedby     : abp_locinst_lastchange_user;
      lastchangedat     : abp_lastchange_tstmpl;
      locllastchangedat : abp_locinst_lastchange_tstmpl;
      "%admin"          : include sych_bdl_draft_admin_inc;
    
    }​
  7. Implement the methods for Create/Update/Delete/Read being unmanaged scenarios.
  8. On SAVE during Create, New key can be generated with a number range object and the developer has full control on persisting the actual records into existing tables.
      METHOD create.
    
        DATA ls_pur_con TYPE zrkt_proc_doc.
    
        READ TABLE entities ASSIGNING FIELD-SYMBOL(<fs_entity>) INDEX 1.
        IF sy-subrc EQ 0.
    
          TRY.
              cl_numberrange_runtime=>number_get(
                EXPORTING
    *            ignore_buffer     =
                  nr_range_nr       = '01'
                  object            = 'ZRK_NR_PR'
                  quantity          = 1
    *            subobject         =
    *            toyear            =
                IMPORTING
                      number            = DATA(number_range_key)
                      returncode        = DATA(number_range_return_code)
                      returned_quantity = DATA(number_range_returned_quantity)
              ).
            CATCH cx_number_ranges.
              "handle exception
          ENDTRY.
          DATA(max_id) = number_range_key - number_range_returned_quantity.
    
          ls_pur_con-client = sy-mandt.
          ls_pur_con-zztemp_guid = <fs_entity>-TempGuid.
          ls_pur_con-object_id = |PC{ max_id }| .
          ls_pur_con-description = <fs_entity>-Description .
          ls_pur_con-buyer = <fs_entity>-Buyer .
          ls_pur_con-supplier = <fs_entity>-Supplier .
          ls_pur_con-sup_con_id = <fs_entity>-SupConId .
          ls_pur_con-comp_code = <fs_entity>-CompCode .
          ls_pur_con-stat_code = <fs_entity>-StatCode .
          ls_pur_con-valid_from = <fs_entity>-Validfrom .
          ls_pur_con-valid_to = <fs_entity>-ValidTo .
          ls_pur_con-fiscl_year = <fs_entity>-FisclYear .
          ls_pur_con-created_at = <fs_entity>-CreatedAt .
          ls_pur_con-created_by = <fs_entity>-CreatedBy .
    
          INSERT zrkt_proc_doc FROM @ls_pur_con.
    
        ENDIF.
    
      ENDMETHOD.​
  9. Implement Behavior projection, Service definition and publish Service Binding
  10. Fiori application is ready for preview and test

    Fiori app preview:.
    Fiori%20App%20Preview
    Table entries in draft table:

    Table entries in Active table:

​Advantages:

  1. Almost out of the box implementation and no custom development behind the ” Keep Draft” to see if it is a fresh draft ( no active ) or delta draft ( active entry exists ).
  2. No need to consume number range without user explicit SAVE . Otherwise, if we need to store draft records with original key field, we have to consume it.

Restrictions:

  1. Base table must be extensible to append an include with temp_guid. Ex: EKKO also extensible

Note:

​If you have already existing transactional data, then you have to plan for a cutover activity to fill this temp_guid ( simply generate ) in the base table to reflect into the Fiori application. ​

Conclusion:

​Application is ready for usage and we can see a fiori application with unmanaged draft scenario​ with the fact  ” Draft table does not need to have the same keys as base table but need to have same as base/interface view”.

Please share your views and comment if you have any other alternative proposals to achieve this.

Revision on 17.03.2022 : Added section “Technical representation of the business requirement”.

Revision on 05.04.2022 : Added screen shots for final results.

Assigned Tags

      25 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Sandra Rossi
      Sandra Rossi

      Thanks for posting. One question: you say "this question has been unanswered for a year", which question, or why is it a question? In fact, I don't see why it's an issue to have a GUID column as primary key and the legacy column as a "candidate key". Could you provide a real scenario?

      Author's profile photo Ramjee Korada
      Ramjee Korada
      Blog Post Author

      Hi Sandra,

      Below are 3 questions on this issue. Real use case is explained in problem statement and below questions.

      https://answers.sap.com/questions/13597723/unmanaged-draft-duplicate-draft-key-issue.html

      https://answers.sap.com/questions/13203562/abap-restful-programming-model-unmanaged-draft-gen.html

      https://answers.sap.com/questions/13145223/draft-table-key-field-design-in-abap-restful-appli.html

       

      Please let me know for any concerns.

       

      Best wishes,

      Ramjee Korada

      Author's profile photo Sandra Rossi
      Sandra Rossi

      Thanks a lot for the links. Well I have very little experience on the topic (MOOC training about RAP and behaviors), so I guess it must be trivial but I don't understand yet why it's so important to have the key based on legacy columns, and not on GUID column. I'll follow the post and other threads to see if there are some other comments.

      Author's profile photo Ramjee Korada
      Ramjee Korada
      Blog Post Author

      Sandra Rossi

      If it is not based on GUID , RAP Framework cannot generate new key automatically. That's why, it is important to have key with GUID in RAP Draft scenario.

      Author's profile photo Andre Fischer
      Andre Fischer

      The topic is complicated.
      And yes, the RAP framework does not support all options depending on the underlying release.
      I will provide some general hints in my answer below and will try to get in touch with you to clarify some details in a call with you.

       

      Author's profile photo Ramjee Korada
      Ramjee Korada
      Blog Post Author

      Andre Fischer

      Thanks for feedback! This experiment is carried out in BTP Trail account and I believe it is latest release.

      Sure. We can connect over call to discuss in detail and I am reachable at koradaramjee@gmail.com.

      Author's profile photo Andre Fischer
      Andre Fischer

      Hi Ramjee,

      I will come back to you and I see that we need some clarifications with regards to the different implementation scenarios (managed and unmanaged) and how the keys are drawn (managed and unmanaged).

      RAP supports different key layout: Numbering

      The scenario you're describing seems to be similar to the Unmanaged Internal Early Numbering which is available on SAP BTP ABAP Environment and on-premise as of SAP S/4HANA 2021.

      In scenarios with internal numbering, the runtime framework assigns the primary key value when creating the new instance. Internal numbering can be managed by the RAP runtime or unmanaged, that is implemented by the application developer.

      The scenario where the uuid-based primary key is automatically assigned by the RAP framework is the Managed Internal Early Numbering.

      As of SAP S/4HANA 2021 SP1 or in SAP BTP ABAP Environment there is also late numbering available for managed (draft) scenarios available.

      Unmanaged Internal Late Numbering - SAP Help Portal

      This can however not yet be found in the documenation since documentation will only be available as of SAP S/4HANA 2021 FSP2 as described in SAP Note 3145465 - Late Numbering Enabled for RAP Business Objects of Implementation Type Managed, Managed with Draft and Unmanaged with Draft - SAP ONE Support Launchpad

      Author's profile photo Ramjee Korada
      Ramjee Korada
      Blog Post Author

      Hi Andre,

      Thank you for your time to discuss the topic over call.

      Business use case that we are describing here :

      1. Unmanaged Implementation
      2. Draft
      3. Late numbering ( number can only be generated by BAPI during saving )
      4. Non-Guid key based legacy tables ( Ex: EKKO , VBAK )

      The documentation supports some of above features but not all 4 at once.

      1. Unmanaged Implementation + Draft + Late numbering + GUID based keys => Works
      2. Unmanaged Implementation + Draft + Late numbering + Non-GUID based keys => Works without draft

      Kindly share more details on the topic once available.

      Best wishes,

      Ramjee Korada

       

      Author's profile photo Dmitrii Ulianov
      Dmitrii Ulianov

      Hello Ramjee,

       

      your solution works well as a workaround, however SAP suggests to use a DRAFTUUID key for each draft table declared in an unmanaged late numbering behavior. If the behavior refers to an association CDS, it additionally requires a PARENTDRAFTUUID field (NOT a key). But the system doesn't require to extend your original tables with anything. With my development it works.

       

      With best regards,

      Dmitry

      Author's profile photo Ramjee Korada
      Ramjee Korada

      Hi Dmitry,

      If the table is not extended, how did you create base/root view with GUID as a key? ( step #4 in my example )

      Are you fetching the data from DRFAT TABLE instead of ACTUAL TABLE ?

       

      Best wishes,

      Ramjee Korada

      Author's profile photo Dhananjay Hegde
      Dhananjay Hegde

      Hi,

      I see that for "Unmanaged with semantic keys + Late Numbering + Draft", draft tables automatically get an additional key field "draftuuid".  This gets generated by RAP framework for CREATE operation.

      Also, in this case, for EDIT operation "draftuuid" is not generated again. Instead, semantic keys are used in draft tables.

      Although, I cannot say in which release it is/will be available.

       

      Thank you,

      Dhananjay

       

      Author's profile photo Ramjee Korada
      Ramjee Korada

      Hi Dhananjay,

      Framework must generate "draftuuid" even for EDIT scenario as long as draftuuid is key field and marked for numbering in Behavior definition.

      Best wishes,

      Ramjee

      Author's profile photo Dhananjay Hegde
      Dhananjay Hegde

      Hi Ramjee

       

      Yes, that works as your explained.

      But, I am talking about Unmanaged with Semantic Keys + Draft + Late Numbering, "draftuuid" is not the key field, then EDIT does not generate new draftuuid.  Unlike in the BOPF framework, RAP does not do this.

      I tried this in BTP today:

      Unmanaged with Semantic Keys + Draft + Late Numbering

      • Framework automatically adds "draftuuid" only to draft tables as a key field.  DB table for active persistence does not have a "uuid" field as key field

      Draft Table - Generated by Framework

      define table zdh_trvl_umd_01 {
        key mandt     : mandt not null;
        key travelid  : /dmo/travel_id not null;
        key draftuuid : sdraft_uuid not null;
        .
        .
        createdby     : syuname;
        createdat     : timestampl;
        lastchangedby : syuname;
        lastchangedat : timestampl;
        "%admin"      : include sych_bdl_draft_admin_inc;
      
      }

      Active Persistency

      define table zdh_travel_um_01 {
        key client      : abap.clnt not null;
        key travel_id   : /dmo/travel_id not null;
        agency_id       : /dmo/agency_id;
        .
        .
        created_by      : syuname;
        created_at      : timestampl;
        last_changed_by : syuname;
        last_changed_at : timestampl;
      
      }

       

      BDEF

      unmanaged implementation in class zbp_dh_i_travel_um_tp_01 unique;
      strict;
      with draft;
      
      define behavior for ZDH_I_TRAVEL_UM_TP_01 alias Travel
      draft table zdh_trvl_umd_01
      late numbering
      lock master
      total etag LastChangedAt
      authorization master ( instance )
      {
        create;
        update;
        delete;
      
        draft action Edit;
        draft action Resume;
        draft action Discard;
        draft action Activate;
      
        draft determine action Prepare { }
      
        mapping for zdh_travel_um_01 corresponding
        {
          .
          .
        }
      }
      • CREATE operation generates "draftuuid" for the draft generated.  So, multiple drafts can exist together without duplicate key issue
      • EDIT does not generate "draftuuid" again.  Semantic key is used for EDIT draft scenario

       

      Hope this helps someone.

      Thank you,

      Dhananjay

      Author's profile photo Ramjee Korada
      Ramjee Korada

      I agree .

      Author's profile photo Ramjee Korada
      Ramjee Korada

      Hi Dhananjay,

      what is the key field in Interface and Consumption view ? is Draftuud is part of key ?

      Best wishes,

      Ramjee Korada.

      Author's profile photo Dhananjay Hegde
      Dhananjay Hegde

      Hi ramjee korada

      I saw your comment only today.

      DRAFTUUID is not part of the key in any of the CDS entities.  Neither in core layer, nor in consumption.  However, generated draft table gets a DRAFUUID field as key field along with the semantic keys.

      If the BO has only semantic keys with late numbering, then DRAFUUID is generated only for CREATE scenario, and not for EDIT scenario.  This is unlike in BOPF.

      Regards,

      Dhananjay

      Author's profile photo Andre Fischer
      Andre Fischer

      I just published the following blog post which describes a solution for the problem

      How to develop an unmanaged draft enabled RAP business object with semantic keys | SAP Blogs

      Author's profile photo Ramjee Korada
      Ramjee Korada

      Hi Andre,

      I have gone through the blog and it talks about Early numbering.

      But the problem is being discussed for late numbering where object_id ( Sales order ID , Purchase Order ID ) will be generated by  BAPI.

      The disadvantage with early numbering is that the number range ( even if we take out from BAPI ) , is consumed for draft documents.

      As you mentioned already, There will be a new blog on BAPI use cases. I will be looking forward to checking it. ​

      Best wishes,

      Ramjee Korada

       

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli

      Hi Ramjee, You don't need to consume the numbering right, just generate a dummy id with '$000000001' (based on total number of drafts? or some poor mans approach like andre mentioned) and show it in the UI, which indirectly tells that it is a temporary id and once we save, we can override '$000000001' with actual generated ID from bapi,

      In theory it works, not tried practically..

      Author's profile photo Ramjee Korada
      Ramjee Korada

      Hi Mahesh,

      this option might confuse user . In other draft applications, we don’t show any value for it.

      also we can not hide this column in list report page.

       

      best wishes,

      Ramjee Korada

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli

      true, that is something to consider while designing the solution.

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli

      Nice approach Ramjee, or i would say 'hack' 😉 , thanks for sharing.., <= s/4hana 2020 doesn't have early numbering for unmanaged, this approach can be helpful..

      Author's profile photo S. Appukuttan
      S. Appukuttan

      Thank you Ramjee for providing insight to the draft GUID for unmanaged. Nice one ! So with your approach, I see no difference between managed draft table (GUID) and unmanaged draft (also GUID). Hope you agree.

      Also, if the late numbering can be achieved via adjust_numbers in the save sequence steps as per SAP documentation below, in your expert opinion where do you think the actual BAPI call could be triggered in the "Save sequence" steps ?

      Unmanaged%20Scenario%20Late%20numbering

      Unmanaged Scenario Late numbering

       

      Save%20sequence

      Save sequence

      Author's profile photo Ramjee Korada
      Ramjee Korada

      Hi Appukuttan,

      ​Thanks for the feedback!

      Irrespective of my proposal, Managed and Unmanaged draft remains the same as draft is handled by  the framework.

      My proposal here is to solve the scenarios 'non-guid' key and draft.

      I have gone through the reference you shared.

      In the top, it says that RAP supports Late numbering for both "with draft" and "without draft".

      But if you scroll down on the same page, it is mentioned to extend your table with draftUUID.

      The example also shows that the base table needs to be extended with 'draftuuid key'.

      Then only you can use ADJUST_NUMBERS.
      That concludes that you can't skip extending "draftuuid key".
      Also mentioned 'The ADJUST_NUMBERS implementation is called during the save sequence after the point-of-no-return. At this point in time, the transactional buffer is in a consistent state and all instances can receive their final number."

      I see a risk you call BAPI here. What if the BAPI throws an error ?

      Best wishes,

      Ramjee Korada

      Author's profile photo Pratik Gupta
      Pratik Gupta

      Ramjee Korada ramjee korada

      Thanks for the good blog Ramjee.

      I am implementing the unmanaged scenario with Draft functionality.

      Create is working properly but while I am clicking on update then 1 screen is getting opened with all the fields as non Editable then on that screen while I am going to click on the button "Edit" it is saying "No Data Found"

      What could be the possible reason?

      Do I need to implement instance feature method for update scenario or is it something else?