Wednesday, 15 September 2021

RAP Unmanaged BO – Late Numbering

In this blog post, I would like to talk about `late numbering` and how it can be implemented.

Objective

I will use an example to demonstrate:

◉ How to define late numbering

◉ How to handle created instances during Interaction Phase [Create and Create By Association (CBA) methods for now]

◉ At what point in Save sequence actual number is generated

◉ How this number is mapped back to temporary numbers used during Interaction Phase

◉ An Entity Manipulation Language (EML) statement to test this

This is Unmanaged Business Object (BO). That means, there is already a code base that handles Create, Read, Update and Delete (CRUD) operations for our BO.  I am using Purchase Contract as an example.  cl_ctr_handler_mm provides various methods such as

◉ set_outl_agreement_header, set_outl_agreement_item etc. to set Outline Agreement buffers

◉ process that runs checks on data that is set in buffer and gives back all messages

◉ post that will finally save the Purchase Contract and gives the number back

To keep the the blog post short, I have already created Transactional Processing CDS view entities that model the header, item and account entities based on tables EKKO, EKPO and EKKN.  This is not so important for the purpose of this post.

With this, BO model looks like this

ABAP RESTful Application Programming Model, ABAP Development, SAP ABAP Career, SAP ABAP Tutorial and Material, SAP ABAP Learning, SAP ABAP Study Material, SAP ABAP Preparation

Sample Purchase Contract Transactional Processing Model

What is Late Numbering


Late numbering is a scenario where document number is generated, usually from number range objects, only during the save phase of RAP BO – adjust_numbers.  At this stage, it is almost certain that the document can be saved to Database (DB) since all checks are expected to have been completed – check_before_save.

During interaction phase, for every entity instance that is successfully created, a temporary ID%PID (also called “Preliminary ID”)  should be generated and mapped to corresponding %CID in CREATE or Create By Association (CBA) methods.

Developer must ensure that semantic keys are mapped to %PIDs later in the save phase.  It is usually done in adjust_numbers step.

How to define Late Numbering in Behavior Definition (BDEF)


It is defined using keywords late numbering for each entity of the RAP BO that should support late numbering:

unmanaged implementation in class zcl_bp_dh_r_purctr unique;
strict;

define behavior for ZDH_R_PurCtr alias Header
late numbering
lock master
etag master LastChangeDateTime
authorization master ( global, instance )
{
  create;
  update;
  association _Item { create; }
}

define behavior for ZDH_R_PurCtrItem alias Item
late numbering
lock dependent by _Header
etag master LastChangeDateTime
authorization dependent by _Header
{

  field ( readonly ) PurchaseContract;

  update;

  association _Account { create; }
  association _Header;
}

define behavior for ZDH_R_PurCtrAccount alias Account
late numbering
lock dependent by _Header
etag master LastChangeDateTime
authorization dependent by _Header
{

  field ( readonly ) PurchaseContract, PurchaseContractItem;

  update;

  association _Header;
  association _Item;
}

Create and CBA methods


Once this is defined in BDEF, MAPPED parameter of CREATE and CREATE BY ASSOCIATION methods get an additional generated component named %PID.  Such a temporary number can be generated within RAP implementation ( e.g. a GUID ) and mapped to this field.

ABAP RESTful Application Programming Model, ABAP Development, SAP ABAP Career, SAP ABAP Tutorial and Material, SAP ABAP Learning, SAP ABAP Study Material, SAP ABAP Preparation

Generated component %PID in MAPPED parameter

Once you activate the BDEF and generate the Behavior Implementation (BIL) class, you will see that the class zcl_bp_dh_R_PurCtr has 4 local classes

◉ lhc_Header
◉ lhc_Item
◉ lhc_Account
◉ lsc_ZDH_R_PurCtr

In the create method of class lhc_Header, we

◉ get an instance of class cl_ctr_handler_mm class to interact with outline agreement buffer
◉ set the outline agreement header data from payload
◉ If there are no errors, generate a %PID and map it to %CID and fill mapped-header table
◉ Also buffer the combination of %PID and %CID in a buffer class – in this case zcl_dh_purctr_buffer

This tells RAP that setting header data was successful.

Sample code from create method of class lhc_Header

METHOD create.

    DATA:
      lt_messages TYPE mepo_t_messages_bapi,
      ls_header   TYPE outline_agrmnt_header_data.

    LOOP AT entities ASSIGNING FIELD-SYMBOL(<ls_header_payload>).

      DATA(lv_pid) = lcl_util=>generate_pid( ).

      DATA(lo_handler) = lcl_factory=>get_bo_handler_instance(
                           EXPORTING
                             iv_pid          = lv_pid
                             iv_mode         = cl_mmpur_constants=>hin
                           IMPORTING
                             et_messages     = lt_messages
                         ).

      IF lo_handler IS BOUND.

        ls_header-data  = CORRESPONDING #( <ls_header_payload>-%data MAPPING FROM ENTITY ).
        ls_header-datax = CORRESPONDING #( <ls_header_payload> MAPPING FROM ENTITY USING CONTROL ).

        lo_handler->set_outl_agreement_header(  is_outl_agrmnt_header    = ls_header
                                                is_outl_agrmnt_set_flags = VALUE #( header = abap_true ) ).

        lo_handler->outl_agrmnt_process( IMPORTING ex_messages =  lt_messages ).

      ENDIF.


      IF NOT line_exists( lt_messages[ msgty = 'E' ] ).

        INSERT VALUE #( %cid = <ls_header_payload>-%cid
                        %pid = lv_pid ) INTO TABLE mapped-header.

        zcl_dh_purctr_buffer=>get_instance( )->add_header( VALUE #( cid   = <ls_header_payload>-%cid
                                                                    pid   = lv_pid ) ).

      ELSE.

        "
        " Create failed.  Fill FAILED and REPORTED
        "

      ENDIF.
    ENDLOOP.

  ENDMETHOD.

NOTE: I have added the complete listing of all utility classes such as lcl_util, lcl_fctory, zcl_dh_purctr_buffer as attachments to this blog.

Perform similar set of operations in CBA_Item method of class lhc_Header .  Important things to note here

◉ get the same instance of cl_ctr_handler_mm class.  In this case, this is a singleton class.  However, it is important to not try to call open again when trying to set item data into outline agreement buffer. This is handled in lcl_factory class

◉ Generate a new %PID for each successfully created item instance and map it to their corresponding %CID.

◉ Also buffer this mapping for future use during save sequence

Coding from method CBA_Item looks like this

METHOD cba_Item.
    DATA:
      lv_ctr      TYPE ebeln,
      lt_messages TYPE mepo_t_messages_bapi,
      lt_item     TYPE outline_agrmnt_t_item.


    LOOP AT entities_cba ASSIGNING FIELD-SYMBOL(<ls_item_cba>).
      LOOP AT <ls_item_cba>-%target ASSIGNING FIELD-SYMBOL(<ls_item_payload>).

        IF <ls_item_cba>-PurchaseContract IS NOT INITIAL.
          lv_ctr = <ls_item_cba>-PurchaseContract.
        ELSEIF <ls_item_cba>-%cid_ref IS NOT INITIAL.
          DATA(ls_header_key) = zcl_dh_purctr_buffer=>get_instance( )->get_header_by_cid( iv_cid = <ls_item_cba>-%cid_ref ).
        ELSE.
          "-- Invalid parent key
        ENDIF.


        DATA(lo_handler) = lcl_factory=>get_bo_handler_instance(
                             EXPORTING
                               iv_ctr_number   = lv_ctr
                               iv_pid          = ls_header_key-pid
                               iv_mode         = cl_mmpur_constants=>hin
                             IMPORTING
                               et_messages     = lt_messages
                           ).

        IF lo_handler IS BOUND.

          lt_item  = VALUE #( ( id    = lo_handler->get_id( )
                                data  = CORRESPONDING #( <ls_item_payload>-%data MAPPING FROM ENTITY )
                                datax = CORRESPONDING #( <ls_item_payload> MAPPING FROM ENTITY USING CONTROL ) ) ).


          lo_handler->set_outl_agrrement_items( it_items          = lt_item
                                                is_item_set_flags = VALUE #( item = abap_true ) ).

          lo_handler->outl_agrmnt_process( IMPORTING ex_messages =  lt_messages ).

        ENDIF.


        IF NOT line_exists( lt_messages[ msgty = 'E' ] ).

          DATA(lv_item_pid) = lcl_util=>generate_pid( ).

          INSERT VALUE #( %cid = <ls_item_payload>-%cid
                          %pid = lv_item_pid  ) INTO TABLE mapped-item.

          zcl_dh_purctr_buffer=>get_instance( )->add_item( VALUE #( cid     = <ls_item_payload>-%cid
                                                                    cid_ref = <ls_item_cba>-%cid_ref
                                                                    pid     = lv_item_pid ) ).

        ELSE.

          "
          " Create failed.  Fill FAILED and REPORTED
          "

        ENDIF.

        CLEAR: lt_item, lv_ctr, lv_item_pid, ls_header_key.
      ENDLOOP.
    ENDLOOP.
  ENDMETHOD.
 

Save Phase


In case of late numbering main task in save phase is to map the document number that is generated to %PIDs.  This needs to be done in adjust_numbers step.  Also, in late numberingscenarios, save step can usually be empty.

Here, we call post method of cl_ctr_handler_mm which will send back a success message with Purchase contract number if all went well.  Then, we read all buffered mappings of %CID and %PID for all entities, and map the Purchase Contract number accordingly.  This is done in method _map_results of lsc_ZDH_R_PurCtr here:

METHOD adjust_numbers.

    mo_buffer = zcl_dh_purctr_buffer=>get_instance( ).

    mt_header_buffer = mo_buffer->get_all_header_data( ).
    mt_item_buffer   = mo_buffer->get_all_item_data( ).

    LOOP AT lcl_factory=>get_all_handlers( ) INTO DATA(ls_bo_handler).

      ls_bo_handler-handler->outl_agrmnt_post(
         EXPORTING
           im_no_commit = abap_true
         IMPORTING
           ex_messages  = DATA(lt_messages)
       ).

      ASSIGN lt_messages[ msgty = 'S' msgid = '06' msgno = '017' ] TO FIELD-SYMBOL(<ls_message>).

      IF sy-subrc = 0.
        DATA(lv_ctr_created) = <ls_message>-ebeln.

        _map_results(
          EXPORTING
            iv_header_pid = ls_bo_handler-root_pid
            iv_ctr        = lv_ctr_created
          CHANGING
            cs_mapped     = mapped
        ).
      ENDIF.
    ENDLOOP.
ENDMETHOD.

METHOD _map_results.

    ASSIGN mt_header_buffer[ pid = iv_header_pid ] TO FIELD-SYMBOL(<ls_header_buff>).

    IF <ls_header_buff> IS ASSIGNED.
      cs_mapped-header = VALUE #( BASE cs_mapped-header ( %pid              = iv_header_pid
                                                          PurchaseContract  = iv_ctr ) ).

      LOOP AT mt_item_buffer ASSIGNING FIELD-SYMBOL(<ls_item_buff>) USING KEY sorted_cid_ref WHERE cid_ref = <ls_header_buff>-cid.
        cs_mapped-item = VALUE #( BASE cs_mapped-item ( %pid                  = iv_header_pid
                                                        PurchaseContract      = iv_ctr
                                                        PurchaseContractItem  = <ls_item_buff>-key-PurchaseContractItem ) ).
      ENDLOOP.
    ENDIF.
ENDMETHOD.
 

EML Example


Start the interaction phase and set header and item data with below EML. I am using some test data here.  Adjust them according to your setup:

MODIFY ENTITIES OF ZDH_R_PurCtr
     ENTITY  Header
       CREATE SET FIELDS WITH VALUE #( ( %cid                        = 'header1'
                                         CompanyCode                 = '0001'
                                         PurchasingDocumentCategory  = 'K'
                                         PurchaseContractType        = 'MK'
                                         PurchasingOrganization      = '0001'
                                         PurchasingGroup             = '001'
                                         DocumentCurrency            = 'EUR'
                                         Supplier                    = 'STANDARD'
                                         ValidityStartDate           = sy-datum
                                         ValidityEndDate             = sy-datum + 30
                                         QuotationSubmissionDate     = sy-datum ) )
        CREATE BY \_Item
          SET FIELDS WITH VALUE #( ( %cid_ref = 'header1'
                                     %target  = VALUE #( ( %cid                           = 'item1'
                                                           CompanyCode                    = '0001'
                                                           PurchasingDocumentItemCategory = '0'
                                                           Material                       = 'AD-08'
                                                           ManufacturerMaterial           = 'AD-08'
                                                           PurchaseContractItemText       = 'Integration test PASS API'
                                                           MaterialGroup                  = '01'
                                                           Plant                          = '0001'
                                                           StorageLocation                = '0001'
                                                           ContractNetPriceAmount         = '1000'
                                                           TargetQuantity                 = '200'
                                                           NetPriceQuantity               = '1'
                                                           OrderPriceUnit                 = 'EA'
                                                           OrderQuantityUnit              = 'EA'
                                                           AccountAssignmentCategory      = ''
                                                           MultipleAcctAssgmtDistribution = '' " = Single, 1 = By Qty, 2 = By %, 3 = By Amount
                                                           OrdPriceUnitToOrderUnitDnmntr  = '1'
                                                           OrderPriceUnitToOrderUnitNmrtr = '1'
                                                           GoodsReceiptIsExpected         = 'X'
                                                           GoodsReceiptIsNonValuated      = ''
                                                           EvaldRcptSettlmtIsAllowed      = ''
                                                           InvoiceIsExpected              = 'X'
                                                           InvoiceIsGoodsReceiptBased     = 'X'
                                                           PurgDocPriceDate               ='99991231' ) ) ) )
        FAILED DATA(failed)
        REPORTED DATA(reported)
        MAPPED DATA(mapped).

Note, after this, if everything went well, then mapped-header and mapped-item will contain the respective %PID .  Now, it is important to convert this to Purchase Contract number.  For this, RAP provides a new syntax CONVERT KEY OF ...  So, the COMMIT ENTITIES...  looks like this now:

This is possible because of the mapping done in adjust_numbers method of lsc_zdh_r_PurCtr class

COMMIT ENTITIES
     BEGIN RESPONSE OF ZDH_R_PurCtr
       FAILED DATA(failed_late)
       REPORTED DATA(reported_late).

    LOOP AT mapped-header ASSIGNING FIELD-SYMBOL(<mapped>).
      CONVERT KEY OF ZDH_R_PurCtr FROM <mapped>-%pid TO DATA(ls_ctr).
      <mapped>-PurchaseContract = ls_ctr-PurchaseContract.
    ENDLOOP.
COMMIT ENTITIES END.
 

Looking into debugger…


we find mapped filled with %PID after interaction phase and with Purchase Contract number CONVERT KEY OF...

ABAP RESTful Application Programming Model, ABAP Development, SAP ABAP Career, SAP ABAP Tutorial and Material, SAP ABAP Learning, SAP ABAP Study Material, SAP ABAP Preparation

mapped after interaction phase

ABAP RESTful Application Programming Model, ABAP Development, SAP ABAP Career, SAP ABAP Tutorial and Material, SAP ABAP Learning, SAP ABAP Study Material, SAP ABAP Preparation

mapped after save phase

No comments:

Post a Comment