Background
There are not many options in the SAP ERP system for consuming FI-CO and its associated master data objects especially in a single Work Process (LUW). Other constraints below:
◉ BDC screen recordings come in the way of consumption – no options for Class based APIs for these FI-CO master data objects especially for master data maintenance (these guys are as old as the dinosaurs but they are not extinct yet!).
◉ MDG/MDM is not used by majority of the customers who use SAP ERP.
◉ There are some Function Modules available in the system but they are not even wrapped up for data maintenance in a single instance – they are also not object oriented as a result the caller has to map data every time and subsequently call those Function Modules again.
◉ They all live happily in their own Function Groups with global data dependency and call sequence dependency (E.g. FM 2 of Function Group does nothing unless FM 1 of the same FG was called prior to calling FM 2)
◉ These APIs could be helpful for anyone looking to consume them from anywhere in the SAP system or outside – for example, Fiori OData or a Webservice or from a non-SAP system via an inbound PI interface; these API objects could be called within their Helper Class for example.
Business Requirement
For the business requirement I was working on, the following needed to happen from a business process perspective:
◉ A new Adobe Interactive Form (ISR Processes and Forms) to be able to enter master data attributes for CRUD operations (except the Delete).
◉ The Adobe form is validated, existence of master data objects are checked, master data object creation/updates are simulated before user is able to send the data for review and approval via Business Workflow.
◉ New Workflow is triggered when the Adobe form is submitted.
◉ Create/Update of Master data objects takes place at the final level approval of the Workflow.
The FI-CO master data objects are:
◉ Create/Update of a GL Account in two separate Chart of Accounts (FSP0) and the flexibility to:
◉ Create a GL Account in Chart of Account with a Reference to an existing GL Account.
◉ Create/Update of a GL Account in Company Code (FS00, FSS0) and the flexibility to:
◉ Create a GL Account in Company Code with a Reference to an existing GL Account.
◉ Create/Update (i.e. Assignment) of a GL Account in two separate Financial Statement Version (FSV) (FSE2)
◉ Create/Update of a Commitment Item (related to the GL Account) (FMCIA) and the flexibility to:
◉ Create a Commitment Item with a Reference to an existing Commitment Item.
◉ Create/Update of a Cost Element (related to the GL Account) (KA01/02) and the flexibility to:
◉ Create a Cost Element with Reference to an existing Cost Element.
Example of a Consumption (Adobe Interactive Form and Workflow)
I will not go into too much details for the Adobe form design and implementation because the focus of this blog are the FI-GL APIs which are consumption agnostic i.e. it does not matter if a form is trying to consume it or a Fiori OData Service implementation Class or a PI interface – just think of the form as a trigger to consume the Abstract Factory objects.
The ISR form implementation is driven by its BAdI (Definition QISR1) implementation where these API objects are called – short view of code snippet below from the BAdI implementation (this is where the object simulation happens during form data validation)
The int_service_request_check method of the BAdI Interface validates the data after Check button is clicked on the ISR form.
METHOD if_ex_qisr1~int_service_request_check.
me->set_mode( mode ).
me->set_command( user_command ).
me->set_view( form_view ).
me->set_special( special_data ).
me->set_additional( additional_data ).
me->set_control( CHANGING ct_special_data = special_data ).
CHECK me->get_view( ) = |ISR_REQUEST| AND
NOT me->get_fldvalue( |DRAFT| ) AND
( me->get_mode( ) = |CREATE| OR me->get_mode( ) = |CHANGE| ) AND
( me->get_command( ) = |CHECK| OR me->get_command( ) = |START| ).
TRY.
me->check_mandatory( ).
me->simulate( ).
CATCH BEFORE UNWIND zcx_fi_general INTO DATA(lo_exception).
IF lo_exception->is_resumable IS NOT INITIAL.
RESUME.
ELSE.
me->set_messages( lo_exception->get_messages( ) ).
message_list = me->get_messages( ).
me->clear_fields( ).
special_data = me->get_special( ).
return = VALUE #( message_list[ 1 ] OPTIONAL ).
RETURN.
ENDIF.
ENDTRY.
message_list = me->get_messages( ).
return = VALUE #( message_list[ 1 ] OPTIONAL ).
special_data = me->get_special( ).
ENDMETHOD.
Master data object simulation happens below – I have only shown two because all others are similar.
Note they all call the same method MAINTAIN_DATA of the API Interface ZIF_MASTERDATA_FACTORY.
METHOD simulate.
me->set_simulate( abap_true ).
me->set_api_mode( SWITCH #( me->get_fldvalue( |REQUEST_TYPE| )
WHEN 1
THEN action-insert
ELSE action-update
)
).
"Simulate Maintain account in COA 2000
me->set_coa_data( SWITCH #( me->get_api_mode( )
WHEN action-insert
THEN me->coa_mapper( )
ELSE me->read_glmast(
VALUE #(
saknr = me->get_fldvalue( |REF_GLACCT| )
ktopl = me->get_fldvalue( |REF_COA| )
)
)-coa_data
)
).
me->set_acct_names( me->acctname_mapper( ) ).
me->lo_gl_factory = me->get_md_factory(
VALUE #( LET api_mode = me->get_api_mode( ) IN
saknr = CONV #( me->get_fldvalue(
SWITCH #( api_mode
WHEN action-insert
THEN |NEW_GLACCT|
ELSE |REF_GLACCT|
)
)
)
ktopl = CONV #( me->get_fldvalue(
SWITCH #( api_mode
WHEN action-insert
THEN |NEW_COA|
ELSE |REF_COA|
)
)
)
)
).
IF me->lo_gl_factory IS BOUND.
me->lo_gl_factory->zif_masterdata_factory~maintain_data( ).
ENDIF.
"Simulate Maintain account in COA 1000
me->set_coa_data( SWITCH #( me->get_api_mode( )
WHEN action-insert
THEN me->coa_mapper( coa_oper_tafe )
ELSE me->read_glmast(
VALUE #(
saknr = me->get_fldvalue( |REF_GLACCT2| )
ktopl = me->get_fldvalue( |REF_COA2| )
)
)-coa_data
)
).
me->set_acct_names( me->acctname_mapper( coa_oper_tafe ) ).
me->lo_gl_factory = me->get_md_factory(
VALUE #( LET api_mode = me->get_api_mode( ) IN
saknr = CONV #( me->get_fldvalue(
SWITCH #( api_mode
WHEN action-insert
THEN |NEW_GLACCT2|
ELSE |REF_GLACCT2|
)
)
)
ktopl = CONV #( me->get_fldvalue(
SWITCH #( api_mode
WHEN action-insert
THEN |NEW_COA2|
ELSE |REF_COA2|
)
)
)
)
).
IF me->lo_gl_factory IS BOUND.
me->lo_gl_factory->zif_masterdata_factory~maintain_data( ).
ENDIF.
"Simulate Commitment Item
me->set_commitment( me->commitment_mapper( ) ).
me->lo_gl_factory = me->get_md_factory(
VALUE #( LET api_mode = me->get_api_mode( ) IN
fikrs = CONV #( me->get_fldvalue(
SWITCH #( api_mode
WHEN action-insert
THEN |NEW_FM|
ELSE |REF_FM|
)
)
)
fipex = CONV #( me->get_fldvalue(
SWITCH #( api_mode
WHEN action-insert
THEN |NEW_COMITEM|
ELSE |REF_COMITEM|
)
)
)
)
).
IF me->lo_gl_factory IS BOUND.
me->lo_gl_factory->zif_masterdata_factory~maintain_data( ).
ENDIF.
"Simulate Maintain account in CCODE 1020
me->set_ccode_data( me->ccode_mapper( ) ).
me->lo_gl_factory = me->get_md_factory(
VALUE #( LET api_mode = me->get_api_mode( ) IN
saknr = CONV #( me->get_fldvalue(
SWITCH #( api_mode
WHEN action-insert
THEN |NEW_GLACCT4|
ELSE |REF_GLACCT4|
)
)
)
bukrs = CONV #( me->get_fldvalue(
SWITCH #( api_mode
WHEN action-insert
THEN |NEW_COMPCODE4|
ELSE |REF_COMPCODE4|
)
)
)
)
).
IF me->lo_gl_factory IS BOUND.
me->lo_gl_factory->zif_masterdata_factory~maintain_data( ).
ENDIF.
"Simulate Section Account Lock/Unlock
IF me->get_fldvalue( |REQUEST_TYPE| ) = 3.
me->lo_gl_factory = NEW zcl_fi_glmast_factory(
iv_saknr = CONV #( me->get_fldvalue(
|REF_ACCTLOCK| ) )
iv_ktopl = CONV #( me->get_fldvalue(
|REF_COALOCK| ) )
iv_mode = me->get_api_mode( )
iv_commit = xsdbool( NOT me->get_simulate( ) )
iv_simulate = me->get_simulate( )
).
IF me->lo_gl_factory IS BOUND.
CAST zcl_fi_glmast_factory( me->lo_gl_factory )->set_coa_block(
CONV #( me->get_fldvalue( |NEW_LOCK| ) ) ).
ENDIF.
ENDIF.
"Simulate Section Account Set/Clear Deletion Flag
IF me->get_fldvalue( |REQUEST_TYPE| ) = 4.
me->lo_gl_factory = NEW zcl_fi_glmast_factory(
iv_saknr = CONV #( me->get_fldvalue(
|REF_ACCTDEL| ) )
iv_ktopl = CONV #( me->get_fldvalue(
|REF_COADEL| ) )
iv_mode = me->get_api_mode( )
iv_commit = xsdbool( NOT me->get_simulate( ) )
iv_simulate = me->get_simulate( )
).
IF me->lo_gl_factory IS BOUND.
CAST zcl_fi_glmast_factory( me->lo_gl_factory )->set_coa_delete(
CONV #( me->get_fldvalue( |NEW_DEL| ) ) ).
ENDIF.
ENDIF.
ENDMETHOD.
The Master Data Factory Abstract Class is instantiated here
METHOD get_md_factory.
* @TODO use Structure for import params instead of multiple single fields
TRY.
ro_md_factory = zcl_fico_masterdata_factory=>create(
iv_saknr = is_keys-saknr
iv_costelem = is_keys-kstar
iv_comitem = is_keys-fipex
iv_ktopl = is_keys-ktopl
iv_bukrs = is_keys-bukrs
iv_fikrs = is_keys-fikrs
iv_versn = is_keys-versn
iv_ergsl = is_keys-ergsl
is_coa_data = me->get_coa_data( )
is_ccode_data = me->get_ccode_data( )
is_acct_name = me->get_acct_names( )
iv_coarea = co_kokrs
is_citemdata = me->get_commitment( )
is_costinput = me->get_costelement( )
* iv_keydate = SY-DATUM
* iv_coelclass = '1'
iv_mode = me->get_api_mode( )
iv_simulate = me->get_simulate( )
iv_commit = xsdbool( NOT me->get_simulate( ) )
).
CATCH zcx_fi_general.
"this is necessary for a better user (UI) experience
DATA(raise) = COND #( WHEN is_keys-saknr IS NOT INITIAL OR
is_keys-fipex IS NOT INITIAL
THEN abap_true
ELSE THROW RESUMABLE
zcx_fi_general(
textid = zcx_fi_general=>incorrect_params
gv_string = |Incorrect Parameters|
)
).
ENDTRY.
ENDMETHOD.
Application Design
◉ Design Pattern Abstract Factory to the rescue including an Interface for all Master Data object CRUD operations.
◉ The idea is to increase the level of abstraction for flexibility and reusability within all the objects of this solution as well as within the development landscape in general so that:
◉ It is easy to add a new master data object in this same model in future by simply using object Inheritance.
◉ Within this design and implementation, any master data factory object is the Abstract object and a distinct (concrete) factory object is implemented for each master data object (e.g. GL, Commitment Items and so on).
◉ Two Abstract Super classes are used; one for returning the concrete Subclass (API Superclass) and the other for returning the concrete Factory Subclass (Factory Superclass).
No comments:
Post a Comment