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!
Showing results for 
Search instead for 
Did you mean: 
During last one year, I have had the most rewarding experience of developing RESTful ABAP Programming (RAP) objects for various Business Objects - especially, Unmanaged RAP Business Objects.  In this blog post, I would like to talk about `late numbering` and how it can be implemented.

Since this is my first tech blog post, please bear with me.  I would also highly appreciate your feedback.

Also, please note, if you are not familiar with RESTful ABAP Programming, please refer to this blog post for more details.


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

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.

Read the developer documentation here for further details.


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;

define behavior for ZDH_R_PurCtr alias Header
late numbering
lock master
etag master LastChangeDateTime
authorization master ( global, instance )
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;


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;


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.


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.

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(
iv_pid = lv_pid
iv_mode = cl_mmpur_constants=>hin
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 ).


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 ) ).


" Create failed. Fill FAILED and REPORTED




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.
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 ).
"-- Invalid parent key

DATA(lo_handler) = lcl_factory=>get_bo_handler_instance(
iv_ctr_number = lv_ctr
iv_pid = ls_header_key-pid
iv_mode = cl_mmpur_constants=>hin
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 )

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 ).


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 ) ).


" Create failed. Fill FAILED and REPORTED


CLEAR: lt_item, lv_ctr, lv_item_pid, ls_header_key.


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).

im_no_commit = abap_true
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.

iv_header_pid = ls_bo_handler-root_pid
iv_ctr = lv_ctr_created
cs_mapped = mapped


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 = <ls_item_buff>-pid
PurchaseContract = iv_ctr
PurchaseContractItem = <ls_item_buff>-key-PurchaseContractItem ) ).



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:
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 ) )
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' ) ) ) )
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
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.


Looking into debugger...

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

mapped after interaction phase



mapped after save phase



To wrap it up, in late numbering scenario,

  • you generate %PIDs in create and CBA methods of interaction phase and map them to respective %CIDs

  • map generated semantic key to %PID in adjust_numbersstep of save phase

  • use CONVERT KEY OF... to obtain this semantic key when consuming this BO


I hope this was helpful.  You can find the complete listing of sample coding used for this blog post in this GitHub Repository.

Labels in this area