Tutorial: Build Your Own SAP Fiori Approve Purchase Order App – Part 13
This is the thirteenth part of a tutorial series about how to build your own SAP Fiori Approve Purchase Order app.
The purpose of this tutorial is to show you step-by-step how to build your own SAP Fiori Approve Purchase Orders app and provides additional insights into why certain aspects have been developed as they are.
Please see the introductory post (Tutorial: Build Your Own SAP Fiori Approve Purchase Order App) for background information regarding this tutorial.
Previously posted chapters can be find here:
In this chapter, we set up the app using the SAP Web IDE and ran the app using mock data.
In this chapter, we adapted the list screen of the app.
In this chapter, we adapted the detail screen of the app.
In this chapter, we enabled approve and reject buttons.
In this sixth chapter, we set up a new model that stores the global app state.
In this seventh chapter, we encapsulated approve / reject service calls.
In this eighth chapter, we mimicked the backend logic.
In this ninth chapter, we refreshed the master and detail screen.
In this tenth chapter, we implemented code to block the screen for further input, for example, to prevent a user from approving the same purchase order twice, and we also created an extension point.
In this eleventh chapter, we implemented the OData service used for the purchase approval app and created CDS views.
- Part 12: (Tutorial Build Your Own SAP Fiori Approve Purchase Order App – Part 12)
In this twelfth chapter, we registered the current user as an Enterprise Procurement Model (EPM) user.
In this thirteenth chapter, we’re going to implement the missing approve / reject functions as part of our OData service.
Implement approval and reject functions
We’ll now implement the missing approve / reject functions as part of our OData service. Basically, the EPM model contains the required business functionality, but we need to make it fit to our OData service.
Define required function imports
Each function is realized as function import.
The UI5 OData model checks for the definition of the function imports in the metadata of the OData service. If you call a function import without defining it, you will get an exception from the UI5 OData model.
1. Start SAP Gateway Service Builder using transaction SEGW.
2. In your project, right click Data Model –> Create –> Complex Type to create the folder in which you define the function import.
3. Create a new type FunctionImportResult. This is the return structure of the function import. It contains a single Boolean flag success to indicate the successful execution of a purchase order. Select ABAP Structure SEPMRA_S_FUNCIMPORT_SUCCESS.
4. Expand the Properties node and create a new property success based on Edm Typeboolean.
5. Right click the Function Imports node of your project and chose Create from the context menu.
6. Add ApprovePurchaseOrder and RejectPurchaseOrder as function imports with return type FunctionImportResult.
7. Regenerate the runtime artifacts.
We have now defined both function imports. Now, we need to implement the business logic. This will be done in the DPC_EXT class of our OData service. We now need to switch to the ABAP Development Tools.
Implement the business logic
1. In ABAP Developer Tools, locate your data model provider extension class (_DPC_EXT).
2. In the private section of this class, we’ll define:
- Method APPROVE_PO to store the approval logic
- Method REJECT_PO to store the reject logic
- Method SET_PO_COMMENT implementing the comment handling
- Method ID_TO_KEY to perform key mapping from the external PO key to the internal UUID key
- Constants for the function imports
PRIVATE SECTION.
[…]
METHODS:
APPROVE_PO
IMPORTING
IO_TECH_REQUEST_CONTEXT TYPE REF TO /IWBEP/IF_MGW_REQ_FUNC_IMPORT
RAISING
/IWBEP/CX_MGW_BUSI_EXCEPTION,
REJECT_PO
IMPORTING
IO_TECH_REQUEST_CONTEXT TYPE REF TO /IWBEP/IF_MGW_REQ_FUNC_IMPORT
RAISING
/IWBEP/CX_MGW_BUSI_EXCEPTION,
SET_PO_COMMENT
IMPORTING
IV_COMMENT TYPE IF_EPM_TEXT=>IF_EPM_TEXT_DATA~TY_NODE_DATA-TEXT
IT_KEYS_PO TYPE IF_EPM_BO=>TT_NODE_KEYS
RAISING
/IWBEP/CX_MGW_BUSI_EXCEPTION,
“! Maps a purchase order ID to the corresponding purchase order key (UUID). If the purchase
“! order does not exist or has already been processed, an exception is raised.
“!
“! @parameter iv_po_id | Purchase order ID
“! @parameter rv_key | Purchase order key
“! @exception lcx_exception | Raised if the PO does not exist at all, or has already been processed.
ID_TO_KEY
IMPORTING
IV_PO_ID TYPE IF_EPM_PO_HEADER=>TY_NODE_DATA-PO_ID
RETURNING
VALUE(RV_KEY) TYPE IF_EPM_BO=>TY_NODE_KEY
RAISING
CX_EPM_REF_APPS_BUSI_EXCEPTION.
CONSTANTS:
“! Constants for OData Service Operations offered by this service
BEGIN OF GC_SERVICE_OPERATION,
“! Service Operation: “Approve Purchase Order”
APPROVE_PO TYPE /IWBEP/MGW_TECH_NAME VALUE ‘ApprovePurchaseOrder’ ##NO_TEXT,
“! Service Operation: “Reject Purchase Order”
REJECT_PO TYPE /IWBEP/MGW_TECH_NAME VALUE ‘RejectPurchaseOrder’ ##NO_TEXT,
END OF GC_SERVICE_OPERATION.
3. In the public section, define a redefinition of /IWBEP/IF_MGW_APPL_SRV_RUNTIME~EXECUTE_ACTION. This method is called by gateway to resolve the logic of a function import call.
CLASS ZCL_Z_PO_TUTORIAL_DPC_EXT DEFINITION
PUBLIC
INHERITING FROM ZCL_Z_PO_TUTORIAL_DPC
CREATE PUBLIC .
PUBLIC SECTION.
METHODS CONSTRUCTOR.
METHODS /IWBEP/IF_MGW_APPL_SRV_RUNTIME~EXECUTE_ACTION
REDEFINITION .
4. Now, we’ll switch to the class implementation. First, let’s implement EXECUTE_ACTION. This will basically delegate to the corresponding approve / reject methods for the two known function imports of our model and delegate to the super class implementation otherwise.
METHOD /IWBEP/IF_MGW_APPL_SRV_RUNTIME~EXECUTE_ACTION.
FIELD-SYMBOLS: <LS_RESULT> TYPE CL_EPM_REF_APPS_PO_APV_MPC_EXT=>FUNCTIONIMPORTRESULT.
CASE IO_TECH_REQUEST_CONTEXT->GET_FUNCTION_IMPORT_NAME( ).
WHEN GC_SERVICE_OPERATION-APPROVE_PO.
APPROVE_PO( IO_TECH_REQUEST_CONTEXT ).
CREATE DATA ER_DATA LIKE <LS_RESULT>.
ASSIGN ER_DATA->* TO <LS_RESULT>.
<LS_RESULT>-SUCCESS = ABAP_TRUE.
WHEN GC_SERVICE_OPERATION-REJECT_PO.
REJECT_PO( IO_TECH_REQUEST_CONTEXT ).
CREATE DATA ER_DATA LIKE <LS_RESULT>.
ASSIGN ER_DATA->* TO <LS_RESULT>.
<LS_RESULT>-SUCCESS = ABAP_TRUE.
WHEN OTHERS.
SUPER->/IWBEP/IF_MGW_APPL_SRV_RUNTIME~EXECUTE_ACTION(
EXPORTING
IO_TECH_REQUEST_CONTEXT = IO_TECH_REQUEST_CONTEXT
IMPORTING
ER_DATA = ER_DATA
).
ENDCASE.
ENDMETHOD.
5. We’ll now implement the approval logic. The approval logic is explained as follows:
1. The logic first retrieves the parameters from the request context. Parameters are the purchase order ID and the comment.
2. Next, it maps the external purchase order ID to the internal UUID using the utility function.
3. It then calls the approve logic from the underlying EPM model and changes the status of the purchase order.
4. In case a comment is provided, the corresponding method to store the comment is called.
5. Finally, EPM is called to store to the changes.
In case of exceptions, this implementation reuses the exception classes from the reference library. This is only done to minimize the required coding since the definition of exception classes in not in the core scope of this tutorial.
METHOD APPROVE_PO.
DATA: LS_ACTION_PARAMS TYPE TS_FUNCTIONPARAMETER,
LT_KEYS_PO TYPE IF_EPM_BO=>TT_NODE_KEYS,
LV_KEY_PO LIKE LINE OF LT_KEYS_PO,
LT_INFO TYPE IF_EPM_BO=>TT_NODE_KEY_INFO,
LV_SUCCESS TYPE ABAP_BOOL,
LX_EXCEPTION TYPE REF TO CX_ROOT.
FIELD-SYMBOLS: <LS_INFO> LIKE LINE OF LT_INFO.
IO_TECH_REQUEST_CONTEXT->GET_CONVERTED_PARAMETERS( IMPORTING ES_PARAMETER_VALUES = LS_ACTION_PARAMS ).
LV_KEY_PO = ME->ID_TO_KEY( LS_ACTION_PARAMS-PO_ID ).
INSERT LV_KEY_PO INTO TABLE LT_KEYS_PO.
TRY.
“
” APPROVE PURCHASE ORDER
“
MO_EPM_PO->IF_EPM_PO_HEADER~ACTION_ACCEPT(
EXPORTING
IT_NODE_KEYS = LT_KEYS_PO
II_MESSAGE_BUFFER = MO_EPM_MSG_BUFFER
IMPORTING
ET_NODE_KEY_INFO = LT_INFO
).
” set Purchase Order Status additionally to ‘sent’.
IF NOT LINE_EXISTS( LT_INFO[ OPERATION_SUCCESS = ABAP_FALSE ] ).
MO_EPM_PO->IF_EPM_PO_HEADER~ACTION_PURCHASE_ORDER_SENT(
EXPORTING
IT_NODE_KEYS = LT_KEYS_PO
II_MESSAGE_BUFFER = MO_EPM_MSG_BUFFER
IMPORTING
ET_NODE_KEY_INFO = LT_INFO
).
ENDIF.
“in case of errors, raise them now
READ TABLE LT_INFO ASSIGNING <LS_INFO> WITH KEY NODE_KEY = LV_KEY_PO OPERATION_SUCCESS = ABAP_FALSE.
IF SY-SUBRC = 0.
CX_EPM_REF_APPS_BUSI_EXCEPTION=>RAISE_FROM_EPM_MESSAGE_BUFFER( MO_EPM_MSG_BUFFER ).
ENDIF.
“
” SET COMMENT, IF PROVIDED
“
IF LS_ACTION_PARAMS-NOTE IS NOT INITIAL.
SET_PO_COMMENT(
IV_COMMENT = LS_ACTION_PARAMS-NOTE
IT_KEYS_PO = LT_KEYS_PO
).
ENDIF.
“
” SAVE CHANGES
“
LV_SUCCESS = CL_EPM_SERVICE_FACADE=>SAVE( MO_EPM_MSG_BUFFER ).
IF LV_SUCCESS <> ABAP_TRUE.
CX_EPM_REF_APPS_BUSI_EXCEPTION=>RAISE_FROM_EPM_MESSAGE_BUFFER( MO_EPM_MSG_BUFFER ).
ENDIF.
CATCH CX_EPM_SYSTEM_EXCEPTION CX_EPM_API_EXCEPTION INTO LX_EXCEPTION.
RAISE EXCEPTION TYPE CX_EPM_REF_APPS_BUSI_EXCEPTION
EXPORTING
PREVIOUS = LX_EXCEPTION.
ENDTRY.
ENDMETHOD.
6. Next, we’ll implement the reject logic. The implementation follows the same pattern as the approval logic.
METHOD REJECT_PO.
DATA: LS_ACTION_PARAMS TYPE TS_FUNCTIONPARAMETER,
LT_KEYS_PO TYPE IF_EPM_BO=>TT_NODE_KEYS,
LV_KEY_PO LIKE LINE OF LT_KEYS_PO,
LT_INFO TYPE IF_EPM_BO=>TT_NODE_KEY_INFO,
LV_SUCCESS TYPE ABAP_BOOL,
LX_EXCEPTION TYPE REF TO CX_ROOT.
FIELD-SYMBOLS: <LS_INFO> LIKE LINE OF LT_INFO.
IO_TECH_REQUEST_CONTEXT->GET_CONVERTED_PARAMETERS( IMPORTING ES_PARAMETER_VALUES = LS_ACTION_PARAMS ).
LV_KEY_PO = ME->ID_TO_KEY( LS_ACTION_PARAMS-PO_ID ).
INSERT LV_KEY_PO INTO TABLE LT_KEYS_PO.
TRY.
“
” REJECT PURCHASE ORDER
“
MO_EPM_PO->IF_EPM_PO_HEADER~ACTION_REJECT(
EXPORTING
IT_NODE_KEYS = LT_KEYS_PO
II_MESSAGE_BUFFER = MO_EPM_MSG_BUFFER
IMPORTING
ET_NODE_KEY_INFO = LT_INFO
).
“in case of errors, raise them now
READ TABLE LT_INFO ASSIGNING <LS_INFO> WITH KEY NODE_KEY = LV_KEY_PO OPERATION_SUCCESS = ABAP_FALSE.
IF SY-SUBRC = 0.
CX_EPM_REF_APPS_BUSI_EXCEPTION=>RAISE_FROM_EPM_MESSAGE_BUFFER( MO_EPM_MSG_BUFFER ).
ENDIF.
“
” SET COMMENT, IF PROVIDED
“
IF LS_ACTION_PARAMS-NOTE IS NOT INITIAL.
SET_PO_COMMENT(
IV_COMMENT = LS_ACTION_PARAMS-NOTE
IT_KEYS_PO = LT_KEYS_PO
).
ENDIF.
“
” SAVE CHANGES
“
LV_SUCCESS = CL_EPM_SERVICE_FACADE=>SAVE( MO_EPM_MSG_BUFFER ).
IF LV_SUCCESS <> ABAP_TRUE.
CX_EPM_REF_APPS_BUSI_EXCEPTION=>RAISE_FROM_EPM_MESSAGE_BUFFER( MO_EPM_MSG_BUFFER ).
ENDIF.
CATCH CX_EPM_SYSTEM_EXCEPTION CX_EPM_API_EXCEPTION INTO LX_EXCEPTION.
RAISE EXCEPTION TYPE CX_EPM_REF_APPS_BUSI_EXCEPTION
EXPORTING
PREVIOUS = LX_EXCEPTION.
ENDTRY.
ENDMETHOD.
7. We’ll then implement the logic storing the comment. Again, we’ll use existing EPM logic to store the comment.
METHOD SET_PO_COMMENT.
DATA: LT_DATA_TEXTHEADER TYPE IF_EPM_TEXT=>IF_EPM_TEXT_HEADER~TT_NODE_DATA,
LT_KEYS_TEXTHEADER TYPE IF_EPM_BO=>TT_NODE_KEYS,
LT_DATA_TEXTDATA TYPE IF_EPM_TEXT=>IF_EPM_TEXT_DATA~TT_NODE_DATA,
LV_SUCCESS TYPE ABAP_BOOL,
LT_INFO TYPE IF_EPM_BO=>TT_NODE_KEY_INFO,
LV_KEY_PO LIKE LINE OF IT_KEYS_PO,
LX_EXCEPTION TYPE REF TO CX_ROOT.
FIELD-SYMBOLS: <LS_DATA_TEXTHEADER> LIKE LINE OF LT_DATA_TEXTHEADER,
<LS_DATA_TEXTDATA> LIKE LINE OF LT_DATA_TEXTDATA,
<LS_INFO> LIKE LINE OF LT_INFO.
TRY.
” Try to set the note text in the note’s original language.
MO_EPM_PO->IF_EPM_PO_HEADER~NAVIGATE_TO_HEADER_NOTE(
EXPORTING
IT_SOURCE_NODE_KEYS = IT_KEYS_PO
II_MESSAGE_BUFFER = MO_EPM_MSG_BUFFER
IMPORTING
ET_DATA = LT_DATA_TEXTHEADER
).
IF LT_DATA_TEXTHEADER IS NOT INITIAL.
READ TABLE LT_DATA_TEXTHEADER ASSIGNING <LS_DATA_TEXTHEADER> INDEX 1.
INSERT <LS_DATA_TEXTHEADER>-NODE_KEY INTO TABLE LT_KEYS_TEXTHEADER.
MO_EPM_TXT->IF_EPM_TEXT_HEADER~NAVIGATE_TO_DATA(
EXPORTING
IT_SOURCE_NODE_KEYS = LT_KEYS_TEXTHEADER
IV_EDIT_MODE = IF_EPM_BO=>GC_EDIT_EXCLUSIVE
IV_FILTER_LANGUAGE = <LS_DATA_TEXTHEADER>-ORIGINAL_LANGU
II_MESSAGE_BUFFER = MO_EPM_MSG_BUFFER
IMPORTING
ET_DATA = LT_DATA_TEXTDATA
).
IF LT_DATA_TEXTDATA IS NOT INITIAL.
READ TABLE LT_DATA_TEXTDATA ASSIGNING <LS_DATA_TEXTDATA> INDEX 1.
<LS_DATA_TEXTDATA>-TEXT = IV_COMMENT.
MO_EPM_TXT->IF_EPM_TEXT_DATA~UPDATE(
EXPORTING
IT_DATA = LT_DATA_TEXTDATA
II_MESSAGE_BUFFER = MO_EPM_MSG_BUFFER
IMPORTING
ET_NODE_KEY_INFO = LT_INFO
).
READ TABLE LT_INFO ASSIGNING <LS_INFO> WITH KEY NODE_KEY = <LS_DATA_TEXTDATA>-NODE_KEY OPERATION_SUCCESS = ABAP_FALSE.
IF SY-SUBRC = 0.
CX_EPM_REF_APPS_BUSI_EXCEPTION=>RAISE_FROM_EPM_MESSAGE_BUFFER( MO_EPM_MSG_BUFFER ).
ENDIF.
ENDIF.
ELSE.
” no header note yet –> set one
READ TABLE IT_KEYS_PO INTO LV_KEY_PO INDEX 1.
MO_EPM_PO->IF_EPM_PO_HEADER~SET_NOTE(
EXPORTING
IV_NODE_KEY = LV_KEY_PO
IV_TEXT = IV_COMMENT
II_MESSAGE_BUFFER = MO_EPM_MSG_BUFFER
IMPORTING
EV_SUCCESS = LV_SUCCESS
).
IF LV_SUCCESS = ABAP_FALSE.
CX_EPM_REF_APPS_BUSI_EXCEPTION=>RAISE_FROM_EPM_MESSAGE_BUFFER( MO_EPM_MSG_BUFFER ).
ENDIF.
ENDIF.
CATCH CX_EPM_SYSTEM_EXCEPTION CX_EPM_API_EXCEPTION INTO LX_EXCEPTION.
RAISE EXCEPTION TYPE CX_EPM_REF_APPS_BUSI_EXCEPTION
EXPORTING
PREVIOUS = LX_EXCEPTION.
ENDTRY.
ENDMETHOD.
8. Finally, the implementation of the helper method to map the purchase order keys.
METHOD ID_TO_KEY.
DATA: LT_DATA_PO TYPE IF_EPM_PO_HEADER=>TT_NODE_DATA,
LT_SEL_PAR_PO_IDS TYPE IF_EPM_PO_HEADER=>TT_SEL_PAR_HEADER_IDS,
LS_SEL_PAR_PO_ID LIKE LINE OF LT_SEL_PAR_PO_IDS,
LX_EXCEPTION TYPE REF TO CX_ROOT.
FIELD-SYMBOLS: <LS_DATA_PO> LIKE LINE OF LT_DATA_PO.
” Map PO ID to PO key
” Use the query API instead of direct ID-to-key conversion so that the
” approval status can be taken into account
LS_SEL_PAR_PO_ID-SIGN = ‘I’.
LS_SEL_PAR_PO_ID-OPTION = ‘EQ’.
” PO ID needs to be alpha-converted
CALL FUNCTION ‘CONVERSION_EXIT_ALPHA_INPUT’
EXPORTING
INPUT = IV_PO_ID
IMPORTING
OUTPUT = LS_SEL_PAR_PO_ID-LOW.
INSERT LS_SEL_PAR_PO_ID INTO TABLE LT_SEL_PAR_PO_IDS.
TRY.
MO_EPM_PO->IF_EPM_PO_HEADER~QUERY_BY_HEADER(
EXPORTING
IT_SEL_PAR_HEADER_IDS = LT_SEL_PAR_PO_IDS
IV_START_TRANSACTION = ABAP_FALSE
IMPORTING
ET_DATA = LT_DATA_PO
).
READ TABLE LT_DATA_PO ASSIGNING <LS_DATA_PO> INDEX 1.
IF <LS_DATA_PO> IS ASSIGNED AND <LS_DATA_PO>-APPROVAL_STATUS = IF_EPM_PO_HEADER=>GC_APPROVAL_STATUS_INITIAL.
” PO exists and is in initial approval state
RV_KEY = <LS_DATA_PO>-NODE_KEY.
ELSEIF <LS_DATA_PO> IS ASSIGNED.
” PO exists, but with invalid approval state
RAISE EXCEPTION TYPE CX_EPM_REF_APPS_BUSI_EXCEPTION
EXPORTING
TEXTID = CX_EPM_REF_APPS_BUSI_EXCEPTION=>PO_ALREADY_PROCESSED
MSGV1 = |{ IV_PO_ID }|.
ELSE.
” PO not found at all
RAISE EXCEPTION TYPE CX_EPM_REF_APPS_BUSI_EXCEPTION
EXPORTING
TEXTID = CX_EPM_REF_APPS_BUSI_EXCEPTION=>PO_NOT_FOUND
MSGV1 = |{ IV_PO_ID }|.
ENDIF.
CATCH CX_EPM_API_EXCEPTION CX_EPM_SYSTEM_EXCEPTION INTO LX_EXCEPTION.
RAISE EXCEPTION TYPE CX_EPM_REF_APPS_BUSI_EXCEPTION
EXPORTING
PREVIOUS = LX_EXCEPTION.
ENDTRY.
ENDMETHOD.
9. Activate your service implementation.
I hope the thirteenth part of the tutorial has sparked your interest in the next and final chapter. In the final chapter, we’ll take a look at the occurrences of entity set names in this tutorial should you have to adapt them.