FS-CD Business Domain Filter
,
FI-CA Contract Accounts Receivable and Payable, on which FS-CD is based as an industry-specific enhancement for insurance companies, provides a two-level enhancement concept for industry-specific and customer-specific enhancements. This concept is based on events triggered by FI-CA or FS-CD. Currently 1,686 events are defined, of which 1,365 are from FI-CA and 321 from FS-CD (ERP 6.05 SP 8).
For each event, function modules can be entered in Customizing (transaction FQEVENTS), whereby the interface of the modules is predefined by SAP. SAP also determines whether an event can only be implemented once or several times. This distinction can be explained by the characteristics of the event. If the event has a notification character, that is, the triggering program wants to draw attention to a certain situation without expecting a certain result, the event can be implemented several times. This corresponds to the Observer pattern. However, if the caller needs a result from the enhancement, the event can only be implemented once. This corresponds to the Template-Method-Pattern.
FI-CA provides sample modules. FS-CD also provides industry-specific modules. For certain events, FS-CD can exclude the possibility that customer-specific modules are allowed. For events that can be implemented once, the first of the following modules - if available - is called: Customer module, FS-CD module, sample module. If multiple implementations are possible, all FS-CD modules are processed first and then all customer modules. In this case, the sample module is empty and is only used to define the interface.
But what happens if the requirements of several departments are to be mapped in the same system and in the same client? If events can be implemented several times, all departments could define their additional functions, but it is the responsibility of the respective developer that the additional functions of his department are not executed for other departments. For events that can be implemented once, several departments would have to share a function module. The following problems may occur:
- High, cross-departmental coordination effort.
- regression test effort for other departments, as side effects cannot be completely excluded.
- If different packages are to be used for the different user departments, difficulties may arise in the package assignment of the function modules.
The simplest solution would of course be separate clients, but this is not realistic in most cases. For this reason I would like to present a concept for the solution of this problem, which I could implement recently with a customer. This is a framework that uses the Mediator between the event and the user department specific functionality. This framework, I have called it "FS-CD Business Domain Filter", should determine the current department in a first step from the available information, which is available at the respective event. In a second step, the framework calls the department-specific additional functionality. The framework requires the following components:
- A function module of the framework is provided for each relevant event, which is entered as the customer module for the event. Since each event requires a different interface, a separate function module is required for each event.
- To determine the department, a method is required for each derivation method that adopts this derivation (for example, contract account category, company code, bank clearing account, and so on).
- Determination of the department-specific function module to be called. If no department-specific module is found, the FS-CD industry-specific or SAP sample module is determined.
- Call the function module found.
Derivation of user department
A data element and a fixed value domain ZCD_BDOMAIN are created in the DDIC. The data type is arbitrary, e.g. CHAR 3. The fixed values correspond to the departments. Different information is available at each point in time, which can be used to derive the department. I will use the contract account type as an example, which is available at many times. Other candidates are, for example, the company code, product group or business area. Sometimes more complex derivations may be required, for example, from the contract account number to the contract account category for the department.
A method is defined in interface ZIF_CD_BDOMAIN_DERIVE for each derivation method. These methods are then implemented in class ZCL_CD_BDOMAIN_DERIVE. It makes sense to define interfaces to achieve a loose coupling between a class and its users. Mapping the contract account type to department is stored in the new Customizing table ZCD_VKTYP_BDOM:
Attribute | Key? | Data element | Type | Length | Description |
---|---|---|---|---|---|
CLIENT | X | MANDT | CLNT | 3 | Client |
VKTYP | X | VKTYP_KK | CHAR | 2 | Contract Account Category |
BDOMAIN | ZCD_BDOMAIN | CHAR | 3 | Department |
Of course, a maintenance view with maintenance dialog should be created for this and all other Customizing tables, but I will not describe this in more detail here.
Since many events are also called in batch processes, it's recommended to load the Customizing table completely in the constructor of the class for performance reasons.
We define the instance attribute GT_VKTYP_BDOM in the class ZCL_CD_BDOMAIN_DERIVE with direct type input:
TYPE HASHED TABLE OF zcd_vktyp_bdom WITH UNIQUE KEY vktyp.
You can also define a corresponding table type in the DDIC or in a TYPE-POOL.
The table entries are then loaded completely in the constructor:
SELECT * FROM zcd_vktyp_bdom INTO TABLE gt_vktyp_bdom.
The derivation method then looks like this:
Kind | Parameter | Type |
---|---|---|
IMPORTING | VALUE( IV_VKTYP ) | ZCD_VKTYP_BDOM-VKTYP |
RETURNING | VALUE( RV_BDOMAIN ) | ZCD_VKTYP_BDOM-BDOMAIN |
METHOD zif_cd_bdomain_derive~get_bdomain_by_vktyp. DATA ref_vktyp_bdom TYPE REF TO zcd_vktyp_bdom. READ TABLE gt_vktyp_bdom REFERENCE INTO ref_vktyp_bdom WITH TABLE KEY vktyp = iv_vktyp. IF sy-subrc EQ 0. MOVE ref_vktyp_bdom->bdomain TO rv_bdomain. ELSE. CLEAR rv_bdomain. ENDIF. ENDMETHOD.
Determination of the function module to be called
A Customizing table ZCD_BDOMAIN_FUNC with the following structure is required to determine the user department specific module:
Attribut | Key? | Data element | Typ | Length | Description |
---|---|---|---|---|---|
CLIENT | X | MANDT | CLNT | 3 | Client |
BDOMAIN | X | ZCD_BDOMAIN | CHAR | 3 | Department |
EVENT | X | FBEVE_KK | CHAR | 4 | Event |
FUNCTION | FUNCC_KK | CHAR | 30 | Function module |
The primary key selected in the Customizing table allows you to transport a change for a user department without affecting other user departments. If the user department were not in the key, a change to an entry may affect another user department.
The method for determining and calling the function module is encapsulated in a separate class ZCL_CD_EVENT_INVOKER, which implements the interface ZIF_CD_EVENT_INVOKER, independently of the logic for determining the user department.
Kind | Parameter | Type |
---|---|---|
IMPORTING | VALUE( IV_BDOMAIN ) | ZCD_BDOMAIN_FUNC-BDOMAIN |
IMPORTING | VALUE( IV_EVENT ) | ZCD_BDOMAIN_FUNC-EVENT |
IMPORTING | IT_PARMBIND | ABAP_FUNC_PARMBIND_TAB |
IMPORTING | IT_EXCPBIND | ABAP_FUNC_EXCPBIND_TAB |
RETURNING | VALUE( RV_SUBRC ) | SYSUBRC |
METHOD zif_cd_event_invoker~invoke_event. DATA lv_function TYPE zcd_bdomain_func-function. lv_function = get_function_by_bdomain( iv_bdomain = iv_bdomain iv_event = iv_event ). IF NOT lv_function IS INITIAL. CALL FUNCTION lv_function PARAMETER-TABLE it_parmbind EXCEPTION-TABLE it_excpbind. ENDIF. MOVE sy-subrc TO rv_subrc. ENDMETHOD.
The types of the two internal tables IT_PARMBIND and IT_EXCPBIND are defined in the type-pool ABAP and are used to pass the parameters to a function module completely generically. The actual determination of the module to be called takes place in method GET_FUNCTION_BY_BDOMAIN.
Kind | Parameter | Type |
---|---|---|
IMPORTING | VALUE( IV_BDOMAIN ) | ZCD_BDOMAIN_FUNC-BDOMAIN |
IMPORTING | VALUE( IV_EVENT ) | ZCD_BDOMAIN_FUNC-EVENT |
RETURNING | VALUE( RV_FUNCTION ) | ZCD_BDOMAIN_FUNC-FUNCTION |
METHOD get_function_by_domain. DATA: ref_bdomain_func TYPE REF TO zcd_bdomain_func, ls_tfkfbm TYPE tfkfbm, ref_func TYPE REF TO tfkfbc, lt_func LIKE TABLE OF ref_func->*. CLEAR rv_function. READ TABLE gt_bdomain_func REFERENCE INTO ref_bdomain_func WITH TABLE KEY event = iv_event bdomain = iv_bdomain. IF sy-subrc NE 0. SELECT SINGLE * INTO ls_tfkfbm FROM tfkfbm WHERE fbeve = iv_event. IF ls_tfkfbm-xmehr EQ abap_true. RETURN. ENDIF. CALL FUNCTION 'FKK_FUNC_MODULE_DETERMINE' EXPORTING i_fbeve = iv_event i_applk = 'V' i_only_application = abap_true TABLES t_fbstab = lt_func. READ TABLE lt_func REFERENCE INTO ref_func INDEX 1. IF sy-subrc NE 0. CREATE DATA ref_func. MOVE ls_tfkfbm-funcm TO ref_func->funcc. ENDIF. CREATE DATA ref_bdomain_func. MOVE: iv_bdomain TO ref_bdomain_func->bdomain, iv_event TO ref_bdomain_func->event, ref_func->funcc TO ref_bdomain_func->function. INSERT ref_bdomain_func->* INTO TABLE gt_bdomain_func. ENDIF. MOVE ref_bdomain_func->function TO rv_function. ENDMETHOD.
The module to be called is first searched in the internal table GT_BDOMAIN_FUNC.
This internal table is defined as an instance attribute (direct type entry TYPE HASHED TABLE OF ZCD_BDOMAIN_FUNC WITH UNIQUE KEY EVENT BDOMAIN
) and was loaded completely from the Customizing table ZCD_BDOMAIN_FUNC in the constructor.
If no suitable entry is found there, the system checks whether the event can be implemented more than once.
If the event can be implemented more than once, the method is exited, since in this case the FS-CD modules have already been called.
However, if the event can only be implemented once, the corresponding FS-CD or FI-CA module must be called in this case if no department-specific module was found.
First, the function module FKK_FUNC_MODULE_DETERMINE with the flag I_ONLY_APPLICATION set is used to determine the industry-specific module.
If a function module is found (since it is only a simple-implementable event, access with INDEX 1
is OK), it will not be returned as a module to be called, unless the default module is returned, but not without first memorizing it in our internal table GT_BDOMAIN_FUNC for the next time.
Parameter transfer
As already shown above, the two internal tables IT_PARMBIND and IT_EXCPBIND are used to pass parameters dynamically and bind the exceptions dynamically. I chose this way to keep the later coding in the event modules as short as possible and to enable unit tests for the event modules. These binding tables are created by method ZIF_CD_EVENT_INVOKER~BUILD_BINDING:
Kind | Parameter | Type |
---|---|---|
IMPORTING | VALUE( IV_EVENT ) | ZCD_BDOMAIN_FUNC-EVENT |
EXPORTING | ET_PARMBIND | ABAP_FUNC_PARMBIND_TAB |
EXPORTING | ET_EXCPBIND | ABAP_FUNC_EXCPBIND_TAB |
METHOD zif_cd_event_invoker~build_binding. DATA: lv_funcname TYPE rs38l-name, lt_exc TYPE rsfb_exc, lt_exp TYPE TABLE OF rsexp, lt_imp TYPE TABLE OF rsimp, lt_cha TYPE TABLE OF rscha, lt_tbl TYPE TABLE OF rstbl. CONCATENATE gc_func_prefix iv_event INTO lv_funcname. CALL FUNCTION 'FUNCTION_IMPORT_INTERFACE' EXPORTING funcname = lv_funcname TABLES exception_list = lt_exc export_parameter = lt_exp import_parameter = lt_imp changing_parameter = lt_cha tables_parameter = lt_tbl. CALL METHOD build_excpbind EXPORTING it_rsexc = lt_exc CHANGING ct_excpbind = et_excpbind. CALL METHOD build_parmbind EXPORTING it_parm = lt_exp iv_kind = abap_func_importing CHANGING ct_parmbind = et_parmbind. CALL METHOD build_parmbind EXPORTING it_parm = lt_imp iv_kind = abap_func_exporting CHANGING ct_parmbind = et_parmbind. CALL METHOD build_parmbind EXPORTING it_parm = lt_cha iv_kind = abap_func_changing CHANGING ct_parmbind = et_parmbind. CALL METHOD build_parmbind EXPORTING it_parm = lt_tbl iv_kind = abap_func_tables CHANGING ct_parmbind = et_parmbind. ENDMETHOD.
The constant GC_FUNC_PREFIX is a prefix with which the name of the event module is formed (see below).
The auxiliary method BUILD_EXCPBIND has the following code:
Kind | Parameter | Type |
---|---|---|
IMPORTING | IT_RSEXC | RSFB_EXC |
CHANGING | CT_EXCPBIND | ABAP_FUNC_EXCPBIND_TAB |
METHOD build_excpbind. DATA: lr_excp TYPE REF TO rsexc, ls_excpbind LIKE LINE OF ct_excpbind. LOOP AT it_rsexc REFERENCE INTO lr_excp. MOVE: lr_excp->exception TO ls_excpbind-name, sy-tabix TO ls_excpbind-value. INSERT ls_excpbind INTO TABLE ct_excpbind. ENDLOOP. ENDMETHOD.
Since the auxiliary method BUILD_PARMBIND is to be used for importing, exporting and changing parameters, the parameter IT_PARM has the type ANY TABLE
and access is dynamic with field symbols:
Kind | Parameter | Type |
---|---|---|
IMPORTING | IT_PARM | ANY TABLE |
IMPORTING | IV_KIND | ABAP_FUNC_PARMBIND-KIND |
CHANGING | CT_PARMBIND | ABAP_FUNC_PARMBIND_TAB |
METHOD build_parmbind. DATA: ls_parmbind LIKE LINE OF ct_parmbind. FIELD-SYMBOLS: <parameter> TYPE ANY, <name> TYPE ls_parmbind-name. LOOP AT it_parm ASSIGNING <parameter>. ASSIGN COMPONENT 'PARAMETER' OF STRUCTURE <parameter> TO <name>. ASSERT sy-subrc EQ 0. MOVE: <name> TO ls_parmbind-name, iv_kind TO ls_parmbind-kind. INSERT ls_parmbind INTO TABLE ct_parmbind. ENDLOOP. ENDMETHOD.
This means that we have already filled the two internal binding tables. Only the references to the concrete data objects (attribute ABAP_FUNC_PARMBIND-VALUE) are missing, which can only be added in the event modules.
The event modules
As mentioned above, we need a separate event function module for each relevant event.
These are created by copying the corresponding SAP sample modules into one (or, depending on the number of modules, several) customer-specific function groups.
I use the function group ZF_CD_DOMAIN_FILTER, and as naming convention for the event modules I use Z_CD_FILTER_EVENT_{event}, for example Z_CD_FILTER_EVENT_1102.
The constant GC_FUNC_PREFIX in the class ZCL_CD_EVENT_INVOKER is set to 'Z_CD_FILTER_EVENT_'
.
In each module, you determine the department from the available information using class ZCL_CD_DOMAIN_DERIVE and bind the parameters and exceptions using class ZCL_CD_EVENT_INVOKER and determine and call the department specific module.
The objects of both classes are created and stored in the TOP include of the function group:
DATA: gr_derive TYPE REF TO zif_cd_domain_derive, gr_invoke TYPE REF TO zif_cd_event_invoker. LOAD-OF-PROGRAM. IF gr_derive IS INITIAL. CREATE OBJECT gr_derive TYPE zcl_cd_domain_derive. ENDIF. IF gr_invoke IS INITIAL. CREATE OBJECT gr_invoke TYPE zcl_cd_event_invoker. ENDIF.
The ABAP event LOAD-OF-PROGRAM is something like the constructor of a function group. The objects are only created if they do not yet exist, for example, by a unit test. This makes it possible to use mocks for unit tests instead of the two classes.
I have combined the parameter binding and the call of the department-specific module in a macro in the top include that can be used in each of the event modules:
DEFINE invoke_event. data: lt_parm type abap_func_parmbind_tab, lr_parm type ref to abap_func_parmbind, lt_excp type abap_func_excpbind_tab, lv_tab type string, lv_subrc type sysubrc. field-symbols: <fs> type any. call method gr_invoke->build_binding exporting iv_event = &2 importing et_parmbind = lt_parm et_excpbind = lt_excp. loop at lt_parm reference into lr_parm. if lr_parm->kind eq abap_func_tables. assign (lr_parm->name) to <fs>. get reference of <fs> into lr_parm->tables_wa. concatenate lr_parm->name '[]' into lv_tab. assign (lv_tab) to <fs>. get reference of <fs> into lr_parm->value. else. assign (lr_parm->name) to <fs>. get reference of <fs> into lr_parm->value. endif. endloop. lv_subrc = gr_invoke->invoke_event( iv_domain = &1 iv_event = &2 it_parmbind = lt_parm it_excpbind = lt_excp ). move lv_subrc to sy-subrc. END-OF-DEFINITION.
In this macro, the binding tables are first filled with the method BUILD_BINDING. References to the data objects in the parameter table are then added - for internal tables also the header line (work area). These data objects are only visible in the body of the event module, hence the solution with the macro. Finally, the department-specific module is determined and called using the method INVOKE_EVENT.
Finally, the coding of the event 1102:
FUNCTION z_cd_filter_event_1102. *"---------------------------------------------------------------------- *"*"Local Interface: *" IMPORTING *" REFERENCE(I_FKKOP) LIKE FKKOP STRUCTURE FKKOP *" REFERENCE(I_FKKKO) LIKE FKKKO STRUCTURE FKKKO OPTIONAL *" REFERENCE(I_CALLID) LIKE CALLID STRUCTURE CALLID OPTIONAL *" EXPORTING *" REFERENCE(E_FKKOP) LIKE FKKOP STRUCTURE FKKOP *" TABLES *" T_MESSA STRUCTURE FIMSG OPTIONAL *"---------------------------------------------------------------------- DATA lv_bdomain TYPE zcd_bdomain_func-bdomain. lv_bdomain = gr_derive->get_bdomain_by_vktyp( i_fkkop-vktyp ). invoke_event lv_bdomain '1102'. ENDFUNCTION.
Pretty neat, huh? As a small performance optimization I have defined all parameters as call-by-reference. This is not critical here, since the data is not changed. Depending on the setting, call-by-value is used again when it is transferred to the department specific function module.
Recap
This framework makes it possible to separate the requirements of the departments and thus avoid dependencies. In individual cases, it can be difficult to determine the department as planned at certain times, as the information provided is not sufficient. Additional accesses to additional tables, such as the payment lot or the parameters of a mass activity, may be required to find the information required for derivation. In some cases it is necessary to remember information between specific times, since specific times are processed in a sequence (for example, 0010, 0020 and 0030). Using the where-used list of the SAP sample modules, you can usually find the call points in FI-CA or FS-CD in order to analyze the SAP coding for further to find ways to derive the department.
An essential feature of the presented solution is the unit test capability. This is made possible by encapsulating the actual logic in classes that are only communicated with via interfaces. This allows the two classes involved and the event modules to be tested in isolation from each other, which is very important for a cross-cutting topic like this.
Translated with DeepL