Technical Articles
RAP Unmanaged BO – Late Numbering
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.
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 buffersprocess
that runs checks on data that is set in buffer and gives back all messagespost
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;
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.
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 fillmapped-header
table - Also buffer the combination of
%PID
and%CID
in a buffer class – in this casezcl_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 callopen
again when trying to set item data into outline agreement buffer. This is handled inlcl_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 numbering
scenarios, 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 = <ls_item_buff>-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...
mapped after interaction phase
mapped after save phase
Conclusion
To wrap it up, in late numbering
scenario,
- you generate
%PID
s increate
andCBA
methods ofinteraction phase
and map them to respective%CID
s - map generated semantic key to
%PID
inadjust_numbers
step ofsave
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.
not getting proper examples for RAP and we have to write tricky code to make it work...
Hello Jayaramu,
May I know what examples are you looking for?
Thank you,
Dhananjay
Hi Dhananjay,
Thank you for the blog. It gave me understanding on later numbering. I was able to implement the same in my RAP object.
I have Header / Item. Late numbering worked perfectly with Header(root) but not for item its not reflecting, when I debugged, I see mapping is happening in CL_RAP_CHANGESET but in later stage of SADL is only mapping the Header(root node) keys but not the expanded keys. Any advice ?
I believe there is a small bug in your code: While setting the cs_mapped-item, you are setting iv_header_pid, instead of item_pid.
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.
Hi Dhananjay.
I have same question with Ashwin Kumar Chitram, the "Late Numbering" not worked for item entity, I tried all combinations about %pid, %key, %pre %tmp...., but unfortunately none of them worked.
were you ablet to get it working?
Hello Dhananjay .
I have a BAPI which takes the Sales Order Header and Item Details together .
Now since the method for Header and Item is different in RAP .
Can we use this "Late Numbering" Approach for collecting all the data together and pass it to the BAPI
Hi Ashwin Kumar Chitram
Thank you for highlighting it. Yes, it should be item's PID. I will correct it in the post and in repo as well.
Hi Bodhisattwa Pal
I think you have already found the answer to your query. But, yes, you can use late numbering, buffer the payload and then pass the data to BAPI in save phase.
Cheers,
Dhananjay
Hi Dhananjay,
I am using managed implementation with unmanaged save and would like to send the response(Success/ Failure) back with the parameter 'mapped' but the method 'save_modified' has only 'reported' as changing parameter and not mapped.
In this case, how do we send the response back?
Thanks,
Ahamed
Hi Ahmed,
If I remeber correctly,
mapped
would available foradjust_numbers
method which would be available if your BO haslate numbering
. If that is not the case, then you do not need to map anything during save.MODIFY ENTITIES...
in the interaction phase.early numbering
then you would have already generated numbers innumbering exits
and mapped them which will also be available as a response toMODIFY ENTITIES...
This might help in this case - https://blogs.sap.com/2021/11/23/how-to-use-early-numbering-with-semantic-keys-rap-managed-bo/Please check once.
Regard
Dhananjay