Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
Marcel_Wahl
Product and Topic Expert
Product and Topic Expert
17,773

🔥 Updates


2023-08-08
Thank you colleagues for the feedback. As per our discussion we could simplify this guide even more by using a managed RAP implementation with an "unmanaged save". The solution does now no  longer require a custom workspace buffer but is using the RAP default implementation.

Introduction


During the last months we often received the question, how to wrap SAP S/4HANA BAPIs for use in side-by-side scenarios, for example, from SAP Cloud Application Programming Model (CAP).

There are multiple ways to achieve that on a purely technical level basically enabling the module to be called over HTTPS. But the overwhelming interface only lead to meeting after meeting clarifying the purpose of fields of which 90% are not needed for the business use case.

To avoid that we came up with the approach to model facades in ABAP RESTful Programming Model (RAP) that reduce the surface of the API to the minimum and split by the need-to-know principle. The functional experts in the SAP S/4HANA business system could easily tell what input is expect from the end-user / consumer. Anything else was hidden in the RAP facade.

This is an end-to-end "how-to" guide with code snippets focusing on the main features needed to achieve this.

  1. Modelling + implementing RAP

  2. Calling the BAPI

  3. Error handling

  4. Testing in ABAP + POSTMAN

  5. Remarks on transaction handling


Credits


This blog is based on "Using BAPIs in RAP" and was written with support of marcel.hermanns and renzo.colle. Thanks a lot for for the insights.

Sample Use Case


This is a real use case of an incentive / bonus payment for managers used in the performance review cycles.

The managers only want to specify the amount and employee it is for.

The finance experts in the backend know that they have to create a finance posting with debit on corporate benefits money pool and credit to an employee payout account.

Solution Overview



Solution Overview



Entity Model


The bonus payment is modelled as simple as possible on top of the SAP standard accounting document.

The item structure of an accounting document is hidden by using the a unique main item projection.

 
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'RAP Facade: Bonus Payment main item'
@Metadata.ignorePropagatedAnnotations: true
define view entity ZDemo_BonusPaymentItem
as select from I_OperationalAcctgDocItem
association [1] to I_OperationalAcctgDocItem as _AcctDocItem on _AcctDocItem.CompanyCode = $projection.CompanyCode
and _AcctDocItem.FiscalYear = $projection.FiscalYear
and _AcctDocItem.AccountingDocument = $projection.AccountingDocument
and _AcctDocItem.AccountingDocumentItem = $projection.MainItem
{
key CompanyCode, //Access control
key FiscalYear,
key AccountingDocument,
min( AccountingDocumentItem ) as MainItem,

_AcctDocItem
}
// Assumption:
// every bonus payment has exactly 1 debit item with the total bonus amount
// there may be multiple credit items
where
DebitCreditCode = 'S' //Debit line
group by
CompanyCode,
FiscalYear,
AccountingDocument

 

The main entity for the WebAPI is a simplified projection on the accounting document mixed with the figures of the main item.

 
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'RAP Facade: Bonus Payment'
define root view entity ZDemo_BonusPayment
as select from I_AccountingDocument
//Main item
association [0..1] to ZDemo_BonusPaymentItem as _MainItem on _MainItem.CompanyCode = $projection.CompanyCode
and _MainItem.FiscalYear = $projection.FiscalYear
and _MainItem.AccountingDocument = $projection.AccountingDocument
{
key CompanyCode, // Access control
key FiscalYear,
key AccountingDocument,

//main attributes
DocumentDate,
PostingDate,

// main item figures - currency relationship inherited
_MainItem._AcctDocItem.CompanyCodeCurrency as Currency,
_MainItem._AcctDocItem.AmountInCompanyCodeCurrency as Amount,

//Administrative data
CreationTime as CreatedAt,
AccountingDocumentCreationDate as CreatedOn,
AccountingDocCreatedByUser as CreatedBy,

//Document reference used for employee field
DocumentReferenceID as BonusRecipient
}
where
AccountingDocumentType = 'WA'
and IsReversed != 'X'

 

RAP Facade


Creating a behavior definition


To enable creation of new bonus payouts we are defining a behavior definition for this entity with minimal information.

We will use a "managed" implementation with "unmanaged save" to get the transaction workspace handling for free.

As with all BAPIs, the final key will only be available after the real posting run.
Hence "late numbering" must be activated.

 
managed implementation in class zbp_demo_bonuspayment unique;
strict ( 2 );

define behavior for ZDemo_BonusPayment alias BonusPayment
with unmanaged save
late numbering // <<<< must have when working with the BAPI
lock master
authorization master ( instance ) // based on company code field
etag master CreatedAt
{
field ( readonly ) AccountingDocument, FiscalYear; //Key fields filled by API
field ( readonly : update ) CompanyCode; //Key fields provided from external
field ( readonly ) CreatedAt, CreatedBy, CreatedOn; //Admin fields

create;
delete; //Cancellation - Not implemented yet
}

 

Notes:

  1. We do not need draft capabilities because WebAPIs do synchronous calls and dont need it.

  2. We do not require a behavior definition for the item view since it serves only as a filter.

  3. Method "Delete" is included for demo purposes but not fully implemented


Overview of behavior runtime


A RAP facade for a BAPI requires only the minimal methods for writing in the saver class:

  1. Filling the BAPI call by merging requested data with hidden internal control fields
    e.g. document type

  2. Calling the BAPI and handling all errors

  3. Returning the final keys


Save sequence using a BAPI


The BAPI call is part of the save sequence going through the following methods:

  1. "Adjust_Numbers"

    • The BAPI must be called here, since later you can no longer handle errors or return the keys



  2. "Save_Modified"

    • This method is not required because the BAPI has already written the data. It can be left empty.




Important:



  1. To handle the BAPI errors, you must change the inheritance of your local saver class definition from "cl_abap_behavior_saver" to "cl_abap_behavior_saver_failed".
    This will enable the additional parameter "failed" in method "adjust_numbers".See RAP Saver Classes and Methods in ABAP Keyword Documentation for more details

  2. BAPIs often use "CALL FUNCTION ... IN UPDATE TASK" which is forbidden during RAP runtime except in "adjust numbers" and the save methods.


Internal control fields and defaults


BAPIs require a set of control parameters such as document types, company code context and so on.

The RAP Facade requires access to these values which can be implemented in various ways:

  1. BRF+ function returning a structure with all fields

  2. Configuration class reading from a configuration table

  3. Member structure with hard coded defaults --- FOR DEMO PURPOSES ONLY !


For the sake of a copy + paste demo, we defined our configuration in the saver class private section:

 
  PRIVATE SECTION.
DATA:
"! <p class="shorttext synchronized" lang="en">Configuration</p>
"! <p>hard coded just for demo purposes only </p>
"! <p>Use a configuration table or BRF+ rule to fill
"! the values in a real use case </p>
BEGIN OF ms_config,
company_code TYPE bapiache09-comp_code VALUE '1010',
document_type TYPE bapiache09-doc_type VALUE 'WA',
BEGIN OF debit,
transaction TYPE bapiacgl09-acct_key VALUE 'BSX',
glaccount TYPE bapiacgl09-gl_account VALUE '0054070000',
END OF debit,
BEGIN OF credit,
transaction TYPE bapiacgl09-acct_key VALUE 'GBB',
glaccount TYPE bapiacgl09-gl_account VALUE '0013600000',
END OF credit,
END OF ms_config ##no_text.

 

Filling BAPI Requests


To keep the code as easy as possible, it is a good a practice to have all BAPI parameters in a request structure type and build a request table based on it.

 
    TYPES:
"! <p class="shorttext synchronized" lang="en">Accounting document request</p>
BEGIN OF ty_s_bapi_request,
pid TYPE abp_behv_pid,
header TYPE bapiache09,
amounts TYPE STANDARD TABLE OF bapiaccr09 WITH DEFAULT KEY,
accounts TYPE STANDARD TABLE OF bapiacgl09 WITH DEFAULT KEY,
END OF ty_s_bapi_request.

 

The methods "fill_header" and "fill_amounts" follow the same pattern:

  1. Map fields from RAP structure to BAPI structure

  2. Fill control fields from configuration


Example for header:

 
  METHOD fill_header.
"basics
CLEAR es_header.
es_header-comp_code = is_bonus-CompanyCode.
es_header-fisc_year = is_bonus-FiscalYear.
"dates
es_header-doc_date = is_bonus-DocumentDate.
es_header-pstng_date = is_bonus-PostingDate.
"document references
es_header-ref_doc_no = is_bonus-BonusRecipient.
"determination of internal attributes
det_header_defaults( EXPORTING is_bonus = is_bonus
CHANGING cs_header = es_header
cs_reported = cs_reported ).
ENDMETHOD.

 

The example code for control field determination can be found in the appendix below.

Optional: determinations and validations before save


You can use RAP determinations and validations to do any custom logic before saving the data record, for example, filling field defaults that havent been provided by the caller.

Avoid coding any validation here that is anyway covered by ther BAPI logic.

BAPI call and final keys


For readable code during BAPI processing a result type and table can look like this:

 
    "there is no re-usable structure to split this key
TYPES:
BEGIN OF ty_s_acc_doc_key,
belnr TYPE belnr_d,
bukrs TYPE bukrs,
gjahr TYPE gjahr,
END OF ty_s_acc_doc_key.
TYPES:
"! <p class="shorttext synchronized" lang="en">Accounting document result</p>
BEGIN OF ty_s_bapi_result,
pid TYPE abp_behv_pid,
temp_key TYPE ty_s_acc_doc_key,
key TYPE awkey,
msg TYPE STANDARD TABLE OF bapiret2 WITH DEFAULT KEY,
END OF ty_s_bapi_result.

 

BAPI main processing is then implemented in method "adjust_numbers":

 
METHOD adjust_numbers.

DATA lt_bapi_res TYPE SORTED TABLE OF ty_s_bapi_result WITH UNIQUE KEY pid.
DATA lt_bapi_req TYPE SORTED TABLE OF ty_s_bapi_request WITH UNIQUE KEY pid.

"get all records from buffer
READ ENTITY IN LOCAL MODE ZDemo_BonusPayment\\BonusPayment
ALL FIELDS WITH VALUE #( FOR ls_Payment IN mapped-bonuspayment ( %pky = ls_payment-%pre ) )
RESULT DATA(lt_payments).

"prepare the create requests
LOOP AT mapped-bonuspayment ASSIGNING FIELD-SYMBOL(<ls_bonus_key>).
TRY.
"get the record from transaction buffer for this key
DATA(lr_bonus) = REF #( lt_payments[ KEY pid COMPONENTS %pid = <ls_bonus_key>-%pid
%key = <ls_bonus_key>-%tmp ] ).
"new BAPI request
DATA(ls_bapi_request) = VALUE ty_s_bapi_request( pid = lr_bonus->%pid ).

"fill header
fill_header(
EXPORTING is_bonus = lr_bonus->*
IMPORTING es_header = ls_bapi_request-header ).

"fill amounts and account information
"Every payment is a combination of a debit and credit item
fill_amounts(
EXPORTING is_bonus_payment = lr_bonus->%data
is_header = ls_bapi_request-header
CHANGING ct_amounts = ls_bapi_request-currency_amount
ct_accounts = ls_bapi_request-accountgl ).

"call the BAPI
DATA(ls_bapi_result) = VALUE ty_s_bapi_result( pid = ls_bapi_request-pid ).
CALL FUNCTION 'BAPI_ACC_DOCUMENT_POST'
EXPORTING
documentheader = ls_bapi_request-header
IMPORTING
obj_key = ls_bapi_result-acct_doc_key
TABLES
accountgl = ls_bapi_request-accountgl
currencyamount = ls_bapi_request-currency_amount
return = ls_bapi_result-msg.

"handle errors
handle_bapi_result( EXPORTING is_result = ls_bapi_result
CHANGING cs_reported = reported ).

"split key string
DATA(ls_acc_doc_key) = CONV ty_s_acc_doc_key( ls_bapi_result-acct_doc_key ).
<ls_bonus_key>-companycode = ls_acc_doc_key-bukrs.
<ls_bonus_key>-fiscalyear = ls_acc_doc_key-gjahr.
<ls_bonus_key>-accountingdocument = ls_acc_doc_key-belnr.

CATCH zcx_demo_rap_facade_web_api INTO DATA(lx_bapi_error).
"log error reason
APPEND VALUE #( %pky = <ls_bonus_key>-%pre
%msg = lx_bapi_error ) TO reported-bonuspayment.
"BAPI call or preparation failed
INSERT VALUE #( %pky = <ls_bonus_key>-%pre
%create = if_abap_behv=>mk-on
) INTO TABLE failed-bonuspayment.
ENDTRY.
ENDLOOP.
ENDMETHOD.

 

 

OData V4 Web API


The root entity view is exposed as OData V4 service using a new service definition:

 
@EndUserText.label: 'RAP facade demo: bonus payment'
define service ZDEMO_BONUS_PAYMENT {
expose ZDemo_BonusPayment as BonusPayments;
}

 

Service Binding is created using template OData V4 - WebAPI:

 


Service Binding


 

Testing with Postman


For the real test you can setup a Postman collection with the following requests:

  1. GET - Read the entity set


  2. POST - to Create a new payment

    • URL:  same as for above

    • Header parameter to set the CSRF Token from read
      "x-csrf-token = ...."

    • Payload raw, please choose type JSON
      {
      "Currency": "EUR",
      "Amount": 100.00,
      "BonusRecipient": "Jan Sample"
      }​




  3. Sample response JSON:

    • The response should look like this
      {
      "@odata.context": "$metadata#BonusPayments/$entity",
      "@odata.metadataEtag": "W/\"20230731082241\"",
      "@odata.etag": "W/\"SADL-303832333039~082309\"",
      "CompanyCode": "1010",
      "FiscalYear": "2023",
      "AccountingDocument": "4900010896",
      "DocumentDate": "2023-07-31",
      "PostingDate": "2023-07-31",
      "Currency": "EUR",
      "Amount": 100,
      "CreatedAt": "08:23:09",
      "CreatedOn": "2023-07-31",
      "CreatedBy": "HIDDEN",
      "BonusRecipient": "Jan Sample",
      "SAP__Messages": []
      }





 

ABAP test runner


For a quick test, create an executable class with interface "if_oo_adt_classrun".

 
"! <p class="shorttext synchronized" lang="en">RAP Facade: Bonus payment test run</p>
CLASS zcl_demo_rap_facade_test DEFINITION PUBLIC FINAL CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun .
ENDCLASS.

 

The main method can run the end-to-end test with minimal data:

 
  METHOD if_oo_adt_classrun~main.

TYPES ty_s_bonus_data TYPE STRUCTURE FOR CREATE zdemo_bonuspayment\\BonusPayment.

"fill minimal data
DATA(ls_bonus) = VALUE ty_s_bonus_data(
%cid = 'CREATE_MINIMAL_001'
CompanyCode = '1010'
PostingDate = sy-datum "optional
amount = 500
Currency = 'EUR'
%control = VALUE #(
CompanyCode = if_abap_behv=>mk-on
PostingDate = if_abap_behv=>mk-on
amount = if_abap_behv=>mk-on
Currency = if_abap_behv=>mk-on
)
) ##no_text.

"run
MODIFY ENTITY zdemo_bonuspayment\\BonusPayment
CREATE FROM VALUE #( ( ls_bonus ) )
MAPPED DATA(ls_create_mapped)
FAILED DATA(ls_create_failed)
REPORTED DATA(ls_create_reported).

"check for problems
IF ls_create_failed IS NOT INITIAL.
LOOP AT ls_create_reported-bonuspayment ASSIGNING FIELD-SYMBOL(<ls_create_msg>).
out->write( <ls_create_msg>-%msg->if_message~get_text( ) ).
ENDLOOP.
out->write( 'Creation failed'(e01) ).
RETURN.
ENDIF.

"save
COMMIT ENTITIES BEGIN
RESPONSE OF zdemo_bonuspayment
FAILED DATA(ls_save_failed)
REPORTED DATA(ls_save_reported).

"check for problems
IF ls_save_failed IS NOT INITIAL.
LOOP AT ls_save_reported-bonuspayment ASSIGNING FIELD-SYMBOL(<ls_save_msg>).
out->write( <ls_save_msg>-%msg->if_message~get_text( ) ).
ENDLOOP.
out->write( 'Saving failed'(e01) ).
RETURN.
ENDIF.

LOOP AT ls_create_mapped-bonuspayment ASSIGNING FIELD-SYMBOL(<ls_temp_key>).
"get the final keys from late numbering
CONVERT KEY OF zdemo_bonuspayment\\BonusPayment
FROM TEMPORARY VALUE #( %pid = <ls_temp_key>-%pid
%tmp = <ls_temp_key>-%key ) TO DATA(ls_acc_doc_key).
"check we have a new document key
IF ls_acc_doc_key-AccountingDocument IS INITIAL.
out->write( 'Saving did not generate AccountingDocument key'(e02) ).
RETURN.
ELSE.
out->write( |Generated Accounting Document key { ls_acc_doc_key-AccountingDocument }| ).
ENDIF.
ENDLOOP.

COMMIT ENTITIES END.

"check for problems
IF ls_save_failed IS NOT INITIAL.
out->write( 'Saving failed'(e03) ).
RETURN.
ENDIF.

"re-read new record from DB
READ ENTITY zdemo_bonuspayment\\BonusPayment
FROM VALUE #( ( CORRESPONDING #( ls_acc_doc_key ) ) )
RESULT DATA(lt_new_payment_data).

"check for problems
IF lt_new_payment_data IS INITIAL.
out->write( 'Re-read accounting document failed'(e04) ).
ENDIF.

ENDMETHOD.

 

Transaction handling


Database commits


Use of statements causing database commits like "Commit", "Call function destination" or "wait" must not be used inside the RAP processing. It can lead to data inconsistencies and will be prevented by RAP runtime checks in most cases.

Mass processing


When enabling "draft", the draft persistency can be used as staging tables for mass processing.
This approach has been successfully used for initial load of millions of records in multiple projects and scales pretty well, for example, if used together with SAP Application Interface Framework (AIF).

Use of “Call function destination ‘NONE'”


It is common practice to use “Call Function Destination ‘NONE'” in mass processing and AIF interfaces for better control in error situations and memory management.

This concept must not be used inside RAP saver implementations but only as part of the load balancing outside the RAP entity processing.

 

Appendix


The following are support methods and objects just for you information.

Access control inheriting from accounting document


 
@EndUserText.label: 'Demo payment: read access control'
@MappingRole: true
//Inheriting read access from accouting document by company code
define role ZDEMO_BONUSPAYMENT {
grant select on ZDemo_BonusPayment
where inheriting conditions from entity I_AccountingDocument;

grant select on ZDemo_BonusPaymentItem
where inheriting conditions from entity I_AccountingDocument;
}

 

RAP compatible exception class


For debugging it is very useful to have exception classes instead of messages since they contain the triggering source code position.

When you enable your exception class as RAP message class they will be forwarded  throughout the stack and you jump to the trigger point even from many levels above.

Note: Make sure to default the message severity in the "Constructor".
"! <p class="shorttext synchronized" lang="en">RAP demo: error messages</p>
CLASS zcx_demo_rap_facade_web_api DEFINITION
PUBLIC
INHERITING FROM cx_static_check
FINAL
CREATE PUBLIC .

PUBLIC SECTION.
INTERFACES if_abap_behv_message .
CONSTANTS:
"! <p class="shorttext synchronized" lang="en">Unexpected technical error</p>
BEGIN OF c_tech_error,
msgid TYPE symsgid VALUE 'ZDEMO_RAP_FACADE_WEB',
msgno TYPE symsgno VALUE '002',
attr1 TYPE scx_attrname VALUE '',
attr2 TYPE scx_attrname VALUE '',
attr3 TYPE scx_attrname VALUE '',
attr4 TYPE scx_attrname VALUE '',
END OF c_tech_error ##no_text.
CONSTANTS:
"! <p class="shorttext synchronized" lang="en">Document already exists</p>
BEGIN OF c_already_exists,
msgid TYPE symsgid VALUE 'ZDEMO_RAP_FACADE_WEB',
msgno TYPE symsgno VALUE '001',
attr1 TYPE scx_attrname VALUE 'MV_ACCOUNTING_DOCUMENT',
attr2 TYPE scx_attrname VALUE 'MV_COMPANY_CODE',
attr3 TYPE scx_attrname VALUE 'MV_FISCAL_YEAR',
attr4 TYPE scx_attrname VALUE '',
END OF c_already_exists ##no_text.

"! <p class="shorttext synchronized" lang="en">Company code</p>
DATA mv_company_code TYPE ZDemo_BonusPayment-CompanyCode.
"! <p class="shorttext synchronized" lang="en">Fiscal year</p>
DATA mv_Fiscal_Year TYPE ZDemo_BonusPayment-FiscalYear.
"! <p class="shorttext synchronized" lang="en">Document number</p>
DATA mv_Accounting_Document TYPE ZDemo_BonusPayment-AccountingDocument.

"! <p class="shorttext synchronized" lang="en">CONSTRUCTOR</p>
METHODS constructor
IMPORTING
textid LIKE if_t100_message=>t100key OPTIONAL
previous LIKE previous OPTIONAL
severity TYPE if_abap_behv_message=>t_severity DEFAULT if_abap_behv_message=>severity-error
mv_company_code TYPE zdemo_bonuspayment-companycode OPTIONAL
mv_fiscal_year TYPE zdemo_bonuspayment-fiscalyear OPTIONAL
mv_accounting_document TYPE zdemo_bonuspayment-accountingdocument OPTIONAL.
ENDCLASS.

CLASS zcx_demo_rap_facade_web_api IMPLEMENTATION.
METHOD constructor ##ADT_SUPPRESS_GENERATION.
super->constructor( previous = previous ).

me->mv_company_code = mv_company_code.
me->mv_fiscal_year = mv_fiscal_year.
me->mv_accounting_document = mv_accounting_document.

"RAP message severity
me->if_abap_behv_message~m_severity = severity. //<<<<< manually added!

CLEAR me->textid.
IF textid IS INITIAL.
if_t100_message~t100key = if_t100_message=>default_textid.
ELSE.
if_t100_message~t100key = textid.
ENDIF.
ENDMETHOD.
ENDCLASS.

 

Filling document header from configuration


 
METHOD det_header_defaults.
"values from configuration - hard coded just for demo purposes!
IF cs_header-comp_code IS INITIAL.
cs_header-comp_code = ms_config-company_code.
ENDIF.

cs_header-doc_type = ms_config-document_type.
cs_header-header_txt = |Bonus payout ARE { cs_header-fisc_year } for { cs_header-ref_doc_no }| ##no_text. "demo only
cs_header-username = sy-uname.

IF cs_header-doc_date IS INITIAL. "fallback
cs_header-doc_date = sy-datum.
ENDIF.

IF cs_header-pstng_date IS INITIAL. "fallback
cs_header-pstng_date = sy-datum.
ENDIF.

"determine fiscal year if not provided
IF cs_header-fisc_year IS INITIAL.
DATA(ls_msg) = VALUE bapiret1( ).
CALL FUNCTION 'BAPI_COMPANYCODE_GET_PERIOD'
EXPORTING
companycodeid = cs_header-comp_code
posting_date = cs_header-pstng_date
IMPORTING
fiscal_year = cs_header-fisc_year
return = ls_msg.
"handle errors
IF ls_msg-type CA c_msg_type_error.
RAISE EXCEPTION new_msg_from_bapi( CORRESPONDING #( ls_msg ) ).
ENDIF.
ENDIF.
ENDMETHOD.

 

Handling BAPI messages in RAP


Definition:

 
    "! <p class="shorttext synchronized" lang="en">Late messages</p>
TYPES ty_s_reported TYPE RESPONSE FOR REPORTED LATE zdemo_bonuspayment.
"! <p class="shorttext synchronized" lang="en">Late errors</p>
TYPES ty_s_failed TYPE RESPONSE FOR FAILED LATE zdemo_bonuspayment.

"! <p class="shorttext synchronized" lang="en">Handle BAPI exceptions + messages</p>
"! @parameter is_result | BAPI result
METHODS handle_bapi_result
IMPORTING
is_result TYPE bapiret2_tab
CHANGING
cs_reported TYPE ty_s_reported
cs_failed TYPE ty_s_failed.

 

Implementation:

 
  METHOD handle_bapi_result.
"process all messages
LOOP AT is_result-msg ASSIGNING FIELD-SYMBOL(<ls_bapi_msg>).
"Convert all messages
DATA(lo_behv_msg) = new_msg_from_bapi( <ls_bapi_msg> ).
"Stop on error
IF <ls_bapi_msg>-type CA c_msg_type_error.
DATA(lx_error_msg) = lo_behv_msg.
EXIT.
ELSE.
APPEND VALUE #( %pid = is_result-pid
%msg = lo_behv_msg ) TO cs_reported-bonuspayment.
ENDIF.
ENDLOOP.
"check a key was generated
IF lx_error_msg IS NOT BOUND
AND is_result-acct_doc_key IS INITIAL.
"Unexpected technical error
RAISE EXCEPTION TYPE zcx_demo_rap_facade_web_api
EXPORTING
textid = zcx_demo_rap_facade_web_api=>c_tech_error.
ENDIF.
"If there was an error, raise it
IF lx_error_msg IS BOUND.
RAISE EXCEPTION lx_error_msg.
ENDIF.
ENDMETHOD.

 

Conversion of BAPI messages to RAP messages


 

Definition:

 
    "! <p class="shorttext synchronized" lang="en">New RAP message from bapiret</p>
"! @parameter is_message | BAPIRET message
"! @parameter ro_msg | RAP behavior message
METHODS new_msg_from_bapi
IMPORTING is_message TYPE bapiret2
RETURNING VALUE(ro_msg) TYPE REF TO zcx_demo_rap_facade_web_api.

 

Implementation:

 
  METHOD new_msg_from_bapi.
CHECK is_message IS NOT INITIAL.
"determine severity
DATA(lv_msg_type) = COND #( WHEN is_message-type IS NOT INITIAL THEN is_message-type
ELSE if_abap_behv_message=>severity-error ).
"set system variables
MESSAGE ID is_message-id TYPE lv_msg_type NUMBER is_message-number
WITH is_message-message_v1 is_message-message_v2 is_message-message_v3 is_message-message_v4
INTO DATA(lv_msg).
TRY.
"raise message from system variables
RAISE EXCEPTION TYPE zcx_demo_rap_facade_web_api USING MESSAGE.
CATCH zcx_demo_rap_facade_web_api INTO ro_msg ##no_handler.
ENDTRY.
ENDMETHOD.

References


The concepts for wrapping APIs in SAP S/4HANA is based on the official guidelines:

ABAP Cloud API Enablement Guidelines for SAP S/4HANA Cloud, private edition, and SAP S/4HANA

Extend SAP S/4HANA in the cloud and on premise with ABAP based extensions
17 Comments