Payment Notes Parser using Regular Expressions
Back to list < Fire Protection Tax Dependency Injection with ABAP Objects >
,
To process incoming payments and correctly assign them to open items, insurance objects, contract accounts or business partners, you need to parse the payment notes transmitted by the bank by bank statement. FS-CD only offers the standard SAP pattern recognition with the wildcards + (exactly one character) or * (any number of characters). In most cases this will certainly suffice, especially if depositors can be given standardized payment notes. However, there may be scenarios in which the payment notes cannot be standardized, for example, in the B2B area, if the payers use different accounting systems with different payment notes. In such a scenario, you quickly reach the limits of the SAP standard, and a hard-coded ABAP solution is doomed to failure from the outset because it is far too inflexible. In such a scenario, regular expressions are useful for pattern recognition. An introduction to regular expressions can be found in the linked Wikipedia article.
Solution approach
The solution I will describe here is based on the fact that the structure of the payment note (which elements are contained in which order) is recognized with a regular expression. In a second step, these elements are then extracted from the note to payee using the submatches.
Examples for payment notes:
0212647563/274635
KONTO 021.1426342 RE 685634
The account numbers in this example always have the prefix 021, followed by seven digits. The invoice numbers have six digits. A regular expression could look like this:
(021\.?)(\d{7})(\D*)(\d{6})
Means: A hit starts with 021, optionally followed by a period. Then a seven-digit number. Next, a section of any length (also empty), which must not contain any digits. Then a six-digit number. The four blocks in parentheses mean that these four blocks are returned as submatches when a hit occurs (ABAP command find first occurrence of regex... in ... results ...
). The selection values for the number stack position can now be formed from these four submatches:
Examples:
Insurance Object ID (selection category V): ABC-(2)
Reference Document Number(selection category X): (4)
The digits in brackets are replaced by the respective submatch and the results are written to the payment lot position. This all takes place in event 0950 (Payment lot transfer: Add selection).
This results in the following selection values for the two above-mentioned payment notes:
V = ABC-2647563, X = 274635
V = ABC-1426342, X = 685634
Which elements are required for the implementation? Obviously a function module for event 0950, and still a Customizing table in which the rules are stored. In order not to let the function module become too extensive, I lay out the actual parser in a separate class.
The Customizing table ZCDRULES
The key of this table is the rule ID in addition to the client. This gives each rule a unique key that can be defined as required. Furthermore, each rule has a priority that determines the sequence of execution.
Since FS-CD also transfers the company code and the bank clearing account in event 0950, it is useful for special cases to provide a way of restricting the execution of certain rules to individual company codes or bank clearing accounts.
Other attributes of the table are the regular expression that describes the pattern of the note to payee, and an expression for each selection type to be found that describes how the value is composed of the submatches.
The table also has three additional flags, "Post on account", "Create clarification case" and "Active". The first two flags are written directly to the payment lot item if they are set, and thus immediately generate a posting on account or a clarification case without the item being sent to clearing control beforehand. With the third flag, a rule can be simply deactivated without having to delete the rule immediately.
There is also an attribute for documenting the rule, in which comments can be entered.
Here the complete structure of the table:
Attribute | Key | Data Type | Description |
---|---|---|---|
CLIENT | X | CLNT | Client |
RULE_ID | X | CHAR 10 | Rule ID |
PATTERN | CHAR 200 | Pattern of payment note (RegExp) | |
PRIORITY | NUMC 3 | Priority of the rule | |
COMPANY_CODE | CHAR 4 | Company Code (also * possible) | |
BANK_ACCOUNT | CHAR 10 | Bank Clearing account (also * possible) | |
INSOBJ_ID | CHAR 30 | Template of Insurance Object ID (with (n) parameters) | |
INVOICE_ID | CHAR 30 | Template for Reference Document Number (with (n) parameters) | |
ON_ACCOUNT | CHAR 1 | Set On Account Flag | |
CLEARING | CHAR 1 | Set clarification case flag | |
ACTIVE | CHAR 1 | Indicator as to whether the rule is active | |
REMARK | CHAR 50 | Remark |
The Parser class ZCL_CD_PARSER
The class essentially consists of the method PARSE, which determines the rule to be used for a transferred purpose and returns the required elements. The method uses the local auxiliary class CL_RULE, which represents a single rule. The parser class creates an object of CL_RULE in its constructor for each rule and passes the corresponding entry from ZCDRULES to it.
In addition to the constructor, the auxiliary class has the two functional methods APPLIES_TO and GET_RESULT.
The method APPLIES_TO first checks whether the rule can be applied (company code and bank clearing account) and then evaluates the regular expression. If successful, it saves the submatches and returns ABAP_TRUE, otherwise ABAP_FALSE.
The method GET_RESULT creates a copy of the entry from ZCDRULES and replaces the (n)
parameters with the concrete submatches.
The complete source code of the auxiliary class CL_RULE:
*"* use this source file for the definition and implementation of *"* local helper classes, interface definitions and type *"* declarations *----------------------------------------------------------------------* * CLASS CL_RULE DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* class CL_RULE definition final. public section. methods CONSTRUCTOR importing RULE_DATA type ZCDRULES. methods APPLIES_TO importing COMPANY_CODE type ZCDRULES-COMPANY_CODE BANK_ACCOUNT type ZCDRULES-BANK_ACCOUNT REFERENCE type CLIKE returning VALUE(RESULT) type ABAP_BOOL. methods GET_RESULT returning VALUE(RESULT) type ref to ZCDRULES. private section. data: RULE_DATA type ZCDRULES, SUBMATCHES type table of STRING. endclass. "CL_RULE DEFINITION *----------------------------------------------------------------------* * CLASS CL_RULE IMPLEMENTATION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* class CL_RULE implementation. method CONSTRUCTOR. ME->RULE_DATA = RULE_DATA. endmethod. "CONSTRUCTOR method APPLIES_TO. data: MATCH_RESULT type MATCH_RESULT, SUBMATCH_RESULT type ref to SUBMATCH_RESULT, SUBMATCH type STRING. clear SUBMATCHES. if RULE_DATA-COMPANY_CODE <> '*' and RULE_DATA-COMPANY_CODE <> COMPANY_CODE. return. endif. if RULE_DATA-BANK_ACCOUNT <> '*' and RULE_DATA-BANK_ACCOUNT <> BANK_ACCOUNT. return. endif. find first occurrence of regex RULE_DATA-PATTERN in REFERENCE results MATCH_RESULT. if SY-SUBRC <> 0. return. endif. loop at MATCH_RESULT-SUBMATCHES reference into SUBMATCH_RESULT. if SUBMATCH_RESULT->LENGTH > 0. SUBMATCH = REFERENCE+SUBMATCH_RESULT->OFFSET(SUBMATCH_RESULT->LENGTH). insert SUBMATCH into table SUBMATCHES. else. insert initial line into table SUBMATCHES. endif. endloop. RESULT = ABAP_TRUE. endmethod. "APPLIES_TO method GET_RESULT. data SUBMATCH type ref to STRING. create data RESULT. RESULT->* = RULE_DATA. loop at SUBMATCHES reference into SUBMATCH. replace all occurrences of |({ SY-TABIX })| in RESULT->INSOBJ_ID with SUBMATCH->*. replace all occurrences of |({ SY-TABIX })| in RESULT->INVOICE_ID with SUBMATCH->*. endloop. endmethod. "GET_RESULT endclass. "CL_RULE IMPLEMENTATION
In the method PARSE of the parser class ZCL_CD_PARSER not much happens anymore. The method loops all rule objects and calls the method APPLIES_TO for each rule object. If this ABAP_TRUE returns, the method PARSE calls the method GET_RESULT and returns the result (the applied rule with the resolved elements) to the caller. If no rule could be applied, the method returns an empty rule with the clarification case flag set. It is also important that the rules are pre-sorted according to priority and rule ID so that the rules are run through later in the correct and always the same order.
Here is the complete source code of the parser class:
class ZCL_CD_PARSER definition public final . public section. *"* public components of class ZCL_CD_PARSER *"* do not include other source files here!!! types: RULES_TAB type table of ZCDRULES with default key . methods CONSTRUCTOR . methods PARSE importing !COMPANY_CODE type ZCDRULES-COMPANY_CODE !BANK_ACCOUNT type ZCDRULES-BANK_ACCOUNT !REFERENCE type CLIKE returning value(RESULT) type ref to ZCDRULES raising CX_SY_REGEX . protected section. *"* protected components of class ZCL_CD_PARSER *"* do not include other source files here!!! private section. *"* private components of class ZCL_CD_PARSER *"* do not include other source files here!!! data: RULES type table of ref to CL_RULE with non-unique default key . ENDCLASS. CLASS ZCL_CD_PARSER IMPLEMENTATION. * <signature>---------------------------------------------------------------------------------------+ * | Instance Public Method ZCL_CD_PARSER->CONSTRUCTOR * +-------------------------------------------------------------------------------------------------+ * +--------------------------------------------------------------------------------------</signature> method CONSTRUCTOR. data: RULE_DATA_TAB type table of ZCDRULES, RULE_DATA type ref to ZCDRULES, RULE type ref to CL_RULE. select * from ZCDRULES into table RULE_DATA_TAB where ACTIVE = ABAP_TRUE. sort RULE_DATA_TAB by PRIORITY RULE_ID. loop at RULE_DATA_TAB reference into RULE_DATA . create object RULE exporting RULE_DATA = RULE_DATA->*. insert RULE into table RULES. endloop. endmethod. "CONSTRUCTOR * <signature>---------------------------------------------------------------------------------------+ * | Instance Public Method ZCL_CD_PARSER->PARSE * +-------------------------------------------------------------------------------------------------+ * | [--->] COMPANY_CODE TYPE ZCDRULES-COMPANY_CODE * | [--->] BANK_ACCOUNT TYPE ZCDRULES-BANK_ACCOUNT * | [--->] REFERENCE TYPE CLIKE * | [<-()] RESULT TYPE REF TO ZCDRULES * | [!CX!] CX_SY_REGEX * +--------------------------------------------------------------------------------------</SIGNATURE> method PARSE. data RULE type ref to CL_RULE. loop at RULES into RULE. check RULE->APPLIES_TO( COMPANY_CODE = COMPANY_CODE BANK_ACCOUNT = BANK_ACCOUNT REFERENCE = REFERENCE ) = ABAP_TRUE. RESULT = RULE->GET_RESULT( ). *** Include further checks here if necessary *** return. endloop. create data RESULT. RESULT->CLEARING = ABAP_TRUE. endmethod. "PARSE ENDCLASS.
The Function Module Z_CD_EVENT_0950
This function module, which is defined in transaction FQEVENTS in event 0950, represents the link between FS-CD and the parser. Another important detail is that the function module enters the rule ID of the applied rule in the attribute INFOF in the payment lot position. This helps tremendously in the analysis of clarification cases, since it can be recognized immediately whether and which rule was applied. Here is a possible implementation of the function module:
function Z_CD_EVENT_0950. *"---------------------------------------------------------------------- *"*"Lokale Schnittstelle: *" IMPORTING *" REFERENCE(I_FKKZK) LIKE DFKKZK STRUCTURE DFKKZK *" TABLES *" T_FKKZP STRUCTURE DFKKZP *" T_FKKZS STRUCTURE DFKKZS *" T_FKKZV STRUCTURE DFKKZV OPTIONAL *"---------------------------------------------------------------------- data: PARSER type ref to ZCL_CD_PARSER, POSITION type ref to DFKKZP, REFERENCE type STRING, EXT_REF type ref to DFKKZV, RESULT type ref to ZCDRULES. create object PARSER. loop at T_FKKZP reference into POSITION. REFERENCE = TO_UPPER( POSITION->TXTVW ). loop at T_FKKZV reference into EXT_REF where KEYZ1 = POSITION->KEYZ1 and UPOSV = POSITION->UPOSV. "#EC CI_NESTED REFERENCE = REFERENCE && TO_UPPER( EXT_REF->TXTVW ). endloop. RESULT = PARSER->PARSE( COMPANY_CODE = POSITION->BUKRS BANK_ACCOUNT = POSITION->BVRKO REFERENCE = REFERENCE ). if not RESULT->INSOBJ_ID is initial. POSITION->SELW2 = RESULT->INSOBJ_ID. POSITION->SELT2 = 'V'. endif. if not RESULT->INVOICE_ID is initial. POSITION->SELW3 = RESULT->INVOICE_ID. POSITION->SELT3 = 'X'. endif. POSITION->XAKON = RESULT->ON_ACCOUNT. POSITION->XKLAE = RESULT->CLEARING. POSITION->INFOF = RESULT->RULE_ID. endloop. endfunction.
Recap
The solution approach presented here represents a basic framework that can be extended for the most diverse requirements. If the numbers transmitted in the note to payee contain check digits, these could be checked, and if the check digit is invalid, the clarification case flag could be set immediately (see selected position in method PARSE).
Translated with DeepL
Back to list < Fire Protection Tax Dependency Injection with ABAP Objects >