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