Zum Hauptinhalt springen

Konzept: kit.common.ix.functions.applyDynKwlMap

Ziel

Es soll eine generische ELO Indexserver-Funktion entstehen, die aus einem Workflow heraus per JSON-Konfiguration eine dynamische Stichwortliste aufruft und deren Ergebnis kontrolliert in ELO-Map-Tabellenfelder schreibt.

Die Funktion dient zunächst als sauberer Datenlieferant und Schreibbaustein für Tabellenfelder in der ELO Invoice Solution. Später soll darauf eine Wareneingangsprüfung mit Merge-/Abgleichlogik aufgebaut werden.

Neuer Namespace:

kit.common.ix.functions.applyDynKwlMap

RF-Funktion:

RF_kit_function_applyDynKwlMap

Ausgangssituation

In der ELO Invoice Solution werden Tabellen über Map-Felder abgebildet.

Beispiel:

IX_MAP_PURCHASE_ORDER_NO1
IX_MAP_PURCHASE_ORDER_NO2
IX_MAP_PURCHASE_ORDER_NO3

Dabei steht die Zahl am Ende für die Tabellenzeile.

Zu einer Bestellung können weitere Felder existieren, z. B.:

IX_MAP_PURCHASE_ORDER_DELIVERY_NO1
IX_MAP_PURCHASE_ORDER_DELIVERY_NO2
IX_MAP_PURCHASE_ORDER_DELIVERY_NO3

Die Bestellnummer kann automatisch erkannt oder manuell im Formular eingetragen worden sein. Das ist für die neue Funktion zunächst egal.

Es gibt bereits dynamische Stichwortlisten, die anhand von Bestellnummer, Lieferscheinnummer oder anderen Eingabewerten Positionen aus einer Datenbank laden und Tabellenfelder füllen können.

Diese bestehende DynKwl-Logik soll weiterverwendet werden.

Fachliches Ziel

Die Funktion soll aus dem Workflow heraus aufgerufen werden können und über JSON vollständig steuerbar sein.

Sie soll:

  1. das aktuelle Dokument/Sord laden
  2. die vorhandenen Map-Felder lesen
  3. anhand definierter Lookup-Felder eine oder mehrere Tabellenzeilen auswerten
  4. pro Zeile eine dynamische Stichwortliste aufrufen
  5. das Ergebnis der Stichwortliste in strukturierte Zeilen umwandeln
  6. die Ergebniszeilen je nach Modus in die Zieltabelle schreiben
  7. optional Statusinformationen schreiben
  8. optional im DryRun nur protokollieren, aber nichts ändern

Beispiel für Workflow-Konfiguration

{
"scriptName": "sol.invoice.ix.dynkwl.PurchaseOrderLine",
"source": {
"maxLines": 50,
"lookupFields": [
"IX_MAP_PURCHASE_ORDER_NO"
],
"lookupFieldsOptional": [
"IX_MAP_PURCHASE_ORDER_DELIVERY_NO"
],
"focusField": "IX_MAP_PURCHASE_ORDER_NO",
"stopOnEmptyRequiredLookup": true
},
"target": {
"mode": "append",
"appendNewRows": true,
"overwriteExisting": false
},
"options": {
"dryRun": false,
"writeStatus": true,
"statusPrefix": "IX_MAP_DYNKWL_APPLY"
}
}

Geplante Modi

1. append

Die Ergebniszeilen der dynamischen Stichwortliste werden immer unten an die bestehende Tabelle angehängt.

Dieser Modus entspricht grob dem bisherigen Verhalten beim manuellen Button „Bestellpositionen laden“.

Vorteil:

  • einfach
  • gut für manuelles Nachladen

Nachteil:

  • kann Duplikate erzeugen
  • kein fachlicher Abgleich

Beispiel:

"target": {
"mode": "append",
"appendNewRows": true,
"overwriteExisting": false
}

2. overwrite

Die Ergebniszeilen werden ab einer definierten Startzeile in die Tabelle geschrieben und vorhandene Werte werden überschrieben.

Dieser Modus ist gefährlich und sollte nicht Standard sein.

Beispiel:

"target": {
"mode": "overwrite",
"startLine": 1,
"overwriteExisting": true
}

3. singleLine

Die Ergebniswerte werden in dieselbe Tabellenzeile geschrieben, aus der der Lookup-Wert stammt.

Beispiel:

"target": {
"mode": "singleLine",
"overwriteExisting": true
}

Beispiel:

Lookup aus PURCHASE_ORDER_NO3
→ Ergebnis wird in Zeile 3 geschrieben

4. merge

Die Ergebniszeilen werden anhand definierter Schlüsselfelder mit vorhandenen Tabellenzeilen abgeglichen.

Beispiel:

"target": {
"mode": "merge",
"appendNewRows": true,
"overwriteExisting": false,
"mergeKeyFields": [
"IX_MAP_PURCHASE_ORDER_NO",
"IX_MAP_PURCHASE_ORDER_LINE_NO",
"IX_MAP_PURCHASE_ORDER_DELIVERY_NO"
]
}

Logik:

Wenn Merge-Schlüssel bereits vorhanden:
- nicht überschreiben, außer overwriteExisting = true

Wenn Merge-Schlüssel nicht vorhanden:
- neue Tabellenzeile anhängen

Wenn Zielzeile geschützt ist:
- ignorieren

Späterer Einsatz für Wareneingangsprüfung

Für die geplante Wareneingangsprüfung soll die Funktion später im Merge-Modus genutzt werden.

Fachliche Regel:

Zeilen mit bereits vorhandener Lieferschein-/Wareneingangsnummer werden standardmäßig ignoriert.
Automatische Prüfung darf vorhandene manuelle Werte nicht überschreiben.
Überschreiben ist nur mit explizitem Parameter erlaubt.

Passende Konfiguration:

{
"scriptName": "sol.invoice.ix.dynkwl.PurchaseOrderLine",
"source": {
"maxLines": 100,
"lookupFields": [
"IX_MAP_PURCHASE_ORDER_NO"
],
"lookupFieldsOptional": [
"IX_MAP_PURCHASE_ORDER_DELIVERY_NO"
],
"focusField": "IX_MAP_PURCHASE_ORDER_NO",
"stopOnEmptyRequiredLookup": true
},
"target": {
"mode": "merge",
"appendNewRows": true,
"overwriteExisting": false,
"mergeKeyFields": [
"IX_MAP_PURCHASE_ORDER_NO",
"IX_MAP_PURCHASE_ORDER_LINE_NO",
"IX_MAP_PURCHASE_ORDER_DELIVERY_NO"
],
"ignoreTargetRowsWhere": [
{
"field": "IX_MAP_PURCHASE_ORDER_DELIVERY_NO",
"operator": "notEmpty"
}
]
},
"options": {
"dryRun": false,
"writeStatus": true,
"statusPrefix": "IX_MAP_GR_CHECK"
}
}

Wichtige Designentscheidung

Die neue Funktion soll sauberer aufgebaut werden als das alte Script.

Das alte Script mischt:

DynKwl-Aufruf
Datenumwandlung
Schreiblogik
Tabellenlogik

Die neue Funktion soll diese Bereiche trennen.

Geplante interne Struktur:

process()
loadSord()
loadMap()
collectLookupContexts()
callDynKwl()
convertDynKwlResultToRows()
applyRows()
writeStatus()

Geplante interne Methoden

process()

Hauptablauf.

Aufgaben:

- Konfiguration prüfen
- Sord laden
- Map lesen
- Lookup-Kontexte sammeln
- DynKwl pro Kontext aufrufen
- Ergebnisse sammeln
- Ergebniszeilen schreiben
- Status schreiben

collectLookupContexts(map, config)

Liest die Tabellenzeilen anhand der Lookup-Felder.

Beispiel aus Map:

PURCHASE_ORDER_NO1 = 45000123
PURCHASE_ORDER_DELIVERY_NO1 = leer

PURCHASE_ORDER_NO2 = 45000456
PURCHASE_ORDER_DELIVERY_NO2 = LS-7788

Ergebnis:

[
{
"sourceLine": 1,
"mapData": {
"IX_MAP_PURCHASE_ORDER_NO": "45000123",
"IX_MAP_PURCHASE_ORDER_DELIVERY_NO": ""
}
},
{
"sourceLine": 2,
"mapData": {
"IX_MAP_PURCHASE_ORDER_NO": "45000456",
"IX_MAP_PURCHASE_ORDER_DELIVERY_NO": "LS-7788"
}
}
]

callDynKwl(context, config)

Ruft die dynamische Stichwortliste auf.

Prinzip:

var keywordsDynamicInfo = new KeywordsDynamicInfo();

keywordsDynamicInfo.mapData = context.mapData;
keywordsDynamicInfo.mapLineFocus = config.source.focusField;
keywordsDynamicInfo.mapScriptName = config.scriptName;

var result = ixConnect.ix().checkoutKeywordsDynamic(keywordsDynamicInfo);

convertDynKwlResultToRows(result)

Wandelt das ELO-DynKwl-Ergebnis in ein sauberes Array aus Objekten um.

Beispiel:

[
{
"IX_MAP_PURCHASE_ORDER_NO": "45000123",
"IX_MAP_PURCHASE_ORDER_LINE_NO": "10",
"IX_MAP_PURCHASE_ORDER_DELIVERY_NO": "LS-1001",
"IX_MAP_PURCHASE_ORDER_ITEM_NO": "A-123",
"IX_MAP_PURCHASE_ORDER_QUANTITY": "5"
}
]

applyRows(rows, map, sord, config)

Verteilt je nach Modus:

append
overwrite
singleLine
merge

applyAppend(rows, map, sord, config)

Hängt Ergebniszeilen unten an die bestehende Tabelle an.

applyOverwrite(rows, map, sord, config)

Schreibt Ergebniszeilen ab einer Startzeile und überschreibt vorhandene Werte.

applySingleLine(rows, sourceLine, map, sord, config)

Schreibt Ergebniswerte in die Quellzeile.

applyMerge(rows, map, sord, config)

Späterer Modus für fachlichen Abgleich.

Aufgaben:

- bestehende Zielzeilen lesen
- Merge-Key je bestehender Zeile bilden
- Merge-Key je neuer Ergebniszeile bilden
- vorhandene Schlüssel überspringen oder überschreiben
- neue Schlüssel unten anhängen
- geschützte Zeilen ignorieren

Feldnamen-Normalisierung

ELO DynKwl arbeitet häufig mit Feldnamen wie:

IX_MAP_PURCHASE_ORDER_NO
IX_GRP_VENDOR_NO

Map-Felder werden aber intern oft ohne Prefix gelesen/geschrieben:

PURCHASE_ORDER_NO1

Daher brauchen wir Hilfsfunktionen.

normalizeFieldName(fieldName)

normalizeFieldName: function (fieldName) {
if (!fieldName) {
return "";
}

if ((fieldName.indexOf("IX_MAP_") === 0) || (fieldName.indexOf("IX_GRP_") === 0)) {
return fieldName.substring(7);
}

return fieldName;
}

getLineFieldName(fieldName, lineNo)

getLineFieldName: function (fieldName, lineNo) {
return this.normalizeFieldName(fieldName) + String(lineNo);
}

Beispiel:

IX_MAP_PURCHASE_ORDER_NO + 1
→ PURCHASE_ORDER_NO1

Merge-Key

Für den späteren Merge wird ein fachlicher Schlüssel gebildet.

Beispiel:

buildMergeKey: function (row, mergeKeyFields) {
var parts = [];

mergeKeyFields.forEach(function (fieldName) {
var value = row[fieldName] || "";
parts.push(String(value).trim());
});

return parts.join("|");
}

Beispiel-Ergebnis:

45000123|10|LS-1001

Wichtig:

Die Lieferanten-Lieferscheinnummer ist fachlich nützlich, aber technisch nicht immer eindeutig. Wenn möglich, sollte später ein robusterer Schlüssel verwendet werden:

Bestellnummer
Bestellposition
Wareneingangsbelegnummer
Wareneingangsposition

Falls diese Felder nicht verfügbar sind, kann zunächst gearbeitet werden mit:

Bestellnummer
Bestellposition
Lieferscheinnummer

Schutzlogik gegen Überschreiben

Für die spätere Wareneingangsprüfung gilt:

Wenn IX_MAP_PURCHASE_ORDER_DELIVERY_NOx bereits gefüllt ist,
darf diese Zeile standardmäßig nicht überschrieben werden.

Beispielkonfiguration:

"ignoreTargetRowsWhere": [
{
"field": "IX_MAP_PURCHASE_ORDER_DELIVERY_NO",
"operator": "notEmpty"
}
]

Bedeutung:

Alle Zielzeilen, bei denen PURCHASE_ORDER_DELIVERY_NOx nicht leer ist,
werden beim automatischen Merge ignoriert.

DryRun

Die Funktion soll einen DryRun-Modus unterstützen.

"options": {
"dryRun": true
}

Dann soll nichts geschrieben werden.

Stattdessen soll geloggt werden:

- welche Lookup-Zeilen gefunden wurden
- welche DynKwl aufgerufen wurde
- wie viele Ergebniszeilen zurückkamen
- welche Felder geschrieben würden
- welche Zeilen übersprungen würden
- welche Zeilen angehängt würden

DryRun ist wichtig für Tests im Kundensystem.

Statusfelder

Optional sollen Statusfelder geschrieben werden können.

Beispiel:

"options": {
"writeStatus": true,
"statusPrefix": "IX_MAP_DYNKWL_APPLY"
}

Mögliche Felder:

IX_MAP_DYNKWL_APPLY_STATUS
IX_MAP_DYNKWL_APPLY_MESSAGE
IX_MAP_DYNKWL_APPLY_DATE
IX_MAP_DYNKWL_APPLY_COUNT

Für WE-Prüfung später:

IX_MAP_GR_CHECK_STATUS
IX_MAP_GR_CHECK_MESSAGE
IX_MAP_GR_CHECK_DATE
IX_MAP_GR_CHECK_COUNT

Erste Umsetzungsphase

Nicht direkt alles bauen.

Version 1

Ziel: Saubere generische DynKwl-Ausführung.

Umfang:

- Namespace kit.common.ix.functions.applyDynKwlMap
- Workflow-Aufruf über onEnterNode/onExitNode
- RF-Aufruf RF_kit_function_applyDynKwlMap
- JSON-Parameter lesen
- Sord und Map lesen
- Lookup-Kontexte aus Tabellenzeilen sammeln
- DynKwl aufrufen
- Ergebnis in Rows umwandeln
- Modus append
- Modus overwrite
- Modus singleLine
- dryRun
- Logging

Noch nicht zwingend:

- vollständiger Merge-Modus
- WE-spezifische Logik
- komplexe Duplikatprüfung

Version 2

Ziel: Merge-Modus.

Umfang:

- bestehende Zielzeilen lesen
- Merge-Key bilden
- neue Ergebniszeilen gegen Bestand prüfen
- appendNewRows
- overwriteExisting
- ignoreTargetRowsWhere

Version 3

Ziel: Wareneingangsprüfung.

Umfang:

- Bestellung + Position + Lieferschein/WE als fachlicher Schlüssel
- Zeilen mit vorhandener Lieferscheinnummer ignorieren
- nur fehlende WE-Zeilen ergänzen
- kein Überschreiben ohne expliziten Parameter
- Status zur WE-Prüfung schreiben

Bekannte Probleme im alten Script

Das alte Script sollte nicht direkt weiter ausgebaut werden, ohne es zu bereinigen.

Auffälligkeiten:

- DynKwl-Abfrage und Schreiben sind vermischt
- fillMap schreibt direkt per checkinMap
- dirty wird nicht sauber zurückgegeben
- RF-Funktion erzeugt vermutlich falschen Klassennamen
- maxLines ist nicht sauber abgesichert
- focusField und scriptName sollten required sein
- optionale Lookup-Felder sollten aktiv geleert werden, wenn leer
- append-Modus prüft keine Duplikate

Daher neue Implementierung unter KIT-Namespace statt Änderung am alten Script.

Zielbild

Am Ende soll es zwei wiederverwendbare Bausteine geben:

1. kit.common.ix.functions.applyDynKwlMap
Generischer Baustein:
DynKwl aufrufen und Ergebnisse kontrolliert in Map-Tabellen schreiben.

2. spätere WE-Prüffunktion
Fachlicher Baustein:
Wareneingänge prüfen, vorhandene Zeilen analysieren, neue WE-Zeilen mergen.

Die WE-Prüffunktion soll später möglichst denselben Datenlieferanten verwenden und nicht nochmal eigene DB-Abfragen duplizieren.

Wichtigster Grundsatz

Die automatische Verarbeitung muss nicht-destruktiv sein.

Standard:

Keine vorhandenen Lieferschein-/Wareneingangszeilen überschreiben.
Keine manuellen Korrekturen zerstören.
Neue Daten nur ergänzen.
Überschreiben nur über expliziten Parameter.