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

There have been several questions in the community how unmanaged scenarios with draft are handled in the ABAP RESTful Application Programming Model (RAP).

I plan to publish several examples based on the on well known on premise Enterprise Procurement Data Model that comes with Products, Business Partners, Sales Orders and Sales Order Items where I would like to address the special requirements when you have to handle BAPI calls in on premise systems.

However to answer the most pressing question that was how semantic key fields are populated in a draft enabled scenario I started to implement a first small sample based on the travel sample.

Problem: Semantic key fields

The problem with semantic key fields in draft scenarios is that when creating several draft entities they all will be created with an empty key. Already when trying to create a second item a short dump will be raised which indicates that it was tried to insert an entry into the draft table with a duplicated key.

A duplicate primary key '00000000' was detected when inserting data into persistence 'ZTRAVELSEM00D_01'.

Solution : Early Numbering

The documentation about early numbering can be found here:

Internal Early Numbering – SAP Help Portal

To demonstrate the use of internal early numbering in an unmanaged scenario I have created the following Z-table based on the flight reference scenario:

@EndUserText.label : 'Flight Reference Scenario: Managing Travels'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table ztravel_sem_01 { key client : abap.clnt not null; key travel_id : /dmo/travel_id not null; description_2 : abap.char(50); gr_data : include /dmo/travel_data; gr_admin : include /dmo/travel_admin; }

In the behavior definition we have to specify that early numbering is used.

unmanaged;
with draft; define behavior for ZI_TRAVELSEMANTIC_01 alias TravelSemantic
implementation in class ZBP_I_TRAVELSEMANTIC_01 unique
draft table ztravelsem00d_01
etag master Lastchangedat
lock master total etag Lastchangedat
early numbering
authorization master ( global ) { field ( readonly ) TravelID; create; update; delete; draft action Edit; draft action Activate; draft action Discard; draft action Resume; draft determine action Prepare; mapping for ZTRAVEL_SEM_01 control ZSTravelSemantic_X_01 { TravelID = TRAVEL_ID; Description2 = DESCRIPTION_2; AgencyID = AGENCY_ID; CustomerID = CUSTOMER_ID; BeginDate = BEGIN_DATE; EndDate = END_DATE; BookingFee = BOOKING_FEE; TotalPrice = TOTAL_PRICE; CurrencyCode = CURRENCY_CODE; Description = DESCRIPTION; Status = STATUS; Createdby = CREATEDBY; Createdat = CREATEDAT; Lastchangedby = LASTCHANGEDBY; Lastchangedat = LASTCHANGEDAT; }
}

In the behavior implementation class to specify a method of type FOR NUMBERING.

METHODS earlyNumbering_Create FOR NUMBERING
IMPORTING entities FOR CREATE TravelSemantic.

In the implementation of this method we have first to check for which entities which have been handed over to us the key field is not yet set.

Then we calculate the travelid that should be used. Here we use a poor mans approach by simply selecting the highest value for a travelid from the normal as well as from the draft table.

Since we are in the create phase the %tky is not yet filled. So we have to populate the mapped return table with values for %cid, %key and %is_draft that were handed over to us by the RAP framework.

Usually you will use a number range here or if the semantic key values are provided by the BAPI you would use those.

Result

After having added the early numbering statement in the BDEF and after having implemented the method earlyNumbering_Create the app works correctly and provides TravelID’s for each newly created draft elements.

Pitfalls

When you simply start with entering the method for early numbering in your behavior implementation class you get the error message

The operation ‘CREATE’ is not activated for entity “ZI_TRAVELSEMANTIC_01”.

though the create method is part of the behavior definition.

What was actually missing in the BDEF was the early numbering statement .

Code

CLASS lhc_travelsemantic DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS: get_global_authorizations FOR GLOBAL AUTHORIZATION IMPORTING REQUEST requested_authorizations FOR TravelSemantic RESULT result, create FOR MODIFY IMPORTING entities FOR CREATE TravelSemantic, update FOR MODIFY IMPORTING entities FOR UPDATE TravelSemantic, delete FOR MODIFY IMPORTING keys FOR DELETE TravelSemantic, lock FOR LOCK IMPORTING keys FOR LOCK TravelSemantic, read FOR READ IMPORTING keys FOR READ TravelSemantic RESULT result. METHODS earlyNumbering_Create FOR NUMBERING IMPORTING entities FOR CREATE TravelSemantic. ENDCLASS. CLASS lhc_travelsemantic IMPLEMENTATION. METHOD get_global_authorizations. ENDMETHOD. METHOD create. ENDMETHOD. METHOD update. ENDMETHOD. METHOD delete. ENDMETHOD. METHOD lock. ENDMETHOD. METHOD read. ENDMETHOD. METHOD earlynumbering_create. DATA:
* entity TYPE STRUCTURE FOR CREATE /DMO/I_Travel_M, travel_id_max TYPE /dmo/travel_id. " Ensure Travel ID is not set yet (idempotent)- must be checked when BO is draft-enabled LOOP AT entities INTO DATA(entity) WHERE travelid IS NOT INITIAL. APPEND CORRESPONDING #( entity ) TO mapped-travelsemantic. ENDLOOP. DATA(entities_wo_travelid) = entities. DELETE entities_wo_travelid WHERE travelid IS NOT INITIAL. "Get max travelID SELECT SINGLE FROM ztravel_sem_01 FIELDS MAX( travel_id ) INTO @DATA(max_travelid). " select from draft table SELECT SINGLE FROM ztravelsem00d_01 FIELDS MAX( travelid ) INTO @DATA(max_travelid_draft). IF max_travelid < max_travelid_draft. max_travelid = max_travelid_draft. ELSEIF max_travelid = 0. max_travelid += 1. ENDIF. " Set Travel ID LOOP AT entities_wo_travelid INTO entity. max_travelid += 1. entity-travelid = max_travelid . APPEND VALUE #( %cid = entity-%cid "%tky is not available since we are in create %key = entity-%key "needed because we are using draft %is_draft = entity-%is_draft ) TO mapped-travelsemantic. ENDLOOP. ENDMETHOD. ENDCLASS.
CLASS lcl_zi_travelsemantic_01 DEFINITION INHERITING FROM cl_abap_behavior_saver. PROTECTED SECTION. METHODS: finalize REDEFINITION, check_before_save REDEFINITION, save REDEFINITION, cleanup REDEFINITION, cleanup_finalize REDEFINITION.
ENDCLASS. CLASS lcl_zi_travelsemantic_01 IMPLEMENTATION. METHOD finalize. ENDMETHOD. METHOD check_before_save. ENDMETHOD. METHOD save. ENDMETHOD. METHOD cleanup. ENDMETHOD. METHOD cleanup_finalize. ENDMETHOD.
ENDCLASS.