Verwendungszweck-Parser mit regulären Ausdrücken
Zurück zur Liste < Feuerschutzsteuer Dependency-Injection mit ABAP Objects >
,
Zur Verarbeitung von Zahlungseingängen und zur korrekten Zuordnung zu offenen Posten, Versicherungsobjekten, Vertragskonten bzw. Geschäftspartnern ist es erforderlich, die von der Bank per Kontoauszug übermittelten Verwendungszwecke zu parsen. FS-CD bietet hier standardmäßig nur die in SAP übliche Mustererkennung mit den Wildcards + (genau ein beliebiges Zeichen) oder * (beliebig viele Zeichen) an. In den meisten Fällen wird dies sicherlich ausreichen, insbesondere wenn Einzahlern standardisierte Verwendungszwecke vorgegeben werden können. Es kann jedoch Szenarios geben, in denen die Verwendungszwecke nicht standardisiert werden können, z. B. im B2B-Bereich, wenn von den Einzahlern verschiedene Abrechnungssysteme mit unterschiedlich aufgebauten Verwendungszwecken eingesetzt werden. In einem solchen Szenario stößt man schnell an die Grenzen des SAP-Standards, und eine hartcodierte ABAP-Lösung ist von vorneherein zum Scheitern verurteilt, da sie viel zu unflexibel ist. In einem solchen Szenario bieten sich reguläre Ausdrücke zur Mustererkennung an. Eine Einführung in reguläre Ausdrücke bietet der verlinkte Wikipedia-Artikel.
Lösungsansatz
Der Lösungsansatz, den ich hier beschreiben werde, basiert darauf, dass die Struktur des Verwendungszwecks (welche Elemente sind in welcher Reihenfolge enthalten), mit einem regulären Ausdruck erkannt wird. In einem zweiten Schritt werden diese Elemente dann mit Hilfe der Submatches aus dem Verwendungszweck extrahiert.
Beispiele für Verwendungszwecke:
0212647563/274635
KONTO 021.1426342 RE 685634
Die Kontonummern haben für dieses Beispiel stets den Präfix 021, gefolgt von sieben Stellen. Die Rechnungsnummern sind sechsstellig. Ein regulärer Ausdruck könnte so aussehen:
(021\.?)(\d{7})(\D*)(\d{6})
Bedeutet: Ein Treffer beginnt mit 021, optional gefolgt von einem Punkt. Danach eine siebenstellige Nummer. Als nächstes ein beliebig langer Abschnitt (auch leer), der keine Ziffern enthalten darf. Danach eine sechsstellige Nummer. Die vier in Klammern gesetzten Blöcke bewirken, dass bei einem Treffer diese vier Blöcke als Submatches zurückgegeben werden (ABAP-Befehl find first occurrence of regex ... in ... results ...). Aus diesen vier Submatches lassen sich nun die Selektionswerte für die Zahlstapelposition bilden:
Beispiele:
Versicherungsobjekt-ID (Selektionstyp V): ABC-(2)
Referenzbelegnummer (Selektionstyp X): (4)
Die in Klammern gesetzten Ziffern werden dabei durch das jeweilige Submatch ersetzt und die Ergebnisse in die Zahlstapelposition geschrieben. Das ganze passiert im Zeitpunkt 0950 (Zahlungsstapelübernahme: Selektion ergänzen).
Für die beiden o. g. Verwendungszwecke ergeben sich damit folgende Selektionswerte:
V = ABC-2647563, X = 274635
V = ABC-1426342, X = 685634
Welche Elemente werden für die Umsetzung benötigt? Offensichtlich einen Funktionsbaustein für den Zeitpunkt 0950, und weiterhin eine Customizing-Tabelle, in der die Regeln abgelegt werden. Um den Funktionsbaustein nicht zu umfangreich werden zu lassen, lagere ich den eigentlichen Parser in eine separate Klasse aus.
Die Customizing-Tabelle ZCDRULES
Der Schlüssel dieser Tabelle wird neben dem Mandant die Regel-ID. Jede Regel bekommt somit einen eindeutigen Schlüssel, der beliebig vergeben werden kann. Weiterhin bekommt jede Regel eine Priorität, die die Reihenfolge der Ausführung festlegt.
Da wir von FS-CD im Zeitpunkt 0950 auch den Buchungskreis und das Bankverrechnungskonto übergeben bekommen, bietet es sich für Spezialfälle an, eine Möglichkeit vorzusehen, um die Ausführung bestimmter Regeln auf einzelne Buchungskreise bzw. Bankverrechnungskonten einzuschränken.
Weitere Attribute der Tabelle sind der reguläre Ausdruck, der das Muster des Verwendungszwecks beschreibt, sowie für jeden zu findenden Selektionstyp einen Ausdruck, der beschreibt, wie der Wert aus den Submatches zusammengesetzt wird.
Zusätzlich bekommt die Tabelle noch drei Flags, „Akonto buchen“, „Klärfall erzeugen“ und „Aktiv“. Die ersten beiden Flags werden direkt in die Zahlstapelposition geschrieben, wenn sie gesetzt sind, und erzeugen somit sofort eine Akontobuchung oder einen Klärfall, ohne dass die Position zuvor in die Verrechnungssteuerung geschickt wird. Mit dem dritten Flag kann eine Regel einfach deaktiviert werden, ohne die Regel gleich löschen zu müssen.
Zur Dokumentation der Regel gibt es noch ein Attribut, in dem Anmerkungen erfasst werden können.
Hier der komplette Aufbau der Tabelle:
Attribut | Key | Datentyp | Beschreibung |
---|---|---|---|
CLIENT | X | CLNT | Mandant |
RULE_ID | X | CHAR 10 | Regel-ID |
PATTERN | CHAR 200 | Muster für Verwendungszweck (RegExp) | |
PRIORITY | NUMC 3 | Priorität der Regel | |
COMPANY_CODE | CHAR 4 | Buchungskreis (auch * möglich) | |
BANK_ACCOUNT | CHAR 10 | Bankverrechnungskonto (auch * möglich) | |
INSOBJ_ID | CHAR 30 | Muster für Versicherungsobjekt (mit (n)-Parametern) | |
INVOICE_ID | CHAR 30 | Muster für Referenzbelegnummer (mit (n)-Parametern) | |
ON_ACCOUNT | CHAR 1 | Akonto-Flag setzen | |
CLEARING | CHAR 1 | Klärfall-Flag setzen | |
ACTIVE | CHAR 1 | Kennzeichen, ob die Regel aktiv ist | |
REMARK | CHAR 50 | Anmerkung |
Die Parser-Klasse ZCL_CD_PARSER
Die Klasse besteht im Wesentlichen aus der Methode PARSE, die für einen übergebenen Verwendungszweck die anzuwendende Regel bestimmt und die gewünschten Elemente zurückliefert. Dazu verwendet die Methode die lokale Hilfsklasse CL_RULE, die eine einzelne Regel repräsentiert. Die Parser-Klasse erzeugt in ihrem Konstruktor für jede Regel ein Objekt von CL_RULE und übergibt ihm den zugehörigen Eintrag aus ZCDRULES.
Die Hilfsklasse hat neben dem Konstruktor die beiden funktionalen Methode APPLIES_TO und GET_RESULT.
Die Methode APPLIES_TO prüft zunächst, ob die Regel angewendet werden darf (Buchungskreis und Bankverrechnungskonto) und wertet dann den regulären Ausdruck aus. Falls erfolgreich, sichert sie noch die Submatches und gibt ABAP_TRUE zurück, ansonsten ABAP_FALSE.
Die Methode GET_RESULT legt eine Kopie des Eintrags aus ZCDRULES an und ersetzt die (n)
-Parameter mit den konkreten Submatches.
Hier zunächst der vollständige Quelltext der Hilfsklasse 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 der Methode PARSE der Parser-Klasse ZCL_CD_PARSER passiert dann eigentlich nicht mehr viel. Die Methode führt eine Schleife über alle Regel-Objekte durch und ruft für jedes Regel-Objekt die Methode APPLIES_TO auf. Falls diese ABAP_TRUE zurückliefert, ruft die Methode PARSE die Methode GET_RESULT auf und gibt das Ergebnis (die angewendete Regel mit den aufgelösten Elementen) an den Aufrufer zurück. Falls keine Regel angewendet werden konnte, gibt die Methode eine leere Regel mit gesetztem Klärfall-Flag zurück. Wichtig ist noch, dass der Konstruktur der Parser-Klasse die Regeln nach Priorität und Regel-ID vorsortiert, damit später die Regeln in der richtigen und immer gleichen Reihenfolge durchlaufen werden.
Hier nun der vollständige Quelltext der Parser-Klasse:
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( ). *** hier ggf. weitere Prüfungen einbauen *** return. endloop. create data RESULT. RESULT->CLEARING = ABAP_TRUE. endmethod. "PARSE ENDCLASS.
Der Funktionsbaustein Z_CD_EVENT_0950
Dieser Funktionsbaustein, der in der Transaktion FQEVENTS im Zeitpunkt 0950 eingetragen wird, stellt das Bindeglied zwischen FS-CD und dem Parser dar. Ein wichtiges Detail ist noch, dass der Funktionsbaustein die Regel-ID der angewendeten Regel im Attribut INFOF in der Zahlstapelposition einträgt. Dies hilft ungemein bei der Analyse von Klärfällen, da sofort erkannt werden kann, ob und welche Regel angewendet wurde. Hier eine mögliche Implementierung des Funktionsbausteins:
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.
Zusammenfassung
Der hier vorgestellte Lösungsansatz stellt ein Grundgerüst dar, das für die unterschiedlichsten Anforderungen erweiterbar ist. Sofern in den im Verwendungszweck übermittelten Nummern Prüfziffern enthalten sind, könnten diese überprüft werden, und falls die Prüfziffer ungültig ist, gleich das Klärfall-Flag gesetzt werden (siehe markierte Stelle in der Methode PARSE).
Zurück zur Liste < Feuerschutzsteuer Dependency-Injection mit ABAP Objects >