Erster Commit
This commit is contained in:
25
README.md
Normal file
25
README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# ERP EDI Bridge - Release v1.0
|
||||
|
||||
Dies ist ein eigenständiges Tool zur Konvertierung von EDI-Nachrichten zwischen VDA und EDIFACT Formaten. Es basiert auf reinem JavaScript und benötigt **keine** Installation oder Webserver.
|
||||
|
||||
## Funktionen
|
||||
1. **Outbound**: VDA 4913 (Lieferschein) → EDIFACT DESADV (D:01B).
|
||||
- Inklusive Datenanreicherung (Gewichte, Maße, Verpackung).
|
||||
2. **Inbound**: EDIFACT DELFOR (D:04A) → VDA 4905 (Lieferabruf).
|
||||
- Präzise Umsetzung der Records 511 bis 519.
|
||||
|
||||
## Installation / Nutzung
|
||||
1. Entpacken Sie das gesamte Verzeichnis auf Ihren Rechner.
|
||||
2. Öffnen Sie die Datei `index.html` in einem modernen Webbrowser (Chrome, Edge, Firefox).
|
||||
3. Wählen Sie den gewünschten Modus (Inbound/Outbound).
|
||||
4. Ziehen Sie eine Datei in das Upload-Feld oder klicken Sie darauf.
|
||||
|
||||
## Konfiguration
|
||||
Sie können Standardwerte für Verpackungen und NAD-Partner in einer Konfigurationsdatei (.ini) hinterlegen. Laden Sie diese über den "Konfig laden" Button hoch, um die manuelle Eingabe zu minimieren.
|
||||
|
||||
## Systemvoraussetzungen
|
||||
- Ein aktueller Webbrowser (Chrome, Edge, Safari oder Firefox).
|
||||
- Internetverbindung (nur zum Laden der Lucide-Icons beim ersten Start, danach optional).
|
||||
|
||||
---
|
||||
Entwickelt für Roechling Precision Components.
|
||||
21
check_encoding.js
Normal file
21
check_encoding.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const fs = require('fs');
|
||||
const filePath = 'c:\\Users\\matthias.hoesch\\.gemini\\antigravity\\scratch\\vda-to-edifact-converter\\samples\\test_efz.edi';
|
||||
const buffer = fs.readFileSync(filePath);
|
||||
|
||||
console.log('File size:', buffer.length);
|
||||
|
||||
const searchStr = 'Precision';
|
||||
const searchBuf = Buffer.from(searchStr, 'utf8');
|
||||
|
||||
let index = buffer.indexOf(searchBuf);
|
||||
if (index !== -1) {
|
||||
console.log('Found "' + searchStr + '" at byte offset:', index);
|
||||
const start = Math.max(0, index - 30);
|
||||
const end = Math.min(buffer.length, index + 40);
|
||||
const context = buffer.slice(start, end);
|
||||
console.log('Context (hex):', context.toString('hex'));
|
||||
console.log('Context (utf8):', context.toString('utf8'));
|
||||
console.log('Context (latin1):', context.toString('latin1'));
|
||||
} else {
|
||||
console.log('"' + searchStr + '" not found in file.');
|
||||
}
|
||||
BIN
db/Cache/Cache_Data/data_0
Normal file
BIN
db/Cache/Cache_Data/data_0
Normal file
Binary file not shown.
BIN
db/Cache/Cache_Data/data_1
Normal file
BIN
db/Cache/Cache_Data/data_1
Normal file
Binary file not shown.
BIN
db/Cache/Cache_Data/data_2
Normal file
BIN
db/Cache/Cache_Data/data_2
Normal file
Binary file not shown.
BIN
db/Cache/Cache_Data/data_3
Normal file
BIN
db/Cache/Cache_Data/data_3
Normal file
Binary file not shown.
BIN
db/Cache/Cache_Data/f_000001
Normal file
BIN
db/Cache/Cache_Data/f_000001
Normal file
Binary file not shown.
BIN
db/Cache/Cache_Data/f_000002
Normal file
BIN
db/Cache/Cache_Data/f_000002
Normal file
Binary file not shown.
BIN
db/Cache/Cache_Data/f_000003
Normal file
BIN
db/Cache/Cache_Data/f_000003
Normal file
Binary file not shown.
BIN
db/Cache/Cache_Data/f_000004
Normal file
BIN
db/Cache/Cache_Data/f_000004
Normal file
Binary file not shown.
BIN
db/Cache/Cache_Data/f_000005
Normal file
BIN
db/Cache/Cache_Data/f_000005
Normal file
Binary file not shown.
BIN
db/Cache/Cache_Data/f_000006
Normal file
BIN
db/Cache/Cache_Data/f_000006
Normal file
Binary file not shown.
BIN
db/Cache/Cache_Data/index
Normal file
BIN
db/Cache/Cache_Data/index
Normal file
Binary file not shown.
BIN
db/Code Cache/js/index
Normal file
BIN
db/Code Cache/js/index
Normal file
Binary file not shown.
BIN
db/Code Cache/js/index-dir/the-real-index
Normal file
BIN
db/Code Cache/js/index-dir/the-real-index
Normal file
Binary file not shown.
BIN
db/Code Cache/wasm/index
Normal file
BIN
db/Code Cache/wasm/index
Normal file
Binary file not shown.
BIN
db/Code Cache/wasm/index-dir/the-real-index
Normal file
BIN
db/Code Cache/wasm/index-dir/the-real-index
Normal file
Binary file not shown.
BIN
db/DawnCache/data_0
Normal file
BIN
db/DawnCache/data_0
Normal file
Binary file not shown.
BIN
db/DawnCache/data_1
Normal file
BIN
db/DawnCache/data_1
Normal file
Binary file not shown.
BIN
db/DawnCache/data_2
Normal file
BIN
db/DawnCache/data_2
Normal file
Binary file not shown.
BIN
db/DawnCache/data_3
Normal file
BIN
db/DawnCache/data_3
Normal file
Binary file not shown.
BIN
db/DawnCache/index
Normal file
BIN
db/DawnCache/index
Normal file
Binary file not shown.
BIN
db/GPUCache/data_0
Normal file
BIN
db/GPUCache/data_0
Normal file
Binary file not shown.
BIN
db/GPUCache/data_1
Normal file
BIN
db/GPUCache/data_1
Normal file
Binary file not shown.
BIN
db/GPUCache/data_2
Normal file
BIN
db/GPUCache/data_2
Normal file
Binary file not shown.
BIN
db/GPUCache/data_3
Normal file
BIN
db/GPUCache/data_3
Normal file
Binary file not shown.
BIN
db/GPUCache/index
Normal file
BIN
db/GPUCache/index
Normal file
Binary file not shown.
1
db/Local State
Normal file
1
db/Local State
Normal file
@@ -0,0 +1 @@
|
||||
{"os_crypt":{"audit_enabled":true,"encrypted_key":"RFBBUEkBAAAA0Iyd3wEV0RGMegDAT8KX6wEAAADlg9LsmyQgSas6BLrKsuNKEAAAABIAAABDAGgAcgBvAG0AaQB1AG0AAAADZgAAwAAAABAAAABW5H24fwlGzdIMuEZMgPO1AAAAAASAAACgAAAAEAAAABXpSj0+k5Z5x/lVB1S2EFooAAAAOTpEM4uLHkoAPs0kM30IwQMAcT4uTAhxKOZJ8UK4UpO4ujpesN41kRQAAACqOTDi6xE58RHtBNdRHsjAHKOn/w=="}}
|
||||
BIN
db/Local Storage/leveldb/000003.log
Normal file
BIN
db/Local Storage/leveldb/000003.log
Normal file
Binary file not shown.
1
db/Local Storage/leveldb/CURRENT
Normal file
1
db/Local Storage/leveldb/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
MANIFEST-000001
|
||||
0
db/Local Storage/leveldb/LOCK
Normal file
0
db/Local Storage/leveldb/LOCK
Normal file
3
db/Local Storage/leveldb/LOG
Normal file
3
db/Local Storage/leveldb/LOG
Normal file
@@ -0,0 +1,3 @@
|
||||
2026/03/06-09:44:41.488 ad00 Reusing MANIFEST C:\Users\matthias.hoesch\AppData\Roaming\erp-edi-bridge\Local Storage\leveldb/MANIFEST-000001
|
||||
2026/03/06-09:44:41.492 ad00 Recovering log #3
|
||||
2026/03/06-09:44:41.493 ad00 Reusing old log C:\Users\matthias.hoesch\AppData\Roaming\erp-edi-bridge\Local Storage\leveldb/000003.log
|
||||
3
db/Local Storage/leveldb/LOG.old
Normal file
3
db/Local Storage/leveldb/LOG.old
Normal file
@@ -0,0 +1,3 @@
|
||||
2026/03/06-09:33:25.928 abe4 Reusing MANIFEST C:\Users\matthias.hoesch\AppData\Roaming\erp-edi-bridge\Local Storage\leveldb/MANIFEST-000001
|
||||
2026/03/06-09:33:25.932 abe4 Recovering log #3
|
||||
2026/03/06-09:33:25.933 abe4 Reusing old log C:\Users\matthias.hoesch\AppData\Roaming\erp-edi-bridge\Local Storage\leveldb/000003.log
|
||||
BIN
db/Local Storage/leveldb/MANIFEST-000001
Normal file
BIN
db/Local Storage/leveldb/MANIFEST-000001
Normal file
Binary file not shown.
BIN
db/Network/Cookies
Normal file
BIN
db/Network/Cookies
Normal file
Binary file not shown.
0
db/Network/Cookies-journal
Normal file
0
db/Network/Cookies-journal
Normal file
1
db/Network/Network Persistent State
Normal file
1
db/Network/Network Persistent State
Normal file
@@ -0,0 +1 @@
|
||||
{"net":{"http_server_properties":{"servers":[{"alternative_service":[{"advertised_alpns":["h3"],"expiration":"13419534887043544","port":443,"protocol_str":"quic"}],"anonymization":[],"network_stats":{"srtt":61977},"server":"https://fonts.googleapis.com","supports_spdy":true},{"alternative_service":[{"advertised_alpns":["h3"],"expiration":"13418565220503347","port":443,"protocol_str":"quic"}],"anonymization":[],"network_stats":{"srtt":29484},"server":"https://fonts.gstatic.com"},{"alternative_service":[{"advertised_alpns":["h3"],"expiration":"13417346682105501","port":443,"protocol_str":"quic"}],"anonymization":[],"network_stats":{"srtt":44276},"server":"https://unpkg.com"}],"supports_quic":{"address":"10.22.20.55","used_quic":true},"version":5},"network_qualities":{"CAESABiAgICA+P////8B":"4G","CAYSABiAgICA+P////8B":"Offline"}}}
|
||||
0
db/Network/NetworkDataMigrated
Normal file
0
db/Network/NetworkDataMigrated
Normal file
1
db/Network/TransportSecurity
Normal file
1
db/Network/TransportSecurity
Normal file
@@ -0,0 +1 @@
|
||||
{"sts":[{"expiry":1804322682.11163,"host":"e3SziuwfuO2UvuBno+qkR1ObHAzZmSUoJhrc7dbP1Uo=","mode":"force-https","sts_include_subdomains":true,"sts_observed":1772786682.11164},{"expiry":1804321887.908826,"host":"nAuqgR4iEWti7SOdT3UHPl6rmZU/DeaIm38P2O2OkgA=","mode":"force-https","sts_include_subdomains":false,"sts_observed":1772785887.908828}],"version":2}
|
||||
BIN
db/Network/Trust Tokens
Normal file
BIN
db/Network/Trust Tokens
Normal file
Binary file not shown.
0
db/Network/Trust Tokens-journal
Normal file
0
db/Network/Trust Tokens-journal
Normal file
1
db/Preferences
Normal file
1
db/Preferences
Normal file
@@ -0,0 +1 @@
|
||||
{"spellcheck":{"dictionaries":["de"],"dictionary":""}}
|
||||
BIN
db/Session Storage/000003.log
Normal file
BIN
db/Session Storage/000003.log
Normal file
Binary file not shown.
1
db/Session Storage/CURRENT
Normal file
1
db/Session Storage/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
MANIFEST-000001
|
||||
0
db/Session Storage/LOCK
Normal file
0
db/Session Storage/LOCK
Normal file
3
db/Session Storage/LOG
Normal file
3
db/Session Storage/LOG
Normal file
@@ -0,0 +1,3 @@
|
||||
2026/03/06-09:45:20.654 ad00 Reusing MANIFEST C:\Users\matthias.hoesch\AppData\Roaming\erp-edi-bridge\Session Storage/MANIFEST-000001
|
||||
2026/03/06-09:45:20.656 ad00 Recovering log #3
|
||||
2026/03/06-09:45:20.657 ad00 Reusing old log C:\Users\matthias.hoesch\AppData\Roaming\erp-edi-bridge\Session Storage/000003.log
|
||||
3
db/Session Storage/LOG.old
Normal file
3
db/Session Storage/LOG.old
Normal file
@@ -0,0 +1,3 @@
|
||||
2026/03/06-09:44:37.429 abe4 Reusing MANIFEST C:\Users\matthias.hoesch\AppData\Roaming\erp-edi-bridge\Session Storage/MANIFEST-000001
|
||||
2026/03/06-09:44:37.430 abe4 Recovering log #3
|
||||
2026/03/06-09:44:37.431 abe4 Reusing old log C:\Users\matthias.hoesch\AppData\Roaming\erp-edi-bridge\Session Storage/000003.log
|
||||
BIN
db/Session Storage/MANIFEST-000001
Normal file
BIN
db/Session Storage/MANIFEST-000001
Normal file
Binary file not shown.
BIN
db/Shared Dictionary/cache/index
vendored
Normal file
BIN
db/Shared Dictionary/cache/index
vendored
Normal file
Binary file not shown.
BIN
db/Shared Dictionary/cache/index-dir/the-real-index
vendored
Normal file
BIN
db/Shared Dictionary/cache/index-dir/the-real-index
vendored
Normal file
Binary file not shown.
BIN
db/Shared Dictionary/db
Normal file
BIN
db/Shared Dictionary/db
Normal file
Binary file not shown.
0
db/Shared Dictionary/db-journal
Normal file
0
db/Shared Dictionary/db-journal
Normal file
0
db/SharedStorage
Normal file
0
db/SharedStorage
Normal file
BIN
db/conversions.sqlite3
Normal file
BIN
db/conversions.sqlite3
Normal file
Binary file not shown.
BIN
db/conversions.sqlite3-shm
Normal file
BIN
db/conversions.sqlite3-shm
Normal file
Binary file not shown.
0
db/conversions.sqlite3-wal
Normal file
0
db/conversions.sqlite3-wal
Normal file
5
db/watcher-settings.json
Normal file
5
db/watcher-settings.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"inputDir": "\\\\adewbgsvpas03\\Wincarat\\dfue\\LAB\\DELFOR\\ZF",
|
||||
"outputDir": "\\\\adewbgsvpas03\\Wincarat\\dfue\\LAB",
|
||||
"mode": "auto"
|
||||
}
|
||||
12
debug_pos.js
Normal file
12
debug_pos.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const fs = require('fs');
|
||||
const filePath = 'c:\\Users\\matthias.hoesch\\.gemini\\antigravity\\scratch\\vda-to-edifact-converter\\samples\\test_efz.edi';
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const normalized = content.replace(/\r\n/g, '').replace(/\r/g, '').trim();
|
||||
|
||||
console.log('Position 179 char:', normalized[179]);
|
||||
console.log('Context (170-190):', normalized.substring(170, 190));
|
||||
console.log('Context (hex):', Buffer.from(normalized.substring(170, 190), 'utf8').toString('hex'));
|
||||
|
||||
const searchStr = 'Precision';
|
||||
const idx = normalized.indexOf(searchStr);
|
||||
console.log('Precision found at char index:', idx);
|
||||
BIN
docs/AUMOVIO_Guideline_DELFOR_04A.pdf
Normal file
BIN
docs/AUMOVIO_Guideline_DELFOR_04A.pdf
Normal file
Binary file not shown.
19844
docs/AUMOVIO_Guideline_DESADV_07A.pdf
Normal file
19844
docs/AUMOVIO_Guideline_DESADV_07A.pdf
Normal file
File diff suppressed because it is too large
Load Diff
BIN
docs/AUMOVIO_Guideline_INVRPT_13A.pdf
Normal file
BIN
docs/AUMOVIO_Guideline_INVRPT_13A.pdf
Normal file
Binary file not shown.
BIN
docs/AUMOVIO_Sample_CMI_EDLNOT.pdf
Normal file
BIN
docs/AUMOVIO_Sample_CMI_EDLNOT.pdf
Normal file
Binary file not shown.
BIN
docs/AUMOVIO_Sample_DELFOR_04A.pdf
Normal file
BIN
docs/AUMOVIO_Sample_DELFOR_04A.pdf
Normal file
Binary file not shown.
BIN
docs/AUMOVIO_Sample_DESADV_07A_Packaging.pdf
Normal file
BIN
docs/AUMOVIO_Sample_DESADV_07A_Packaging.pdf
Normal file
Binary file not shown.
BIN
docs/BSOCH_delfor-d-04a_v10_iso9735.pdf
Normal file
BIN
docs/BSOCH_delfor-d-04a_v10_iso9735.pdf
Normal file
Binary file not shown.
3186
docs/BSOCH_delfor-d-04a_v10_iso9735.txt
Normal file
3186
docs/BSOCH_delfor-d-04a_v10_iso9735.txt
Normal file
File diff suppressed because it is too large
Load Diff
BIN
docs/Bosch_global-desadv-d-07a_v19_iso9735.pdf
Normal file
BIN
docs/Bosch_global-desadv-d-07a_v19_iso9735.pdf
Normal file
Binary file not shown.
BIN
docs/VDA_4913_DE_passworded.pdf
Normal file
BIN
docs/VDA_4913_DE_passworded.pdf
Normal file
Binary file not shown.
2561
docs/VDA_4913_DE_passworded.txt
Normal file
2561
docs/VDA_4913_DE_passworded.txt
Normal file
File diff suppressed because it is too large
Load Diff
BIN
docs/ZF Global DESADV 1.3.9 EN Examples.pdf
Normal file
BIN
docs/ZF Global DESADV 1.3.9 EN Examples.pdf
Normal file
Binary file not shown.
BIN
docs/ZF Global DESADV 1.3.9 EN.pdf
Normal file
BIN
docs/ZF Global DESADV 1.3.9 EN.pdf
Normal file
Binary file not shown.
11602
docs/aumovio_desadv_guideline.txt
Normal file
11602
docs/aumovio_desadv_guideline.txt
Normal file
File diff suppressed because it is too large
Load Diff
1739
docs/aumovio_guideline_invrpt.txt
Normal file
1739
docs/aumovio_guideline_invrpt.txt
Normal file
File diff suppressed because it is too large
Load Diff
77
docs/changelog.md
Normal file
77
docs/changelog.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Changelog - 26.02.2026
|
||||
|
||||
## Neue Funktionen: Profi-Viewer (VDA 4913) & Direkt-Vorschau
|
||||
|
||||
Heute wurde der EDI Viewer massiv aufgewertet, um VDA 4913 Lieferbelege in professioneller Form darzustellen und den Workflow zwischen Konverter und Viewer zu optimieren.
|
||||
|
||||
### 1. VDA 4913 Professional Viewer
|
||||
- **Neues Layout**: Strukturierte Darstellung als "echter" Lieferschein mit Absender- und Empfänger-Adressblöcken.
|
||||
- **Transport-Daten**: Übersichtliche Anzeige von Spediteur, Gewichten (Brutto/Netto) und Referenznummern.
|
||||
- **Positions- & Packstück-Tabelle**:
|
||||
- Optimierte Darstellung der Artikelpositionen (Sachnummern, Mengen, A-Stände).
|
||||
- Detaillierte Auflistung der Packmittel und zugehörigen SSCCs/Packstücknummern.
|
||||
- **Print-Optimierung**: Verbessertes PDF-Drucklayout (Druckdatum, Dokumententyp im Footer, Ausblendung von UI-Elementen).
|
||||
|
||||
### 2. Konverter-Integration ("View" Button)
|
||||
- **Direkt-Link**: In den Konvertierungsergebnissen wurde ein "View"-Button neben dem Download-Button ergänzt.
|
||||
- **Nahtloser Übergang**: Das Ergebnis kann sofort im professionellen Viewer betrachtet werden, ohne die Datei vorher lokal speichern und wieder hochladen zu müssen.
|
||||
|
||||
### 3. Dokumentation & Mapping
|
||||
- **Mapping-Übersicht**: Erstellung einer detaillierten Dokumentation (`docs/mapping_overview.md`), die das Mapping von VDA 4913 zu Bosch DESADV sowie die Anreicherung durch Konfigurationsdaten (NAD, Gewichte, Maße) beschreibt.
|
||||
- **UI-UX**: Automatische Re-Initialisierung von Lucide-Icons bei dynamisch generierten Inhalten im Viewer.
|
||||
|
||||
---
|
||||
|
||||
# Changelog - 25.02.2026
|
||||
|
||||
## Neue Funktion: INVRPT (EDIFACT) zu VDA 4913 (EDL36/35) Konverter
|
||||
|
||||
Heute wurde das System um eine Konvertierungsfunktion für Bestandsberichte (INVRPT) erweitert.
|
||||
|
||||
### 1. INVRPT D13A Unterstützung
|
||||
- **Neues Modul**: `invrpt-to-vda4913.js` parst EDIFACT INVRPT Nachrichten und erstellt daraus VDA 4913 Format.
|
||||
- **VDA 4913 Mapping**:
|
||||
- Konvertierung in EDL36 (Lieferschein/Transport) und EDL35 (Bestandsmeldung) Datensätze (711, 712, 713, 714, 719).
|
||||
- Berücksichtigung von Mengen (QTY+156 für Bestand vs. QTY+145 für Kumulativ).
|
||||
- Längenkorrekturen und exaktes Padding für das 128-Zeichen VDA-Format.
|
||||
|
||||
### 2. UI-Integration
|
||||
- **Neuer Konvertierungs-Modus**: "INVRPT → VDA 4913" im User Interface wählbar.
|
||||
- **Automatische Erkennung**: Das System erkennt beim Drop einer INVRPT-Datei den korrekten Modus automatisch.
|
||||
- **Mapping-Verwaltung**: Die Kundennummern-Zuordnung unterstützt nun auch den neuen INVRPT-Modus.
|
||||
|
||||
---
|
||||
|
||||
# Changelog - 23.02.2026
|
||||
|
||||
## Neue Funktionen: EDI Viewer & VDA 4905 Integration
|
||||
|
||||
Heute wurde das System um eine leistungsfähige Viewer-Komponente erweitert, die sowohl EDIFACT- als auch VDA-Dokumente für den Anwender grafisch aufbereitet.
|
||||
|
||||
### 1. EDI Viewer (Allgemein)
|
||||
- **Drag & Drop Schnittstelle**: Dateien können einfach per Maus in den Viewer gezogen werden.
|
||||
- **Druck-Optimierung**: Ein spezielles Print-Layout ermöglicht den sauberen Export als PDF (ähnlich dem klassischen "Lieferplan").
|
||||
- **Benutzerführung**: Ergänzung eines "Zurück"-Buttons zum schnellen Wechsel zwischen Dateien und moderneres Design der Oberfläche.
|
||||
|
||||
### 2. DELFOR (EDIFACT) Unterstützung
|
||||
- **Header-Extraktion**: Auslesen von Belegnummern, Datum sowie Käufer-, Verkäufer- und Warenempfängerdaten (NAD).
|
||||
- **IFM-Spezifika**:
|
||||
- Unterstützung der Qualifier `QTY+194` (Letzte Lieferungen) und `QTY+70/71` (Eingangsfortschrittszahl).
|
||||
- Verknüpfung von Lieferscheinnummern (RFF+AAU) mit den jeweiligen Mengen.
|
||||
- Auslesen der Null-Stellung (DTM+51).
|
||||
|
||||
### 3. VDA 4905 (Lieferabruf) Unterstützung
|
||||
- **Erweiterter Parser**: Vollständige Implementierung der Sätze 511, 512, 513, 514 und 519 im `vda-parser.js`.
|
||||
- **Zusätzliche Felder**: Anzeige von alten und neuen Abrufnummern sowie der Werknummer (Satz 512).
|
||||
- **Präzisions-Steuerung**:
|
||||
- Mengen bei Abrufen werden als ganze Stückzahlen (Integer) behandelt.
|
||||
- Felder wie "Letzte Liefermenge" und "EFZ" werden korrekt mit 3 impliziten Nachkommastellen (Division durch 1000) verarbeitet.
|
||||
|
||||
### 4. Daten-Visualisierung & Logik
|
||||
- **Dynamische Zeiträume**: Intelligente Erkennung und Formatierung von Abrufterminen:
|
||||
- **Tagesgenau**: (TT.MM.JJJJ)
|
||||
- **Wochen-Format**: Anzeige als Zeitspanne inkl. Kalenderwoche (z.B. `W 25.05.26 - 31.05.26 (KW: 22)`).
|
||||
- **Monats-Format**: Anzeige als Monat inkl. Monatsnummer (z.B. `M 01.03.27 - 31.03.27 (MO: 03)`).
|
||||
- **Summenbildung**: Automatische Errechnung und Anzeige von **Monatssummen** und **Kumulativen Mengen** innerhalb der Abruftabelle.
|
||||
- **Lokalisierung**: Alle numerischen Werte nutzen das deutsche Format (Punkt für Tausender, Komma für Dezimalstellen).
|
||||
- **Rundungs-Korrektur**: Behebung von JavaScript-Fließkommafehlern bei der Aufsummierung von Mengen.
|
||||
BIN
docs/ifm_electronic_DELFOR_D04A_070524.pdf
Normal file
BIN
docs/ifm_electronic_DELFOR_D04A_070524.pdf
Normal file
Binary file not shown.
BIN
docs/ifm_electronic_DELVRY03_300620.pdf
Normal file
BIN
docs/ifm_electronic_DELVRY03_300620.pdf
Normal file
Binary file not shown.
101
docs/implementation_plan.md
Normal file
101
docs/implementation_plan.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# ERP EDI Bridge – Erweiterung: IFM-Flows & Ordnerüberwachung
|
||||
|
||||
Erweiterung der bestehenden Browser-App um:
|
||||
1. **IFM-spezifische Converter** in der UI verfügbar machen (Inbound: IFM DELFOR → VDA 4905, Outbound: VDA 4913 → IFM DESADV / DELVRY03)
|
||||
2. **Settings-Seite** mit Input-/Output-Ordner Konfiguration
|
||||
3. **Ordnerüberwachung** (Folder Watch) mit Pause/Resume
|
||||
|
||||
## User Review Required
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Architektur-Entscheidung: Ordnerüberwachung**
|
||||
>
|
||||
> Die aktuelle App läuft als reine HTML/JS-Datei im Browser. Ein echter File-System-Watcher (der einen Netzwerkordner auf neue Dateien überwacht und automatisch konvertiert) ist im Browser **nicht** möglich.
|
||||
>
|
||||
> **Vorgeschlagene Lösung:** Umstellung auf eine **Electron**-App. Damit kann die App:
|
||||
> - Netzwerkordner per `fs.watch()` / `chokidar` überwachen
|
||||
> - Dateien automatisch lesen und konvertieren
|
||||
> - Ergebnisse direkt ins Dateisystem schreiben
|
||||
>
|
||||
> Die bestehende HTML/CSS/JS-Architektur bleibt dabei größtenteils erhalten – Electron wrapped sie nur.
|
||||
>
|
||||
> **Alternative:** Falls kein Electron gewünscht, könnte man einen **kleinen Node.js-Hintergrundprozess** implementieren, der separat gestartet wird. Die Browser-UI würde dann über WebSocket mit dem Watcher kommunizieren.
|
||||
>
|
||||
> **Welche Variante bevorzugen Sie?**
|
||||
|
||||
> [!WARNING]
|
||||
> Die Datei `vda4913-to-delvry03.js.js` hat einen doppelten `.js.js` Dateinamen – soll das korrigiert werden?
|
||||
|
||||
## Proposed Changes
|
||||
|
||||
### Navigation & Seitenstruktur
|
||||
|
||||
Die Single-Page-App bekommt eine einfache Tab-Navigation am oberen Rand:
|
||||
- **Converter** (bestehende Hauptseite)
|
||||
- **Settings** (neue Seite für Ordnerkonfiguration & Watcher-Status)
|
||||
|
||||
#### [MODIFY] [index.html](file:///c:/Users/Zed/vda-to-edifact-converter/index.html)
|
||||
- Navigation-Tabs hinzufügen (`<nav>` mit Converter / Settings)
|
||||
- Mode-Toggle erweitern: 4 Modi statt 2
|
||||
- VDA 4913 → Bosch DESADV (Outbound)
|
||||
- VDA 4913 → IFM DESADV (Outbound)
|
||||
- Bosch DELFOR → VDA 4905 (Inbound)
|
||||
- IFM DELFOR → VDA 4905 (Inbound)
|
||||
- Settings-Seite HTML hinzufügen (Input-Ordner, Output-Ordner, Watcher-Buttons, Log-Bereich)
|
||||
- Script-Tags für neue Module ergänzen
|
||||
|
||||
---
|
||||
|
||||
### Converter-Erweiterung
|
||||
|
||||
#### [MODIFY] [app.js](file:///c:/Users/Zed/vda-to-edifact-converter/js/app.js)
|
||||
- `setMode()` erweitern für 4 Modi: `outbound-bosch`, `outbound-ifm`, `inbound-bosch`, `inbound-ifm`
|
||||
- `processInbound()` erweitern: Bei IFM-Mode den `DelforToVDA4905Converter` nutzen statt den generischen Parser
|
||||
- `processOutbound()` bleibt für Bosch; neuer Flow für IFM Outbound (→ DELVRY03/DESADV)
|
||||
- `generateOutput()` erweitern für IFM-Outbound
|
||||
- Automatische Dateierkennung anpassen (IFM DELFOR erkennen vs. Bosch DELFOR)
|
||||
|
||||
---
|
||||
|
||||
### Ordnerüberwachung (Electron-Variante)
|
||||
|
||||
#### [NEW] [main.js](file:///c:/Users/Zed/vda-to-edifact-converter/main.js)
|
||||
- Electron Main-Prozess: Fenster erstellen, IPC-Handler registrieren
|
||||
- `fs.watch()` oder `chokidar`-basierter Folder-Watcher
|
||||
- IPC-Channels: `select-folder`, `start-watcher`, `stop-watcher`, `watcher-status`, `file-converted`
|
||||
|
||||
#### [NEW] [js/watcher-bridge.js](file:///c:/Users/Zed/vda-to-edifact-converter/js/watcher-bridge.js)
|
||||
- Renderer-seitiger Code der per `ipcRenderer` mit dem Main-Prozess kommuniziert
|
||||
- Stellt Watcher-Status und Konvertierungs-Log in der UI dar
|
||||
|
||||
#### [NEW] [preload.js](file:///c:/Users/Zed/vda-to-edifact-converter/preload.js)
|
||||
- Electron Preload-Script: sichere Brücke zwischen Main/Renderer Prozess via `contextBridge`
|
||||
|
||||
#### [NEW] [package.json](file:///c:/Users/Zed/vda-to-edifact-converter/package.json)
|
||||
- Electron + chokidar als Dependencies
|
||||
- Start-Script: `electron .`
|
||||
|
||||
---
|
||||
|
||||
### Styling
|
||||
|
||||
#### [MODIFY] [styles.css](file:///c:/Users/Zed/vda-to-edifact-converter/styles.css)
|
||||
- Styles für Tab-Navigation
|
||||
- Styles für Settings-Seite (Ordner-Inputs, Watcher-Status-Anzeige, Log-Container)
|
||||
- Styles für 4-Spalten Mode-Toggle (responsive)
|
||||
- Watcher-Status Badge (Aktiv/Pausiert/Gestoppt)
|
||||
|
||||
---
|
||||
|
||||
## Verification Plan
|
||||
|
||||
### Manuelle Tests (im Browser / Electron)
|
||||
|
||||
1. **Tab-Navigation**: Converter ↔ Settings wechseln – beide Seiten korrekt angezeigt
|
||||
2. **Mode-Toggle**: Alle 4 Modi nacheinander auswählen
|
||||
3. **Bosch Outbound**: VDA 4913 Datei hochladen → DESADV generieren (Bestandsverhalten, Regression)
|
||||
4. **IFM Inbound**: IFM DELFOR Datei hochladen → VDA 4905 erzeugen (mit `ifm_delfor-vda4905.js`)
|
||||
5. **IFM Outbound**: VDA 4913 Datei hochladen → IFM DELVRY03 erzeugen
|
||||
6. **Settings**: Input/Output-Ordner eingeben, Watcher starten/pausieren
|
||||
7. **Watcher**: Datei in Input-Ordner legen → automatische Konvertierung → Ergebnis im Output-Ordner prüfen
|
||||
8. **Pause/Resume**: Watcher pausieren → Datei einlegen → keine Konvertierung → Resume → Konvertierung findet statt
|
||||
58
docs/mapping_overview.md
Normal file
58
docs/mapping_overview.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Mapping Overview: VDA 4913 to Bosch DESADV
|
||||
|
||||
This document provides a detailed overview of how data fields from the VDA 4913 (Delivery Note) are mapped to the Bosch DESADV (D.07A GMI022) format, including specifically which values are enriched via the application configuration.
|
||||
|
||||
## 1. Transmission & Dates
|
||||
| VDA 4913 Field | DESADV Segment | Mapping / Logic | Enrichment |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| 711.04 (Lieferschein-Nr) | BGM+351 | Document Number | - |
|
||||
| 711.07 (Datum) | DTM+137/132/10 | Message/Delivery/Despatch Date | - |
|
||||
| 711.11 (Sendungs-Nr) | RFF+AAS | Shipping Reference | - |
|
||||
| - | DTM+36 | **Expiry Date** | **Dynamic**: Calculated as +1 year from delivery date. |
|
||||
|
||||
## 2. Participating Parties (NAD)
|
||||
Parties are primarily identified by their IDs in the VDA file, but all detailed address information is **enriched via configuration**.
|
||||
|
||||
| Party | VDA ID Source | Config Section | Enriched Fields |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Buyer** (BY) | 711.05 (Kunde) | `[NAD_BY_...]` | Name, Street, City, Zip, Country |
|
||||
| **Seller** (SE) | 711.03 (Lieferant) | `[NAD_SE_...]` | Name, Street, City, Zip, Country |
|
||||
| **Ship To** (ST) | 711.05 (Kunde) | `[NAD_ST_...]` | Name, Street, City, Zip, Country |
|
||||
| **Manufacturer** (MF)| 711.03 (Lieferant) | - | Party ID (Qual 92) |
|
||||
|
||||
> [!NOTE]
|
||||
> Address enrichment uses the Partner ID from the VDA file as a lookup key in the config sections.
|
||||
|
||||
## 3. Items & Quantities
|
||||
| VDA 4913 Field | DESADV Segment | Mapping / Logic | Enrichment |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| 713.04 (Sach-Nr Kunde) | LIN | Customer Material No | - |
|
||||
| 713.06 (Sach-Nr Lief.) | PIA+5 | Supplier Material No | - |
|
||||
| 713.05 (A-Stand) | PIA (EC) | Engineering Revision Level | - |
|
||||
| 713.09 (Liefermenge) | QTY+12 | Despatched Quantity | - |
|
||||
| 713.10 (ME) | QTY+12 (Unit) | Mapped (e.g., ST -> EA, KG -> KGM) | - |
|
||||
| 711.06 / 712.04 | RFF+ON | Purchase Order Number | - |
|
||||
| 711.13 | LOC+11 | Unloading Point | - |
|
||||
|
||||
## 4. Weights & Dimensions
|
||||
Weight and dimension data is heavily enriched to ensure compliance with Bosch requirements for precise logistics data.
|
||||
|
||||
| Data Point | Source | Logic |
|
||||
| :--- | :--- | :--- |
|
||||
| **Gross Weight** | VDA 711.08 | Total weight of the transmission. |
|
||||
| **Net Weight** | VDA 711.09 | Falling back to Gross if missing. |
|
||||
| **Package Weight**| **Config** | Looked up in `[PAC_WEIGHT]` using Supplier Pack Mat No. |
|
||||
| **Package Dims** | **Config** | Looked up in `[PAC_LENGTH/WIDTH/HEIGHT]`. |
|
||||
| **Item Weight** | **Config** | Looked up in `[LIN_WEIGHT]` using Supplier Material No. |
|
||||
|
||||
## 5. Shipping Labels & SSCC (GIR/GIN)
|
||||
The application handles the generation of shipping unit identifiers (SSCC/Serial) dynamically.
|
||||
|
||||
* **Label Ranges**: If VDA 715.11/12 (Label No From/To) exists, these specific serials are used.
|
||||
* **Synthetic Serials**: If missing, the app generates unique serials starting from `10000`.
|
||||
* **Prefix (ANK)**: **Enriched** from `[RFF_ANK_SE]` for the specific supplier ID. This prefix is added to all generated SSCCs/Serials.
|
||||
|
||||
## 6. General Configuration
|
||||
The `[GENERAL]` section of the config provides values for:
|
||||
* `UNB_RECEIVER`: The name of the receiver in the interchange header (Default: "BOSCH EDI-TEAM").
|
||||
* `DEFAULT_DIMENSIONS`: Applied if no specific dimensions are found for a packaging material.
|
||||
BIN
docs/vda_4905_de_passworded.pdf
Normal file
BIN
docs/vda_4905_de_passworded.pdf
Normal file
Binary file not shown.
942
docs/vda_4905_de_passworded.txt
Normal file
942
docs/vda_4905_de_passworded.txt
Normal file
@@ -0,0 +1,942 @@
|
||||
|
||||
VDA-Empfehlung 4905 - 1 - 4. Ausgabe vom April 1996
|
||||
|
||||
VDA
|
||||
Arbeitskreis „Vordruckwesen/Datenaustausch“
|
||||
in VDA-Rohstoff-Ausschuß (VDA-AKVD)
|
||||
|
||||
Daten-Fern-Übertragung von Lieferabrufen
|
||||
4905
|
||||
|
||||
Verfahrensbeschreibung
|
||||
Daten-Fern-Übertragung (DFÜ) von Lieferabrufdaten von Kunden an Lieferanten.
|
||||
|
||||
Diese Empfehlung regelt den maschinellen Datenaustausch von Lieferabrufen
|
||||
von den Automobilherstellern an die Zulieferindustrie sowie von der Zulieferin-
|
||||
dustrie an ihre Vorlieferanten. Sie ist das Projektergebnis des VDA-Arbeitskreises
|
||||
„Vordruckwesen/Datenaustausch“ (VDA-AKVD).
|
||||
|
||||
|
||||
4. Ausgabe vom April 1996
|
||||
Ersetzt 3. Ausgabe vom Oktober 1988
|
||||
VDA-AKVD
|
||||
AUDI, Bauer & Schaurte, Behr, BMW, Bosch, Brose, Continental, Eaton Controls,
|
||||
Elring, Ford, Freudenberg,Happich, Hella, Hoppecke, ITT Automotive Europe, Iveco,
|
||||
Kamax, Krupp Hoesch Automotive, Lucas, Mahle, MAN, Mann & Hummel, Mercedes-
|
||||
Benz, Opel, Osram, Phoenix, Porsche, Sekurit, SKF, Valeo, Varta, VDO, VW, ZF
|
||||
Herausgeber: Verband der Automobilindustrie Copyright
|
||||
Westendstraße 61 Nachdruck, auch auszugsweise
|
||||
Postfach 17 05 63 nur mit Genehmigung des
|
||||
60079 Frankfurt Herausgebers gestattet.
|
||||
Telefon 069/7570-283
|
||||
Telefax 069/7570-300
|
||||
|
||||
VDA-Empfehlung 4905 - 2 - 4. Ausgabe vom April 1996
|
||||
|
||||
INHALTSVERZEICHNIS Seite
|
||||
1 KURZBESCHREIBUNG 3
|
||||
1.1 Anwendung 3
|
||||
1.2 Zielsetzung 3
|
||||
1.3 Definition und Grundlagen 4
|
||||
1.4 Änderungen gegenüber Vorversion 4
|
||||
|
||||
2 SATZARTEN 5
|
||||
|
||||
3 PRÜFBEDINGUNGEN 5
|
||||
3.1 Zulässige Satzarten 5
|
||||
3.1.1 Beschreibung der Datenstruktur 6
|
||||
3.1.2 Beispiel 6
|
||||
3.2 Gültige Versions-Nummer 7
|
||||
3.3 Formelle Prüfung der Datenfelder 7
|
||||
3.4 Logische Prüfung von Datenfeldern 7
|
||||
3.5 Kunden-Nummer/Lieferanten-Nummer 7
|
||||
3.6 Abstimmsummen 7
|
||||
3.7 Übertragungs-Nummer alt/neu 7
|
||||
|
||||
4 ZULIEFERINTERNE VERARBEITUNG 8
|
||||
4.1 Ordnungsbegriffe/Suchschlüssel 8
|
||||
4.2 Weiterverarbeitung 8
|
||||
4.3 Eingangs-Fortschrittzahl 8
|
||||
4.4 Aufbau von Fehlermeldungen 9
|
||||
|
||||
5 ANLAGEN 10
|
||||
|
||||
|
||||
VDA-Empfehlung 4905 - 3 - 4. Ausgabe vom April 1996
|
||||
|
||||
1 KURZBESCHREIBUNG
|
||||
1.1 Anwendung
|
||||
Diese Verfahrensbeschreibung regelt die Abwicklung der Daten-Fern-Übertra-
|
||||
gung (DFÜ) von Lieferabrufen über öffentliche Übertragungsnetze zwischen
|
||||
den Automobilherstellern und der Zulieferindustrie sowie zwischen der Zuliefer-
|
||||
industrie und ihren Vorlieferanten. Sie wurde bewußt allgemeingültig und hard-
|
||||
wareunabhängig entwickelt. Die Prozedur ist unabhängig von speziellen An-
|
||||
wendungs-Systemen einsatzfähig.
|
||||
Das Verfahren wird seit April 1978 im Echtbetrieb praktiziert und hat sich in der
|
||||
Praxis bewährt.
|
||||
1.2 Zielsetzung
|
||||
− Schnelle Datenübermittlung
|
||||
− Belegloser Datenaustausch
|
||||
− Fertigungs- und Lieferprogramme beim Lieferanten lassen sich früher als
|
||||
bisher auf die neue Programmsituation beim Kunden einrichten.
|
||||
− Telefonisch oder fernschriftliche Mitteilungen bei Lieferabruf-Änderungen
|
||||
werden reduziert, weil die DFÜ eine umgehende Information der Lieferanten
|
||||
erlaubt.
|
||||
− Weniger Sondermaßnahmen wie Sondertransporte usw.
|
||||
− Neue Programmplanung des Kunden ist 3 bis 5 Arbeitstage früher verfügbar.
|
||||
Dieses bringt u.a. folgende Vorteile:
|
||||
a) Bessere Planungssicherheit (Produktionsplan)
|
||||
b) Erhöhung der Lieferbereitschaft
|
||||
c) Erhöhung der Fertigungsflexibilität
|
||||
d) Rückgang von Sofortplanungen
|
||||
− Pro Verkaufsartikel kann der Bedarf für alle Werke des Kunden gleichzeitig
|
||||
vorgegeben werden.
|
||||
− In Abhängigkeit vom Integrationsgrad des DFÜ-Verfahrens in die Vertriebs-
|
||||
und Materialwirtschaftssysteme beim Lieferanten ergeben sich weitere Vor-
|
||||
teile:
|
||||
a) Kein Aufwand bei derDatenerfassung
|
||||
b) Keine Erfassungsfehler
|
||||
c) Kein manuelles Übertragen der Lieferabrufdaten in lieferanteneige-
|
||||
ne Belege
|
||||
d) Maschinelle Zuordnung von Kundendaten, z.B.
|
||||
- zur Sachnummer des Lieferanten
|
||||
- zur Auftragsnummer des Lieferanten
|
||||
- Abgleich der Kunden-Position mit "unterwegs befindlichen Liefe-
|
||||
|
||||
VDA-Empfehlung 4905 - 4 - 4. Ausgabe vom April 1996
|
||||
|
||||
rungen"
|
||||
- Ermittlung von Bedarfserhöhungen, -reduzierungen, Terminverschie-
|
||||
bungen gegenüber der aktuellen Lieferantenplanung
|
||||
- Einplanung des neuen Lieferabrufes
|
||||
- Sofortige Prüfung auf verfügbaren Lagerbestand bei Sofortbedarf.
|
||||
e) Die per DFÜ gesendeten Lieferabrufdaten sind bis zum Eintreffen
|
||||
vorgegebener Korrekturen bzw. bis zum nächsten DFÜ-Lauf ver-
|
||||
bindlich.
|
||||
1.3 Definition und Grundlagen
|
||||
Definition Lieferabruf
|
||||
Lieferabrufe werden vorwiegend in der Automobilindustrie eingesetzt, um in
|
||||
Menge und Termin wechselnde Bedarfsänderungen in bestimmten Zeitabstän-
|
||||
den fortzuschreiben. Ein neuer Lieferabruf ersetzt vollständig einen vorherge-
|
||||
henden. Mit der Lieferabruf-DFÜ werden Daten übertragen, die auch im Liefer-
|
||||
abruf-Vordruck enthalten sind. Der VDA-Standardvordruck "Lieferabruf" ist in
|
||||
der VDA-Empfehlung 4904 beschrieben. Der Standardvordruck VDA4904 wird
|
||||
nur an Lieferanten geschickt, bei denen keine DFÜ vorhanden ist.
|
||||
Grundlagen
|
||||
Als Grundlage dieser Anwendung dienen die im VDA-Arbeitskreis "Vordruck-
|
||||
wesen/Datenaustausch" getroffenen Vereinbarungen. Hierbei handelt es sich
|
||||
um die Festlegung von
|
||||
- einheitlichen Satzaufbauten,
|
||||
- standardisierten Datenelementen im Bereich Automobilindustrie,
|
||||
- einheitlichen Feldlängen, Feldarten, Schlüsseln/Codes,
|
||||
die als verbindliche Programmierungsgrundlage zwischen den DFÜ-Teilneh-
|
||||
mern eingesetzt werden.
|
||||
Für die DFÜ von Lieferabrufdaten werden, soweit vorhanden, international oder
|
||||
national übliche Schlüssel verwendet, die je nach Erfordernis in anwender-
|
||||
eigene Schlüssel umgesetzt werden. Aus Kostengründen werden bewußt nicht
|
||||
alle Daten aus dem Lieferabruf-Vordruck per DFÜ übertragen. Es werden nur
|
||||
die Daten gesendet, die unbedingt von den DFÜ-Teilnehmern gefordert und im
|
||||
VDA-AKVD festgelegt wurden.
|
||||
1.4 Änderungen gegenüber Vorversion
|
||||
Folgende Änderungen führten zur 4. Ausgabe der Empfehlung VDA 4905:
|
||||
• Erweiterung um die Satzart 515 „Zusatz-LAB-Informationen“,
|
||||
• Darstellungshinweis für die Datumsverschlüsselung „555555,
|
||||
• Überarbeitung der verbalen Beschreibung,
|
||||
• Begrenzung der Satzart 518 auf 5 Wiederholungen,
|
||||
• Aufnahme eines Datums „Zur Nullstellung erreichte Fortschrittzahl“.
|
||||
|
||||
VDA-Empfehlung 4905 - 5 - 4. Ausgabe vom April 1996
|
||||
|
||||
2 SATZARTEN
|
||||
Um die erforderlichen Daten sachlich zusammenhängend übertragen zu kön-
|
||||
nen, wurden entsprechende Datensätze enwickelt. Die Struktur dieser Daten-
|
||||
sätze einschließlich der zugeordneten Satzarten, Datenelemente, Feldlängen,
|
||||
Feldarten usw. ist den Anlagen zu entnehmen.
|
||||
Satzart Beschreibung Muß/Kann
|
||||
511 Vorsatz Lieferabrufdaten Muß
|
||||
512 einmalige Datenelemente des Lieferabrufes Muß
|
||||
513 Abgrenzungs- und Abrufdaten Muß
|
||||
514 weitere Abrufdaten Kann
|
||||
515 Zusatz-LAB Informationen Kann
|
||||
517 Packmitteldaten Kann
|
||||
518 Lieferabruftext Kann
|
||||
519 Nachsatz Lieferabrufdaten Muß
|
||||
|
||||
3 PRÜFBEDINGUNGEN
|
||||
Für den Datenaustausch zwischen LIEFERANTEN und KUNDEN sind die
|
||||
nachstehend aufgeführten Prüfbedingungen zu berücksichtigen. Sie entspre-
|
||||
chen den Festlegungen des VDA-Arbeitskreises "Vordruckwesen/Datenaus-
|
||||
tausch" und werden im VDA-AKVD-Datenkatalog (VDABASE) dokumentiert.
|
||||
3.1 Zulässige Satzarten
|
||||
Satzart Beschreibung
|
||||
511 Vorsatz Lieferabrufdaten (1x pro DFÜ)
|
||||
512 einmalige Daten (1x pro Ordnungsbegriff1))
|
||||
513 Abgrenzungs- und Abrufdaten (1x pro Ordnungsbegriff1))
|
||||
514 weitere Abrufdaten (x-mal pro Ordnungsbegriff1))
|
||||
515 Zusatz-LAB Informationen (1x pro Ordnungsbegriff1))
|
||||
517 Packmitteldaten (x-mal pro Ordnungsbegriff1))
|
||||
518 Textdaten (x-mal pro Ordnungsbegriff1))
|
||||
519 Nachsatz Lieferabrufdaten (1x pro DFÜ)
|
||||
|
||||
1) Ordnungsbegriffe, soweit gefordert, siehe auch 4.1
|
||||
(Werk-Kunde, Sach-Nummer-Kunde, Abschluß-Bestellnummer, Abladestelle)
|
||||
Die Satzarten 511 und 519 sind zwingend am Anfang und am Ende einer DFÜ
|
||||
erforderlich.
|
||||
|
||||
VDA-Empfehlung 4905 - 6 - 4. Ausgabe vom April 1996
|
||||
|
||||
3.1.1 Beschreibung der Datenstruktur
|
||||
|
||||
|
||||
511 512 519
|
||||
M 1 M 1 M 1
|
||||
|
||||
|
||||
|
||||
513
|
||||
M 1
|
||||
|
||||
|
||||
|
||||
514 515 517 518
|
||||
K R K 1 K R K 5
|
||||
|
||||
|
||||
511 muß erster Satz einer logischen Übertragung sein.
|
||||
512 muß auf 511 und kann auf 513, 514, 515, 517 oder 518 folgen.
|
||||
513 muß auf 512 folgen.
|
||||
514 kann auf 513 oder 514 folgen (Abruftermine müssen aufsteigend sein)*.
|
||||
515 kann auf 513 oder 514 folgen.
|
||||
517 kann auf 513, 514, 515 oder 517 folgen.
|
||||
518 kann auf 513, 514, 515, 517 oder 518 folgen.
|
||||
519 kann auf 513, 514, 515, 517 oder 518 folgen und muß letzter Satz
|
||||
einer logischen Übertragung sein.
|
||||
* Ausnahmen bilden die Regelungen zu den Terminschlüsseln
|
||||
000000, 222222, 333333, 444444, 555555.
|
||||
3.1.2 Beispiel:
|
||||
Muß Kann Häufigkeit
|
||||
511 Einmal pro DFÜ
|
||||
512 Einmal pro Teilenummer und Werk
|
||||
513 Einmal pro Teilenummer und Werk
|
||||
514 x-mal pro Teilenummer und Werk
|
||||
515 Einmal pro Teilenummer und Werk
|
||||
517 x-mal pro Teilenummer und Werk
|
||||
518 max. 5-mal pro Teilenummer und Werk
|
||||
519 Einmal pro DFÜ
|
||||
|
||||
|
||||
VDA-Empfehlung 4905 - 7 - 4. Ausgabe vom April 1996
|
||||
|
||||
3.2 Gültige Versions-Nummer
|
||||
Die vom Datenersteller benutzte Versions-Nummer jeder einzelnen Satzart
|
||||
muß mit der beim Datenempfänger verwendeten Versions-Nummer überein-
|
||||
stimmen. Ein Wechsel der Versions-Nummer muß rechtzeitig zwischen den
|
||||
DFÜ-Partnern vereinbart werden und vor dem Einsatztermin in die entspre-
|
||||
chenden Programm-Prüfroutinen eingearbeitet sein.
|
||||
3.3 Formelle Prüfung der Datenfelder
|
||||
Sämtliche Datenfelder einer Satzart müssen den formellen Vorschriften ent-
|
||||
sprechen, die unter der Versionsnummer im VDA-Datenkatalog (VDABASE)
|
||||
zentral verwaltet werden.
|
||||
Numerisch definierte Felder sind als ungepackt dezimal anzusehen und rechts-
|
||||
bündig mit führenden Nullen zu füllen. Werden diese Felder nicht benutzt, sind
|
||||
sie mit Nullen zu füllen.
|
||||
3.4 Logische Prüfung von Datenfeldern
|
||||
Aus verschiedenen Satzarten werden Einzelprüfungen ausgewählt, die sicher-
|
||||
stellen, daß jeder DFÜ-Teilnehmer nur die für ihn bestimmten Daten erhält.
|
||||
3.5 Kunden-Nummer/Lieferanten-Nummer
|
||||
Die vom Absender in der Satzart 511 benutzte Verbindung aus Kunden- und
|
||||
Lieferanten-Nummer muß mit der beim Empfänger registrierten Nummer über-
|
||||
einstimmen.
|
||||
3.6 Abstimmsummen
|
||||
Die in der Satzart 519 ermittelten Zählerstände müssen mit den beim Daten-
|
||||
empfänger ermittelten Zählerständen übereinstimmen. Bei Abweichungen ist
|
||||
die DFÜ ungültig und ist vollständig zu wiederholen.
|
||||
3.7 Übertragungs-Nummer alt/neu
|
||||
Der Datenersteller vergibt pro DFÜ und Sachgebiet sowohl die Übertragungs-
|
||||
Nummer neu als auch die Übertragungs-Nummer alt. Darüber läßt sich der er-
|
||||
forderliche Gleichstand der pro Sachgebiet erfolgten Übertragungen zwischen
|
||||
den jeweiligen Partnern kontrollieren. Eine lückenlos aufsteigende Vergabe der
|
||||
Übertragungs-Nummer neu ist nicht zwingend.
|
||||
|
||||
VDA-Empfehlung 4905 - 8 - 4. Ausgabe vom April 1996
|
||||
|
||||
4 ZULIEFERINTERNE VERARBEITUNG
|
||||
4.1 Ordnungsbegriffe/Suchschlüssel
|
||||
Es sind folgende Ordnungsbegriffe zu berücksichtigen:
|
||||
- Werk Kunde,
|
||||
- Sach-Nummer-Kunde,
|
||||
- Abschluß-Bestellnummer,
|
||||
- Abladestelle.
|
||||
Aufgrund o.a. Ordnungsbegriffe kann beim Datenempfänger der entsprechende
|
||||
Auftrag/Abruf zugeordnet werden.
|
||||
Aufgrund der Sach-Nummer des Kunden kann über eine Referenz-Datei die
|
||||
Sach-Nummer des Lieferanten ermittelt werden.
|
||||
4.2 Weiterverarbeitung
|
||||
Die per DFÜ empfangenen Lieferabrufdaten sollten sofort gesichert, geprüft,
|
||||
gezählt und ergänzt werden, z.B.:
|
||||
− Lieferanteneigene Kunden-Nummer
|
||||
− Lieferanteneigene Artikel-Nummer
|
||||
− Lieferanteneigene Auftrags-Nummer
|
||||
− Artikel-Bezeichnung/-Benennung
|
||||
− Kostenstelle/Fertigungsstelle
|
||||
− Lagerplatz
|
||||
− Verpackungsangaben (Packmittelnummer und Fassungsvermögen)
|
||||
− Abgleich mit unterwegs befindlichen Lieferungen
|
||||
− Ermittlung von Bedarfserhöhungen, Reduzierungen und Terminverschie-
|
||||
bungen gegenüber der aktuellen Planung beim Lieferanten
|
||||
− Berücksichtigung des neuen Lieferabrufes im Produktionsplan bzw. im Ver-
|
||||
sandbereich
|
||||
− Sofortige Überprüfung auf verfügbaren Lagerbestand bei Sofortbedarf
|
||||
− Umsetzen der Bestelldaten in anwendereigene Daten (z.B. gem. Fabrik-
|
||||
Kalender)
|
||||
− der neue Lieferabruf ersetzt immer vollständig den "alten" Lieferabruf
|
||||
− Synchronisation mit der täglichen Feinabruf-DFÜ im Nahbereich
|
||||
4.3 Eingangs-Fortschrittzahl
|
||||
Die Fortschrittszahl beinhaltet alle vom Kunden positiv bzw. negativ verbuchten
|
||||
Lieferungen ab einem bestimmten Zeitpunkt (z.B. ab 1.1. des Jahres) bis zum
|
||||
Stichtag der aktuellen Lieferabruf-Rechnung. Aus der Differenz zwischen der
|
||||
Liefer-Fortschrittszahl beim Lieferanten und der Eingangs-Fortschrittszahl beim
|
||||
Kunden kann z.B. die auf dem Transportwege befindliche Menge abgeleitet
|
||||
werden.
|
||||
|
||||
VDA-Empfehlung 4905 - 9 - 4. Ausgabe vom April 1996
|
||||
|
||||
Die Nullstellung der Eingangsfortschrittzahl (SA 511, Version 02, Pos. 08) wird
|
||||
im ersten Lauf nach der Umstellung übertragen. Das Umstellungsdatum gilt für
|
||||
alle Teile des Zulieferers. Deshalb sollten im ersten Lauf nach der Nullstellung
|
||||
alle Teile enthalten sein. Die Umstellung der Fortschrittzahl kann auch rückwir-
|
||||
kend erfolgen, dies muß jedoch innerhalb einer Frist von 2 Monaten gesche-
|
||||
hen.
|
||||
4.4 Aufbau von Fehlermeldungen
|
||||
Entsprechende Fehlerprüfungen sind in der Anwendung vorzusehen. Als
|
||||
Beispiele könnten u.a. gesehen werden:
|
||||
− Sachnummer des Kunden fehlt in der Stammdatei
|
||||
− Abladestelle nicht in der Stammdatei vorhanden
|
||||
− Die Angaben der letzten Lieferung (Datum, Menge, Fortschrittszahl, Liefer-
|
||||
schein-Nr.) können nicht identifiziert werden
|
||||
− Packmittel-Daten aus LAB-DFÜ sind abweichend von Lieferanten-Angaben
|
||||
(Packmittelnummer und Fassungsvermögen)
|
||||
|
||||
VDA-Empfehlung 4905 - 10 - 4. Ausgabe vom April 1996
|
||||
|
||||
5 ANLAGEN
|
||||
Anlage 1
|
||||
Strukturbeschreibung für Satzart 511, Satzlänge: 128, Mußsatz
|
||||
Vorsatz Lieferabrufdaten
|
||||
Version 02
|
||||
Pos. Datenelement K
|
||||
M
|
||||
LG.
|
||||
BYT
|
||||
AN von-
|
||||
bis
|
||||
Verbale Beschreibung
|
||||
01 Satzart M 3 N 1-3 Konstant"511"
|
||||
02 Versions-
|
||||
Nummer
|
||||
M 2 N 4-5 Kennzeichnung der Aktualität einer Satzart.
|
||||
Wird je Satzart lückenlos aufsteigend geführt
|
||||
und nach jeder vom VDA-AK "Vordruckwesen/
|
||||
Datenaustausch“ beschlossenen Änderung einer
|
||||
Satzart jeweils um 1 erhöht.
|
||||
03 Kunden-
|
||||
Nummer
|
||||
M 9 A 6-14 Identnummer, die der Lieferant einem Kunden
|
||||
zuteilt. Alle Daten einer Satzstruktur, die das
|
||||
Feld Kunden-Nr. enthält, unterliegen dem Daten-
|
||||
schutz. Linksbündiger Eintrag.
|
||||
04 Lieferanten-
|
||||
Nummer
|
||||
M 9 A 15-23 Identnummer, die der Kunde einem Lieferanten
|
||||
(Vertragsnehmer) zuordnet. Alle Daten einer
|
||||
Struktur, die das Feld Lieferanten-Nr. enthält,
|
||||
unterliegen dem Datenschutz. Linksbündiger
|
||||
Eintrag.
|
||||
05 Übertragungs-
|
||||
nummer alt
|
||||
M 5 N 24-28 Beschreibung siehe Übertragungsnummer neu.
|
||||
Bei der ersten Übertragung ist Übertragungs-
|
||||
nummer alt=00000. Rechtsbündiger Eintrag, mit
|
||||
führenden Nullen.
|
||||
06 Übertragungs-
|
||||
nummer neu
|
||||
M 5 N 29-33 Der Datenersteller vergibt innerhalb einer An-
|
||||
wendung (z.B. Lieferabruf-DFÜ, Rechnungs-DFÜ
|
||||
usw.) für jeden DFÜ-Erstellungslauf eine Über-
|
||||
tragungs-Nummer (neu). Der Wert „00000“ darf
|
||||
nicht verwendet werden. Datenersteller und
|
||||
Empfänger bewahren diese Nummer bis zur
|
||||
nächsten Übertragung derselben Anwendung
|
||||
auf. Da der Datenersteller jeweils zu der neuen
|
||||
Übertragungs-Nummer auch die des vorausge-
|
||||
gangenen DFÜ-Erstellungslaufs innerhalb dieser
|
||||
Anwendung angibt, kann der Empfänger die Voll-
|
||||
ständigkeit der DFÜ-Bestände je Anwendung
|
||||
kontrollieren. Daher ist keine lückenlose und auf-
|
||||
steigende Nummernfolge erforderlich.
|
||||
Rechtsbündiger Eintrag mit führenden Nullen.
|
||||
|
||||
|
||||
K = Kann M = Muß
|
||||
|
||||
VDA-Empfehlung 4905 - 11 - 4. Ausgabe vom April 1996
|
||||
|
||||
A = Alphanumerisch N = Numerisch
|
||||
|
||||
VDA-Empfehlung 4905 - 12 - 4. Ausgabe vom April 1996
|
||||
|
||||
Fortsetzung Anlage 1
|
||||
Pos. Datenelement K
|
||||
M
|
||||
LG.
|
||||
BYT
|
||||
AN von-
|
||||
bis
|
||||
Verbale Beschreibung
|
||||
Beispiele für Eintragungen in den beiden Über-
|
||||
tragungs-Nummern:
|
||||
Vorgang Nummer alt Nummer neu
|
||||
Start 00000 00001
|
||||
oder 00000 00017
|
||||
Routine: 00019 00020
|
||||
oder 88051 88061
|
||||
Überlauf: 99999 00001
|
||||
oder 89361 00011
|
||||
07 Übertragungs-
|
||||
Datum
|
||||
M 6 N 34-39 In Form: JJMMTT
|
||||
08 Datum-Null-
|
||||
stellung Ein-
|
||||
gangsfort-
|
||||
schrittszahl
|
||||
K 6 N 40-45 In Form: JJMMTT
|
||||
09 Leer M 83 A 46-128 mit Blanks gefüllt
|
||||
|
||||
K = Kann M = Muß
|
||||
A = Alphanumerisch N = Numerisch
|
||||
|
||||
VDA-Empfehlung 4905 - 13 - 4. Ausgabe vom April 1996
|
||||
|
||||
Anlage 2
|
||||
Strukturbeschreibung für Satzart 512, Satzlänge: 128, Mußsatz
|
||||
Version 01
|
||||
Pos. Datenelement K
|
||||
M
|
||||
LG.
|
||||
BYT
|
||||
AN von-
|
||||
bis
|
||||
Verbale Beschreibung
|
||||
01 Satzart M 3 N 1-3 Konstant"512"
|
||||
02 Versions-
|
||||
Nummer
|
||||
M 2 N 4-5 Kennzeichnung der Aktualität einer Satzart,
|
||||
siehe auch Satzart 511
|
||||
03 Werk-
|
||||
Kunde
|
||||
M 3 A 6-8 Werk des Kunden, an das geliefert werden soll.
|
||||
Verschlüsselte Form des Kunden. Linksbündiger
|
||||
Eintrag, siehe Anlage 9, Pos. 01
|
||||
04 Lieferabruf-
|
||||
Nummer neu
|
||||
M 9 N 9-17 Der Kunde vergibt für jeden Lauf zur Aufberei-
|
||||
tung von Lieferabrufdaten eine Lieferabruf-
|
||||
nummer. Kunde und Lieferant bewahren diese
|
||||
Nummer bis zur nächsten Verarbeitung von
|
||||
Lieferabrufdaten auf. Da der Kunde jeweils zu
|
||||
der neuen Lieferabrufnummer auch die der vor-
|
||||
ausgegangenen Verarbeitung angibt, kann der
|
||||
Lieferant die Vollständigkeit der Lieferabrufdaten
|
||||
je Sachnummer kontrollieren. linksbündiger Ein-
|
||||
trag.
|
||||
05 Lieferabruf-
|
||||
Datum neu
|
||||
M 6 N 18-23 In Form JJMMTT; in Verbindung mit Pos. 04
|
||||
06 Lieferabruf-
|
||||
Nummer alt
|
||||
M 9 N 24-32 s. Lieferabruf-Nummer neu, linksbündiger Ein-
|
||||
trag
|
||||
07 Lieferabruf-
|
||||
Datum alt
|
||||
M 6 N 33-38 In Form: JJMMTT; in Verbindung mit Pos. 06
|
||||
08 Sachnummer
|
||||
Kunde
|
||||
M 22 A 39-60 Identnummer, die der Kunde einem Artikel oder
|
||||
einer sonstigen Leistung zuordnet.
|
||||
Linksbündiger Eintrag in Druckform.
|
||||
09 Sachnummer
|
||||
Lieferant
|
||||
K 22 A 61-82 Identnummer, die der Lieferant einem Artikel
|
||||
oder einer sonstigen Leistung zuordnet.
|
||||
Linksbündiger Eintrag in Druckform.
|
||||
10 Abschluß-/
|
||||
Bestell-
|
||||
Nummer
|
||||
K 12 N 83-94 Identnummer, die der Kunde einer Bestellung
|
||||
bzw. einem Rahmenabschluß zuteilt.
|
||||
Linksbündiger Eintrag in Druckform..
|
||||
|
||||
K = Kann M = Muß
|
||||
A = Alphanumerisch N = Numerisch
|
||||
|
||||
VDA-Empfehlung 4905 - 14 - 4. Ausgabe vom April 1996
|
||||
|
||||
Fortsetzung Anlage 2
|
||||
Pos. Datenelement K
|
||||
M
|
||||
LG.
|
||||
BYT
|
||||
AN von-
|
||||
bis
|
||||
Verbale Beschreibung
|
||||
11 Abladestelle M 5 A 95-99 Die Abladestelle bezeichnet die Stelle im Werk-
|
||||
Kunde, an der die Ware abgeladen werden soll.
|
||||
Verschlüsselte Form des Kunden. Linksbündiger
|
||||
Eintrag.
|
||||
12 Zeichen des
|
||||
Kunden
|
||||
M 4 A 100-103 Linksbündiger Eintrag.
|
||||
13 Mengeneinheit M 2 A 104-105 Verschlüsselte Form; siehe Anlage 9, Pos. 02
|
||||
14 Anlieferungs-
|
||||
Intervall
|
||||
M 1 A 106 Verschlüsselte Form; siehe Anlage 9, Pos. 03
|
||||
15 Fertigungs-
|
||||
freigabe
|
||||
K 1 N 107 Anzahl Monate ausschließlich Stichtags-Monat
|
||||
16 Material-
|
||||
freigabe
|
||||
K 1 N 108 Anzahl Monate ausschließlich Stichtags-Monat
|
||||
17 Verwendungs-
|
||||
Schlüssel
|
||||
M 1 A 109 Verschlüsselte Form; siehe Anlage 9, Pos. 04
|
||||
18 Kontierungs-
|
||||
Schlüssel
|
||||
K 7 A 110-116 Auch Zusatzdaten des Kunden, aus Feld (15)
|
||||
des DIN-Vordrucks 4991-94.
|
||||
Linksbündiger Eintrag.
|
||||
19 Lager K 7 A 117-123 Lagerort Kunde, ergänzend zur Abladestelle.
|
||||
Linksbündiger Eintrag.
|
||||
20 Leer M 5 A 124-128 mit BLANKS gefüllt.
|
||||
|
||||
K = Kann M = Muß
|
||||
A = Alphanumerisch N = Numerisch
|
||||
|
||||
VDA-Empfehlung 4905 - 15 - 4. Ausgabe vom April 1996
|
||||
|
||||
Anlage 3
|
||||
Strukturbeschreibung für Satzart 513, Satzlänge: 128, Mußsatz
|
||||
Abgrenzungs- und Abrufdaten
|
||||
Version 01
|
||||
Pos. Datenelement K
|
||||
M
|
||||
LG.
|
||||
BYT
|
||||
AN von-
|
||||
bis
|
||||
Verbale Beschreibung
|
||||
01 Satzart M 3 N 1-3 Konstant"513"
|
||||
02 Versions-
|
||||
Nummer
|
||||
M 2 N 4-5 Kennzeichnung der Aktualität einer Satzart,
|
||||
siehe auch Satzart 511
|
||||
03 Erfassungs-
|
||||
Datum letzter
|
||||
Eingang
|
||||
M 6 N 6-11 In Form JJMMTT. Der Kunde hat bis zu diesem
|
||||
Datum eingegangene Lieferungen verbucht und
|
||||
in seiner Disposition berücksichtigt.
|
||||
04 Lieferschein-
|
||||
Nummer letzter
|
||||
Eingang
|
||||
M 8 N 12-19 Lieferscheinnummer der letzten beim Kunden
|
||||
verbuchten Lieferung, rechtsbündiger Eintrag mit
|
||||
führenden Nullen.
|
||||
05 Lieferschein-
|
||||
Datum letzter
|
||||
Eingang
|
||||
M 6 N 20-25 Versanddatum der letzten beim Kunden ver-
|
||||
buchten Lieferung, in Form JJMMTT.
|
||||
06 Menge letzter
|
||||
Eingang
|
||||
M 12 N 26-37 Menge der letzten beim Kunden verbuchten Lie-
|
||||
ferung, rechtsbündiger Eintrag, mit führenden
|
||||
Nullen. 3 Dezi-malstellen, ggf. gleitendes Minus-
|
||||
zeichen.
|
||||
07 Eingangs-Fort-
|
||||
schrittszahl
|
||||
M 10 N 38-47 Fortschrittszahl, die alle vom Kunden positiv
|
||||
bzw. negativ verbuchten Lieferungen ab einem
|
||||
bestimmten Zeitpunkt (z.B. ab 1.1. des Jahres)
|
||||
bis zum Stichtag der aktuellen Lieferabrufbe-
|
||||
rechnung beinhaltet. Mit führenden Nullen, keine
|
||||
Dezimalstelle, ggf. gleitendes Minuszeichen.
|
||||
08 Abrufdatum 1 M 6 N 48-53 Abruftermine können als Tages-/Wochen- bzw.
|
||||
Monats-termine angegeben werden. Dieses Feld
|
||||
enthält verschiedene zusätzliche Darstellungsfor-
|
||||
men. Erklärungen siehe Anlage 9, Pos.05.
|
||||
09 Abrufmenge 1 M 9 N 54-62 Enthält die Abruf-Menge 1. Alle Abruf-Mengen
|
||||
rechtsbündiger Eintrag mit führenden Nullen.
|
||||
Keine Dezimalstelle.
|
||||
10 Abrufdatum 2 K 6 N 63-68 siehe Abrufdatum 1
|
||||
|
||||
K = Kann M = Muß
|
||||
A = Alphanumerisch N = Numerisch
|
||||
|
||||
VDA-Empfehlung 4905 - 16 - 4. Ausgabe vom April 1996
|
||||
|
||||
Fortsetzung Anlage 3
|
||||
Pos. Datenelement K
|
||||
M
|
||||
LG.
|
||||
BYT
|
||||
AN von-
|
||||
bis
|
||||
Verbale Beschreibung
|
||||
11 Abrufmenge 2 K 9 N 69-77 siehe Abrufmenge 1
|
||||
12 Abrufdatum 3 K 6 N 78-83 siehe Abrufdatum 1
|
||||
13 Abrufmenge 3 K 9 N 84-92 siehe Abrufmenge 1
|
||||
14 Abrufdatum 4 K 6 N 93-98 siehe Abrufdatum 1
|
||||
15 Abrufmenge 4 K 9 N 99-107 siehe Abrufmenge 1
|
||||
16 Abrufdatum 5 K 6 N 108-113 siehe Abrufdatum 1
|
||||
17 Abrufmenge 5 K 9 N 114-122 siehe Abrufmenge 1
|
||||
18 Leer M 6 A 123-128 mit BLANKS gefüllt
|
||||
|
||||
K = Kann M = Muß
|
||||
A = Alphanumerisch N = Numerisch
|
||||
|
||||
VDA-Empfehlung 4905 - 17 - 4. Ausgabe vom April 1996
|
||||
|
||||
Anlage 4
|
||||
Strukturbeschreibung für Satzart 514, Satzlänge: 128, Kannsatz
|
||||
weitere Abrufdaten
|
||||
Version 01
|
||||
Pos. Datenelement K
|
||||
M
|
||||
LG.
|
||||
BYT
|
||||
AN von-
|
||||
bis
|
||||
Verbale Beschreibung
|
||||
01 Satzart M 3 N 1-3 Konstant"514"
|
||||
02 Versions-
|
||||
Nummer
|
||||
M 2 N 4-5 Kennzeichnung der Aktualität einer Satzart,
|
||||
siehe auch Satzart 511
|
||||
03 Abrufdatum 6 M 6 N 6-11 siehe Abrufdatum 1, SA 513.
|
||||
04 Abrufmenge 6 M 9 N 12-20 siehe Abrufmenge 1, SA 513.
|
||||
05 Abrufdatum 7 K 6 N 21-26 siehe Abrufdatum 1
|
||||
06 Abrufmenge 7 K 9 N 27-35 siehe Abrufmenge 1
|
||||
07 Abrufdatum 8 K 6 N 36-41 siehe Abrufdatum 1
|
||||
08 Abrufmenge 8 K 9 N 42-50 siehe Abrufmenge 1
|
||||
09 Abrufdatum 9 K 6 N 51-56 siehe Abrufdatum 1
|
||||
10 Abrufmenge 9 K 9 N 57-65 siehe Abrufmenge 1
|
||||
11 Abrufdatum 10 K 6 N 66-71 siehe Abrufdatum 1
|
||||
12 Abrufmenge 10 K 9 N 72-80 siehe Abrufmenge 1
|
||||
13 Abrufdatum 11 K 6 N 81-86 siehe Abrufdatum 1
|
||||
14 Abrufmenge 11 K 9 N 87-95 siehe Abrufmenge 1
|
||||
15 Abrufdatum 12 K 6 N 96-101 siehe Abrufdatum 1
|
||||
16 Abrufmenge 12 K 9 N 102-110 siehe Abrufmenge 1
|
||||
17 Abrufdatum 13 K 6 N 111-116 siehe Abrufdatum 1
|
||||
18 Abrufmenge 13 K 9 N 117-125 siehe Abrufmenge 1
|
||||
19 Leer M 3 A 126-128 mit BLANKS gefüllt
|
||||
|
||||
Anmerkung zu Satzart „514“: Diese Tabelle umfaßt wahlweise die Abruffelder 6-13
|
||||
oder 14-21
|
||||
oder 22-29
|
||||
oder 30-37 usw.
|
||||
K = Kann M = Muß
|
||||
A = Alphanumerisch N = Numerisch
|
||||
|
||||
VDA-Empfehlung 4905 - 18 - 4. Ausgabe vom April 1996
|
||||
|
||||
Anlage 5
|
||||
Strukturbeschreibung für Satzart 515, Satzlänge: 128, Kannsatz
|
||||
Zusatz LAB-Informationen
|
||||
Version 02
|
||||
Pos. Datenelement K
|
||||
M
|
||||
LG.
|
||||
BYT
|
||||
AN von-
|
||||
bis
|
||||
Verbale Beschreibung
|
||||
01 Satzart M 3 N 1-3 Konstant"515"
|
||||
02 Versions-
|
||||
Nummer
|
||||
M 2 N 4-5 Kennzeichnung der Aktualität einer Satzart,
|
||||
siehe auch Satzart 511
|
||||
03 Fertigungsfrei-
|
||||
gabe, Anfangs-
|
||||
datum
|
||||
K 6 N 6-11 Form JJMMTT
|
||||
04 Fertigungsfrei-
|
||||
gabe, Enddat.
|
||||
K 6 N 12-17 Form JJMMTT
|
||||
05 Fertigungsfrei-
|
||||
gabe, Kum.
|
||||
Bedarf
|
||||
K 10 N 18-27 Fortschrittszahl der Fertigungsfreigabe, die am
|
||||
Enddatum (Pos. 04) erreicht wird
|
||||
06 Materialfrei-
|
||||
gabe, Anfangs-
|
||||
datum
|
||||
K 6 N 28-33 Form JJMMTT
|
||||
07 Materialfrei-
|
||||
gabe, Enddat.
|
||||
K 6 N 34-39 Form JJMMTT
|
||||
08 Materialfrei-
|
||||
gabe, Kum.
|
||||
Bedarf
|
||||
K 10 N 40-49 Fortschrittszahl der Materialfreigabe, die am
|
||||
Enddatum (Pos. 07) erreicht wird
|
||||
09 Ergänzende
|
||||
Sachnummer
|
||||
K 22 A 50-71 Ergänzende Sachnummer
|
||||
10 Zwischen-
|
||||
Lieferant
|
||||
K 9 A 72-80 Identnummer, die der Kunde einem Zwischen-
|
||||
lieferanten zuordnet.
|
||||
Linksbündiger Eintrag
|
||||
11 Datum Pla-
|
||||
nungshorizont
|
||||
K 6 N 81-86 Enddatum des Planungshorizonts
|
||||
12 Verbrauchs-
|
||||
stelle
|
||||
K 14 A 87-100 Verbrauchsstelle
|
||||
13 Zur Nullstellung
|
||||
erreichte Fort-
|
||||
schrittszahl
|
||||
K 10 N 101-110 Letzte beim „Datum zur Nullstellung“ (Satzart
|
||||
511) erreichte Eingangsfortschrittszahl
|
||||
14 Leer M 18 A 111-128 mit BLANKS gefüllt
|
||||
K = Kann M = Muß
|
||||
|
||||
VDA-Empfehlung 4905 - 19 - 4. Ausgabe vom April 1996
|
||||
|
||||
A = Alphanumerisch N = Numerisch
|
||||
|
||||
VDA-Empfehlung 4905 - 20 - 4. Ausgabe vom April 1996
|
||||
|
||||
Anlage 6
|
||||
Strukturbeschreibung für Satzart 517, Satzlänge: 128, Kannsatz
|
||||
Packmitteldaten
|
||||
Version 01
|
||||
Pos. Datenelement K
|
||||
M
|
||||
LG.
|
||||
BYT
|
||||
AN von-
|
||||
bis
|
||||
Verbale Beschreibung
|
||||
01 Satzart M 3 N 1-3 Konstant"517"
|
||||
02 Versions-
|
||||
Nummer
|
||||
M 2 N 4-5 Kennzeichnung der Aktualität einer Satzart,
|
||||
siehe auch Satzart 511
|
||||
03 Packmittel-
|
||||
Nummer Kunde
|
||||
M 22 A 6-27 Identnummer, die der Kunde einem Packmittel
|
||||
zuordnet, linksbündiger Eintrag.
|
||||
04 Packmittel-
|
||||
Nummer
|
||||
Lieferant
|
||||
K 22 A 28-49 Identnummer, die der Lieferant einem Packmittel
|
||||
zuordnet, linksbündiger Eintrag mit führenden
|
||||
Nullen für Packmitteltyp
|
||||
05 Fassungsver-
|
||||
mögen
|
||||
M 7 N 50-56 Dem Packmittel für die Sachnummer zugeord-
|
||||
nete, rechnerische Füllmenge, rechtsbündiger
|
||||
Eintrag mit führenden Nullen, keine Dezimal-
|
||||
stelle
|
||||
06 Leer M 72 A 57-128 Mit BLANKS gefüllt
|
||||
|
||||
K = Kann M = Muß
|
||||
A = Alphanumerisch N = Numerisch
|
||||
|
||||
VDA-Empfehlung 4905 - 21 - 4. Ausgabe vom April 1996
|
||||
|
||||
Anlage 7
|
||||
Strukturbeschreibung für Satzart 518, Satzlänge: 128, Kannsatz
|
||||
Textdaten
|
||||
Version 01
|
||||
Pos. Datenelement K
|
||||
M
|
||||
LG.
|
||||
BYT
|
||||
AN von-
|
||||
bis
|
||||
Verbale Beschreibung
|
||||
01 Satzart M 3 N 1-3 Konstant"518"
|
||||
02 Versions-
|
||||
Nummer
|
||||
M 2 N 4-5 Kennzeichnung der Aktualität einer Satzart,
|
||||
siehe auch Satzart 511
|
||||
03 Lieferabruf
|
||||
Text 1
|
||||
M 40 A 6-45 Linksbündiger Eintrag
|
||||
04 Lieferabruf
|
||||
Text 2
|
||||
K 40 A 46-85 Linksbündiger Eintrag
|
||||
05 Lieferabruf
|
||||
Text 3
|
||||
K 40 A 86-125 Linksbündiger Eintrag
|
||||
06 Leer M 3 A 126-128 Mit BLANKS gefüllt
|
||||
|
||||
K = Kann M = Muß
|
||||
A = Alphanumerisch N = Numerisch
|
||||
|
||||
VDA-Empfehlung 4905 - 22 - 4. Ausgabe vom April 1996
|
||||
|
||||
Anlage 8
|
||||
Strukturbeschreibung für Satzart 519, Satzlänge: 128, Mußsatz
|
||||
Nachsatz Lieferabrufdaten
|
||||
Version 02
|
||||
Pos. Datenelement K
|
||||
M
|
||||
LG.
|
||||
BYT
|
||||
AN von-
|
||||
bis
|
||||
Verbale Beschreibung
|
||||
01 Satzart M 3 N 1-3 Konstant"519"
|
||||
02 Versions-
|
||||
Nummer
|
||||
M 2 N 4-5 Kennzeichnung der Aktualität einer Satzart,
|
||||
siehe auch Satzart 511
|
||||
03 Zähler
|
||||
Satzart 511
|
||||
M 7 N 6-12 Anzahl übertragene Satzart 511
|
||||
04 Zähler
|
||||
Satzart 512
|
||||
M 7 N 13-19 Anzahl übertragene Satzart 512
|
||||
05 Zähler
|
||||
Satzart 513
|
||||
M 7 N 20-26 Anzahl übertragene Satzart 513
|
||||
06 Zähler
|
||||
Satzart 514
|
||||
M 7 N 27-33 Anzahl übertragene Satzart 514
|
||||
07 Zähler
|
||||
Satzart 517
|
||||
M 7 N 34-40 Anzahl übertragene Satzart 517
|
||||
08 Zähler
|
||||
Satzart 518
|
||||
M 7 N 41-47 Anzahl übertragene Satzart 518
|
||||
09 Zähler
|
||||
Satzart 519
|
||||
M 7 N 48-54 Anzahl übertragene Satzart 519
|
||||
10 Zähler
|
||||
Satzart 515
|
||||
M 7 N 55-61 Anzahl übertragene Satzart 515
|
||||
11 Leer M 67 A 62-128 Mit BLANKS gefüllt
|
||||
|
||||
K = Kann M = Muß
|
||||
A = Alphanumerisch N = Numerisch
|
||||
|
||||
VDA-Empfehlung 4905 - 23 - 4. Ausgabe vom April 1996
|
||||
|
||||
Anlage 9
|
||||
Schlüsselverzeichnis
|
||||
Pos. Schlüssel Schlüsselinhalt in der Satzart
|
||||
01 Werk-Kunde Max. 3stelliger alphanumerischer Schlüssel, muß mit
|
||||
dem Datenersteller abgestimmt werden
|
||||
512
|
||||
02 Mengeneinheit Aus der Vielzahl von anwendereigenen Mengen- und
|
||||
Maßein-heiten kommt in Verbindung mit dem Liefer-
|
||||
abrufverfahren für die Schnittstelle zwischen Kunde
|
||||
und Lieferant nur eine gezielte Auswahl in Betracht,
|
||||
um die Bedarfszahlen (Mengen) für das Produktions-
|
||||
material dimensionieren zu können:
|
||||
Verschlüsselte Form:
|
||||
ST = Stück
|
||||
M = Meter
|
||||
M2 = Quadratmeter
|
||||
M3 = Kubikmeter
|
||||
L = Liter
|
||||
T = Tonne
|
||||
KG = Kilogramm
|
||||
KM = Kilometer
|
||||
Alle verpackungsorientierten Mengeneinheiten (z.B.
|
||||
Dutzend, Gros, Satz, Tüte, Schachtel, Sack usw.)
|
||||
sind nicht zulässig. Umrechnung - ggf. mit Vergabe
|
||||
neuer Sachnummer - auf die zulässige Mengeneinheit
|
||||
ist erforderlich, wobei die verpackungs-orientierte
|
||||
Mengeneinheit bei Bedarf in der Bezeichnung der
|
||||
Lieferung oder Leistung angegeben werden kann.
|
||||
512
|
||||
03 Anlieferungs-
|
||||
Intervall
|
||||
Verschlüsselte Form wie folgt:
|
||||
L = Gemäß Abrufdatum
|
||||
T = Täglich
|
||||
W = Wöchentlich
|
||||
M = Monatlich
|
||||
bzw. Tabelle des Kunden beachten
|
||||
512
|
||||
04 Verwendungs-
|
||||
Schlüssel
|
||||
Verschlüsselte Form wie folgt:
|
||||
S = Serie
|
||||
E = Ersatz allgemein
|
||||
U = Serie und Ersatz
|
||||
V = Versuch
|
||||
P = Pilot
|
||||
Z = Zusatzbedarf
|
||||
M = Erstmuster
|
||||
Y = Muster
|
||||
X = Sonstige
|
||||
512
|
||||
|
||||
VDA-Empfehlung 4905 - 24 - 4. Ausgabe vom April 1996
|
||||
|
||||
Fortsetzung Anlage 9
|
||||
Pos. Schlüssel Schlüsselinhalt in der Satzart
|
||||
05 Abruf-Datum Dieses Feld enthält verschiedene
|
||||
Darstellungsformen:
|
||||
1. Form JJMMTT:
|
||||
bedeutet Tagesdatum als Eintrefftermin.
|
||||
2. Verschlüsselte Formen:
|
||||
000000
|
||||
Kennzeichnet das letzte Abruf - Feld einer Sach-
|
||||
nummer im vorliegenden Lieferabruf. Das dazuge-
|
||||
hörige Mengenfeld sowie alle weiteren Abruffelder
|
||||
des Datensatzes sind BLANK.
|
||||
222222
|
||||
Kennzeichnet, daß für die Sachnummer kein
|
||||
Bedarf vorliegt. Das dazugehörige Mengenfeld
|
||||
sowie alle weiteren Abruffelder dieser Satzart sind
|
||||
BLANK.
|
||||
333333
|
||||
Kennzeichnet die dazugehörige Menge als
|
||||
RÜCKSTAND.
|
||||
444444
|
||||
Kennzeichnet die dazugehörige Menge als
|
||||
SOFORTBEDARF.
|
||||
555555
|
||||
Kennzeichnet, daß die folgenden Abruffelder Ab-
|
||||
rufmengen enthalten, die sich auf den im Abruf-
|
||||
datum angegebenen Zeitraum beziehen. Das da-
|
||||
zugehörige Mengenfeld ist auf „Null“ zu setzen.
|
||||
Wenn alle Mengen einer Sachnummer sich auf
|
||||
Zeiträume beziehen, beginnt das erste Abruffeld
|
||||
mit ‘555555’.
|
||||
Das Abrufdatum kann wie folgt aussehen:
|
||||
JJWWWW
|
||||
Bedarf für den Zeitraum von Woche WW bis
|
||||
Woche WW
|
||||
JJMM00
|
||||
Bedarf für Monat MM
|
||||
JJ00WW
|
||||
Bedarf für Woche WW
|
||||
999999
|
||||
Kennzeichnet das Mengenfeld, das im Lieferabruf
|
||||
unter dem Termin „Rest“ die Vorschaumengen
|
||||
mehrerer Monate enthalten kann.
|
||||
Alle als JJ, MM und TT verwendeten Zahlenwerte
|
||||
entsprechen dem gregorianischen Kalender.
|
||||
|
||||
513, 514
|
||||
|
||||
VDA-Empfehlung 4905 - 25 - 4. Ausgabe vom April 1996
|
||||
|
||||
|
||||
79
docs/walkthrough.md
Normal file
79
docs/walkthrough.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# ERP EDI Bridge v2.5 – Komplettübersicht & Walkthrough
|
||||
|
||||
Die **ERP EDI Bridge** ist eine moderne Web-App (basierend auf Electron oder Browser), die den Datenaustausch zwischen ERP-Systemen und Partnern durch Konvertierung von VDA- und EDIFACT-Nachrichten vereinfacht.
|
||||
|
||||
---
|
||||
|
||||
## 🛠 Hauptfunktionen
|
||||
|
||||
### 1. Multi-Format Converter
|
||||
Unterstützt bidirektionale Konvertierung mit verschiedenen Business-Logiken:
|
||||
- **VDA 4913 → Bosch DESADV**: Erzeugt EDIFACT DESADV Nachrichten inkl. NAD-Anreicherung und Gewichts-Editor.
|
||||
- **VDA 4913 → IFM DELVRY03**: Erzeugt IFM-spezifische XML-IDocs.
|
||||
- **DELFOR → VDA 4905**: Konvertiert EDIFACT Lieferabrufe in das VDA-Format.
|
||||
- **IFM DELFOR → VDA 4905**: Spezieller Flow für IFM D04A Nachrichten.
|
||||
- **INVRPT → VDA 4913**: Konvertiert EDIFACT Lagerbestandsberichte in VDA EDL36.
|
||||
|
||||
### 2. Visueller EDI Editor (Neu!)
|
||||
Ein interaktiver Editor zur strukturierten Bearbeitung von EDI-Dateien:
|
||||
- **Segment-Ansicht**: Stellt EDIFACT-Segmente (UNH, BGM, NAD, etc.) als Karten mit bearbeitbaren Feldern dar.
|
||||
- **VDA-Unterstützung**: Bearbeitung von VDA-Sätzen mit Live-Längenprüfung, um die Festlängen-Struktur beizubehalten.
|
||||
- **Integration**: Dateien können direkt hochgeladen oder per "Edit"-Button aus dem Konvertierungsergebnis übernommen werden.
|
||||
- **Export**: Generiert korrekte EDI-Dateien (inkl. UNA-Header und Separatoren) mit dem Suffix `_edited`.
|
||||
|
||||
### 3. Premium EDI-Viewer (Neu!)
|
||||
Visualisiert EDI-Daten als moderne, druckfreundliche Dokumente mit erweitertem Datenumfang:
|
||||
- **DELFOR**: Lieferplan mit Abrufmengen, Zeitplan (Wochen/Monate) und Lieferhistorie.
|
||||
- **DESADV**: Lieferschein mit Anzeige von Kunden- vs. Lieferanten-Materialnummern, Chargennummern (`PIA+NB`) und detaillierter Verpackungsauflistung (`PAC`).
|
||||
- **INVRPT**: Lagerbestandsbericht mit Bestandsmengen, Lagerorten und Material-Revisionen.
|
||||
- **VDA 4913**: Transport-Hierarchie (Transport -> Lieferschein -> Position) mit VDA-Labelnummern und Packstücken.
|
||||
- **Interaktivität**: Nutzung von Lucide-Icons für bessere Scanbarkeit und integrierte Druckfunktion.
|
||||
|
||||
### 4. Automatisierung (Watcher)
|
||||
*Nur in der Electron-Version verfügbar:*
|
||||
- **Ordner-Überwachung**: Überwacht einen Eingangsordner auf neue Dateien.
|
||||
- **Auto-Konvertierung**: Konvertiert Dateien basierend auf hinterlegten Regeln vollautomatisch.
|
||||
- **Status-Dashboard**: Zeigt Live-Logs und den Status des Watchers in den Einstellungen an.
|
||||
|
||||
---
|
||||
|
||||
## 🏗 Technische Struktur
|
||||
|
||||
| Komponent | Beschreibung |
|
||||
|-----------|--------------|
|
||||
| `index.html` | Zentrales UI mit Glassmorphism-Design und Tab-Navigation. |
|
||||
| `js/app.js` | Haupt-Orchestrierung der UI und Converter-Logik. |
|
||||
| `js/editor.js` | Logik für den visuellen Segment-Editor. |
|
||||
| `js/viewer.js` | Parsing und Rendering der Dokumententypen (XSL-ähnlich). |
|
||||
| `js/watcher-bridge.js` | Schnittstelle zum Electron Dateisystem-Watcher. |
|
||||
| `styles.css` | Modernes Dark-Mode/Glassmorphism Design-System. |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Installation & Start (Electron)
|
||||
|
||||
1. **Voraussetzung**: [Node.js](https://nodejs.org/) (v18+) installieren.
|
||||
2. **Setup**:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
3. **Start**:
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> Die App kann auch direkt über die `index.html` im Browser genutzt werden (ohne Watcher-Funktionalität).
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Einstellungen & Konfiguration
|
||||
In den **Settings** können Netzwerkpfade für den Watcher gewählt und die Kundennummern-Zuordnung (`CUSTOMER_MAPPING`) konfiguriert werden.
|
||||
|
||||
### Intelligente Formaterkennung (Auto-Detect)
|
||||
Die App erkennt beim Hochladen einer Datei automatisch das Format und den Partner:
|
||||
- **VDA Dateien**: Identifikation über die Kundennummer im Satz 711/511.
|
||||
- **EDIFACT Dateien**: Identifikation über den Absender im `UNB`-Segment (Sender ID) oder `NAD+BY`.
|
||||
- **Mapping**: Ist die ID in der `CUSTOMER_MAPPING` (Einstellungen) hinterlegt, wird automatisch der richtige Konverter (z.B. DELFOR → VDA 4905) aktiviert.
|
||||
|
||||
Die Konfiguration wird lokal im Browser-Storage gespeichert oder kann aus einer `config.txt` geladen werden.
|
||||
578
docs/zf_examples.txt
Normal file
578
docs/zf_examples.txt
Normal file
@@ -0,0 +1,578 @@
|
||||
|
||||
Version ZF 1.3.9 – VDA 1.3
|
||||
Base UN D.07A S3
|
||||
Release date 2025-10-07
|
||||
Print 2025-10-07
|
||||
Author VDA / ZF Friedrichshafen AG
|
||||
|
||||
|
||||
ZF Global DESADV
|
||||
VDA 4987
|
||||
Message Examples
|
||||
----------------Page (0) Break----------------
|
||||
ZF Global DESADV Page 2 of 16
|
||||
|
||||
|
||||
ZF Friedrichshafen AG Version: ZF 1.3.9 - VDA 1.3
|
||||
|
||||
Content
|
||||
|
||||
1. Designated use ........................................................................................................................................ 3
|
||||
2. Validation Self-Service ............................................................................................................................ 3
|
||||
3. Examples ................................................................................................................................................... 4
|
||||
3.1. One delivery note, one line item, outer / inner packaging, packaging aid ............................. 5
|
||||
3.2. Delivery with two articles, simplified handling units, packaging aid ....................................... 6
|
||||
3.3. Delivery with one article, different batches with production-/expiry date ............................. 8
|
||||
3.4. Delivery with one article, same batch, different quantities per pack .................................... 10
|
||||
3.5. Delivery with different articles, inner/outer packaging (mixed pallet) .................................. 12
|
||||
3.6. Delivery note with serial numbers ................................................................................................. 14
|
||||
3.7. Delivery note without packaging information ............................................................................. 15
|
||||
|
||||
|
||||
|
||||
|
||||
----------------Page (1) Break----------------
|
||||
ZF Global DESADV Page 3 of 16
|
||||
|
||||
|
||||
ZF Friedrichshafen AG Version: ZF 1.3.9 - VDA 1.3
|
||||
|
||||
1. Designated use
|
||||
This document contains examples for the specification of the ZF Global DESADV for typical
|
||||
delivery processes and packaging scenarios of the ZF Group.
|
||||
The specific characteristics of the particular DESADV delivery messages must be coordinated
|
||||
with the respective ZF locations.
|
||||
Further detailed information on the use of Global DESADV and packaging examples are
|
||||
described in the VDA recommendation "VDA 4987 - Despatch Advice with EDI". Download
|
||||
at www.vda.de
|
||||
|
||||
2. Validation Self-Service
|
||||
To syntactically validate a ZF Global DESADV message, ZF Friedrichshafen offers a self-
|
||||
service on zf.com/EDI. After entering the test message, the service displays a report that
|
||||
supports the identification of errors.
|
||||
The self-service can be accessed via the following link. Registration is not required.
|
||||
ZF DESADV Validation
|
||||
|
||||
|
||||
----------------Page (2) Break----------------
|
||||
ZF Global DESADV Page 4 of 16
|
||||
|
||||
|
||||
ZF Friedrichshafen AG Version: ZF 1.3.9 - VDA 1.3
|
||||
|
||||
3. Examples
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----------------Page (3) Break----------------
|
||||
ZF Global DESADV Page 5 of 16
|
||||
|
||||
|
||||
ZF Friedrichshafen AG Version: ZF 1.3.9 - VDA 1.3
|
||||
|
||||
3.1. One delivery note, one line item, outer / inner packaging, packaging aid
|
||||
UNA:+.? ' Service string advice
|
||||
UNB+UNOC:3+SENDER-
|
||||
ID+0013000046ZFCI1+230712:0716+1++++++1'
|
||||
Header segment of the data exchange file
|
||||
UNH+1+DESADV:D:07A:UN:GAVF13' Message header segment
|
||||
BGM+351+00123457+9' Despatch note no.
|
||||
DTM+137:20230712:102' Date of message
|
||||
DTM+11:20230712:102' Despatch date
|
||||
DTM+132:20230713:102' Estimated arrival date
|
||||
MEA+AAX+AAD+KGM:912.504' Gross weight of shipment
|
||||
MEA+AAX+AAL+KGM:804.404' Net weight of shipment
|
||||
RFF+CRN:923497' Shipment number
|
||||
NAD+BY+1234::91++ZF Braking Systems Poland Sp. z
|
||||
o.o+ul. Leonarda da Vinci+Gliwice++44-121+PL'
|
||||
Customer/buyer (ID) and address
|
||||
NAD+SE+161616::92++Supplier+Street+City++ZIP+DE' ZF Supplier number and address
|
||||
NAD+SF+161616::92++Supplier+Street+City++ZIP+DE' Ship from (despatcher)
|
||||
NAD+ST+4275::92++ZF Braking Systems Poland Sp. z
|
||||
o.o+ul. Leonarda da Vinci 7+Gliwice++44-121+PL'
|
||||
ZF Ship to code and address
|
||||
LOC+11+GR' Place of discharge = unloading point, ID and address
|
||||
TOD+6++FCA' Term of delivery (Free Carrier)
|
||||
TDT+12++30' Means of transport at departure (Road transport)
|
||||
CPS+1++3' Outer packaging type
|
||||
PAC+1+:35:AAD+PP000001208::92' Number and type PP000001208 of outer packaging (pallet)
|
||||
PCI+17+++6J::5' Label type (master label)
|
||||
GIN+ML+9999960' Package unit number #9999960 (master label)
|
||||
GIN+AW+9998219+9998218+9998217+9998216+9998215' Contained package units on pallet
|
||||
PAC+1+:37:AAD+PL000001208::92' Packaging aid, number and type PL000001208
|
||||
CPS+2++1' Inner package group
|
||||
PAC+5+:35:AAD+PC000004314::92' 5 package units of type PC000004314
|
||||
QTY+52:150:PCE' Contained quantity per pack (150 pieces)
|
||||
PCI+17+++1J::5' Label type (single label)
|
||||
GIR+1+1B00860112:BX' Batch #1B00860112 of material (only if batch is required)
|
||||
GIN+ML+9998219+9998218+9998217+9998216+9998215' Package unit numbers (single labels)
|
||||
PAC+5+:37:AAC+PL000000043::92' Packaging aid type PL000000043
|
||||
LIN+++00006030401799:IN' Part number of customer
|
||||
PIA+1+00077735:SA' Part number of supplier
|
||||
IMD+++:::PART X' Description of parts
|
||||
QTY+12:750:PCE' Delivered quantity 750 pieces (5x150)
|
||||
ALI+DE' Country of origin: DE (Germany)
|
||||
RFF+AAU:90112581:1' Delivery note number and line item
|
||||
DTM+171:20160502:102' Delivery note date
|
||||
RFF+ON:5500000001:00010' Scheduling agreement or order no. and line item
|
||||
UNT+37+1' End of message
|
||||
UNZ+1+1' End of data transmission
|
||||
|
||||
----------------Page (4) Break----------------
|
||||
ZF Global DESADV Page 6 of 16
|
||||
|
||||
|
||||
ZF Friedrichshafen AG Version: ZF 1.3.9 - VDA 1.3
|
||||
|
||||
3.2. Delivery with two articles, simplified handling units, packaging aid
|
||||
UNA:+.? ' Service string advice
|
||||
UNB+UNOC:3+SENDER-
|
||||
ID+0013000046ZFCI1+230712:0716+1++++++1'
|
||||
Header segment of the data exchange file
|
||||
UNH+1+DESADV:D:07A:UN:GAVF13' Message header segment
|
||||
BGM+351+00123457+9' Despatch note no.
|
||||
DTM+137:20230712:102' Date of message
|
||||
DTM+11:20230712:102' Despatch date
|
||||
DTM+132:20230713:102' Estimated arrival date
|
||||
MEA+AAX+AAD+KGM:912.504' Gross weight of shipment
|
||||
MEA+AAX+AAL+KGM:804.404' Net weight of shipment
|
||||
RFF+CRN:00123457' Shipment number
|
||||
NAD+BY+1234::91++ZF Braking Systems Poland Sp. z
|
||||
o.o+ul. Leonarda da Vinci+Gliwice++44-121+PL'
|
||||
Customer/buyer (ID) and address
|
||||
NAD+SE+161616::92++Supplier+Street+City++ZIP+DE' ZF Supplier number and address
|
||||
NAD+SF+161616::92++Supplier+Street+City++ZIP+DE' Ship from (despatcher)
|
||||
NAD+ST+4275::92++ZF Braking Systems Poland Sp. z
|
||||
o.o+ul. Leonarda da Vinci 7+Gliwice++44-121+PL'
|
||||
ZF Ship to code and address
|
||||
LOC+11+GR' Place of discharge = unloading point, ID and address
|
||||
TOD+6++FCA' Term of delivery (Free Carrier)
|
||||
TDT+12++30' Means of transport at departure (Road transport)
|
||||
CPS+1++4' First Simplified handling unit
|
||||
PAC+1+:35:AAD+PC000001100::92' 1 package unit of type PC000001100
|
||||
QTY+52:300:PCE' Contained quantity per pack (300 pieces)
|
||||
PCI+17+++1J::5' Label type (single label)
|
||||
GIR+1+0000273362:BX' Batch #0000273362 (only if batch is required)
|
||||
GIN+ML+01583207' Package unit number #01583207 (single label)
|
||||
PAC+1+:37:AAD+PT000011575::92' Packaging aid type PT000011575
|
||||
LIN+++32488422:IN' Part number of customer
|
||||
PIA+1+00000000004100765:SA' Part number of supplier
|
||||
IMD+++11::272:AUR0009083 DIAPHRAGM' Description of parts
|
||||
QTY+12:300:PCE' Delivered quantity 300 pieces (1x300)
|
||||
ALI+DE' Country of origin: DE (Germany)
|
||||
RFF+AAU:90112591:1' Delivery note number #90112591 and line item #1
|
||||
DTM+171:20230712:102' Delivery note date
|
||||
RFF+ON:5500000748:000001' Scheduling agreement or order no. and line item
|
||||
CPS+2++4' Second simplified handling unit
|
||||
PAC+1+:35:AAD+PC000001100::92' One package unit of type PC000001100
|
||||
QTY+52:200:PCE' Contained quantity per pack (200 pieces)
|
||||
PCI+17+++1J::5' Label type (single label)
|
||||
GIR+1+0000273978:BX' Batch #0000273978 (only if batch is required)
|
||||
GIN+ML+01583208' Package unit numbers (single label)
|
||||
PAC+1+:37:AAD+PT000011575::92' Packaging aid type PT000011575
|
||||
LIN+++32484663:IN' Part number of customer
|
||||
PIA+1+13491:SA' Part number of supplier
|
||||
IMD+++:::OR 7,5 X 2,5' Description of parts
|
||||
QTY+12:200:PCE' Delivered quantity 200 pieces (1x200)
|
||||
ALI+IT' Country of origin: IT (Italy)
|
||||
RFF+AAU:90112591:2' Delivery note number #90112591 and line item #2
|
||||
DTM+171:20230712:102' Delivery note date
|
||||
RFF+ON:5500001069:00001' Scheduling agreement or order no. and line item
|
||||
UNT+46+1' End of message
|
||||
----------------Page (5) Break----------------
|
||||
ZF Global DESADV Page 7 of 16
|
||||
|
||||
|
||||
ZF Friedrichshafen AG Version: ZF 1.3.9 - VDA 1.3
|
||||
|
||||
|
||||
|
||||
UNZ+1+000002529'
|
||||
End of data transmission
|
||||
----------------Page (6) Break----------------
|
||||
ZF Global DESADV Page 8 of 16
|
||||
|
||||
|
||||
ZF Friedrichshafen AG Version: ZF 1.3.9 - VDA 1.3
|
||||
|
||||
3.3. Delivery with one article, different batches with production-/expiry date
|
||||
UNA:+.? ' Service string advice
|
||||
UNB+UNOC:3+SENDER-
|
||||
ID+0013000046ZFCI1+230712:0716+1++++++1'
|
||||
Header segment of the data exchange file
|
||||
UNH+1+DESADV:D:07A:UN:GAVF13' Message header segment
|
||||
BGM+351+00123457+9' Despatch note no.
|
||||
DTM+137:20230712:102' Date of message
|
||||
DTM+11:20230712:102' Despatch date
|
||||
DTM+132:20230713:102' Estimated arrival date
|
||||
MEA+AAX+AAD+KGM:912.504' Gross weight of shipment
|
||||
MEA+AAX+AAL+KGM:804.404' Net weight of shipment
|
||||
RFF+CRN:00123457' Shipment number
|
||||
NAD+BY+1234::91++ZF Braking Systems Poland Sp. z
|
||||
o.o+ul. Leonarda da Vinci+Gliwice++44-121+PL'
|
||||
Customer/buyer (ID) and address
|
||||
NAD+SE+161616::92++Supplier+Street+City++ZIP+DE' ZF Supplier number and address
|
||||
NAD+SF+161616::92++Supplier+Street+City++ZIP+DE' Ship from (despatcher)
|
||||
NAD+ST+4275::92++ZF Braking Systems Poland Sp. z
|
||||
o.o+ul. Leonarda da Vinci 7+Gliwice++44-121+PL'
|
||||
ZF Ship to code and address
|
||||
LOC+11+GR' Place of discharge = unloading point, ID and address
|
||||
TOD+6++FCA' Term of delivery (Free Carrier)
|
||||
TDT+12++30' Means of transport at departure (Road transport)
|
||||
CPS+1++3' Outer packaging
|
||||
PAC+1+:35+PP000001208::92+' Number and type PP000001208 of outer packaging (pallet)
|
||||
PCI+17+++6J::5' Label type (master label)
|
||||
GIN+ML+919589569' Package unit number #919589569 (master label)
|
||||
GIN+AW+01583207+01584121+01584122' Contained package units on pallet
|
||||
PAC+1+:37:AAD+PL000001208::92' Packaging aid, number and type PL000001208
|
||||
CPS+2++1' Firs inner package group
|
||||
PAC+1+:35:AAD+PC000001100::92' 1 package unit of type PC000001100
|
||||
QTY+52:300:PCE'
|
||||
Contained quantity per pack (300 pieces)
|
||||
PCI+17+++1J::5' Label type (single label)
|
||||
GIR+1+0000273362:BX' Batch #0000273362 of material
|
||||
DTM+94:20230207:102' Production/manufacture date (if required)
|
||||
DTM+36:20250107:102' Expiry date (if required)
|
||||
GIN+ML+01583207' Package unit number #01583207 (single label)
|
||||
PAC+1+:37:AAD+PL000000043::92' Packaging aid type PL000000043
|
||||
LIN+++32488422:IN' Part number of customer
|
||||
PIA+1+00000000004100765:SA' Part number of supplier
|
||||
IMD+++11::272:AUR0009083 DIAPHRAGM' Description of parts
|
||||
QTY+12:300:PCE' Delivered quantity 300 pieces
|
||||
ALI+DE' Country of origin: DE (Germany)
|
||||
RFF+AAU:90112591:1' Delivery note number#90112591 and line item #1
|
||||
DTM+171:20230712:102' Delivery note date
|
||||
RFF+ON:5500000748:000001' Scheduling agreement or order no. and line item
|
||||
CPS+3++1' Second inner package group
|
||||
PAC+2+:35:AAD+PC000001100::92' 2 package units of type PC000001100
|
||||
QTY+52:350:PCE' Contained quantity per pack (350 pieces)
|
||||
PCI+17+++1J::5' Label type (single label)
|
||||
GIR+1+0000273463:BX' Batch #0000273363 of material (new batch of same material)
|
||||
DTM+94:20230107:102' Production/manufacture date (if required)
|
||||
DTM+36:20250207:102' Expiry date (if required)
|
||||
GIN+ML+01584121+01584122' Package unit number (single label)
|
||||
PAC+1+:37:AAD+PT000011575::92' Packaging aid type PT000011575
|
||||
LIN+++32488422:IN' Part number of customer
|
||||
----------------Page (7) Break----------------
|
||||
ZF Global DESADV Page 9 of 16
|
||||
|
||||
|
||||
ZF Friedrichshafen AG Version: ZF 1.3.9 - VDA 1.3
|
||||
|
||||
PIA+1+000000000041000904:SA'
|
||||
Part number of supplier
|
||||
IMD+++11::272:AUR0009083 DIAPHRAGM' Description of parts
|
||||
QTY+12:700:PCE' Delivered quantity 700 pce (2x350)
|
||||
ALI+DE' Country of origin: DE (Germany)
|
||||
RFF+AAU:90112591:2'
|
||||
Delivery note number #90112591 and line item #2
|
||||
Attention: a new batch of the same material requires a new delivery
|
||||
note line
|
||||
DTM+171:20230712:102' Delivery note date
|
||||
RFF+ON:5500000748:000001' Scheduling agreement or order no. and line item
|
||||
UNT+52+1' End of message
|
||||
UNZ+1+000002529' End of data transmission
|
||||
|
||||
----------------Page (8) Break----------------
|
||||
ZF Global DESADV Page 10 of 16
|
||||
|
||||
|
||||
ZF Friedrichshafen AG Version: ZF 1.3.9 - VDA 1.3
|
||||
|
||||
3.4. Delivery with one article, same batch, different quantities per pack
|
||||
UNA:+.? ' Service string advice
|
||||
UNB+UNOC:3+SENDER-
|
||||
ID+0013000046ZFCI1+230712:0716+1++++++1'
|
||||
Header segment of the data exchange file
|
||||
UNH+1+DESADV:D:07A:UN:GAVF13' Message header segment
|
||||
BGM+351+00123457+9' Despatch note no.
|
||||
DTM+137:20230712:102' Date of message
|
||||
DTM+11:20230712:102' Despatch date
|
||||
DTM+132:20230713:102' Estimated arrival date
|
||||
MEA+AAX+AAD+KGM:912.504' Gross weight of shipment
|
||||
MEA+AAX+AAL+KGM:804.404' Net weight of shipment
|
||||
RFF+CRN:00123457' Shipment number
|
||||
NAD+BY+1234::91++ZF Braking Systems Poland Sp. z
|
||||
o.o+ul. Leonarda da Vinci+Gliwice++44-121+PL'
|
||||
Customer/buyer (ID) and address
|
||||
NAD+SE+161616::92++Supplier+Street+City++ZIP+DE' ZF Supplier number and address
|
||||
NAD+SF+161616::92++Supplier+Street+City++ZIP+DE' Ship from (despatcher)
|
||||
NAD+ST+4275::92++ZF Braking Systems Poland Sp. z
|
||||
o.o+ul. Leonarda da Vinci 7+Gliwice++44-121+PL'
|
||||
ZF Ship to code and address
|
||||
LOC+11+GR' Place of discharge = unloading point, ID and address
|
||||
TOD+6++FCA' Term of delivery (Free Carrier)
|
||||
TDT+12++30' Means of transport at departure (Road transport)
|
||||
CPS+1++3' Outer packaging
|
||||
PAC+1+:35+PP000001208::92+' Number and type of outer packaging (pallet)
|
||||
PCI+17+++6J::5' Label type (master label)
|
||||
GIN+ML+919589569' Package unit number #919589569 (master label)
|
||||
GIN+AW+119589563+119589564+119589565' Contained package units on pallet
|
||||
PAC+1+:37+PL000001208::92' Packaging aid, number and type PL000001208
|
||||
CPS+2++1' First inner package group
|
||||
PAC+2+:35+PC000004314::92' 2 package units of type #PC000004314
|
||||
QTY+52:400:PCE' Contained quantity per pack (400 pieces)
|
||||
PCI+17+++1J::5' Label type (single label)
|
||||
GIR+1+1B00860112:BX' Batch #1B00860112 of material
|
||||
GIN+ML+119589563+119589564' Package unit number (single labels)
|
||||
PAC+2+:37+PL000000043::92' Packaging aid type #PL000000043
|
||||
LIN+10++32482779:IN' Part number of customer
|
||||
PIA+1+S0010881-00-999:SA' Part number of supplier
|
||||
IMD+++:::Flange' Description of parts
|
||||
QTY+11:800:PCE' Partial quantity of this packaging group 800 pce (2x400)
|
||||
QTY+12:1100:PCE' Total quantity of delivery note line 1100 pce (2x400 + 1x300)
|
||||
ALI+DE' Country of origin: DE (Germany)
|
||||
RFF+ON:5500000812:000001' Scheduling agreement or order no. and line item
|
||||
RFF+AAU:91347273:10' Delivery note number #91347273 and line item #10
|
||||
DTM+171:20230712:102' Delivery note date
|
||||
CPS+3++1' Second inner package group
|
||||
PAC+1+:35+PC000004314::92' 1 package unit of type #PC000004314
|
||||
QTY+52:300:PCE' Contained quantity per pack (300 pieces)
|
||||
PCI+17+++1J::5' Label type (single label)
|
||||
GIR+1+1B00860112:BX' Batch #1B00860112 of material
|
||||
GIN+ML+119589565' Package unit number (single labels)
|
||||
PAC+1+:37+PL000000043::92' Packaging aid type #PL000000043
|
||||
LIN+10++32482779:IN' Part number of customer
|
||||
PIA+1+S0010881-00-999:SA' Part number of supplier
|
||||
IMD+++:::Flange' Description of parts
|
||||
QTY+11:300:PCE' Partial quantity of this packaging group 800 pce (1x300)
|
||||
QTY+12:1100:PCE' Total quantity of delivery note line 1100 pce (2x400 + 1x300)
|
||||
ALI+DE' Country of origin: DE (Germany)
|
||||
RFF+ON:5500000812:000001' Scheduling agreement or order no. and line item
|
||||
----------------Page (9) Break----------------
|
||||
ZF Global DESADV Page 11 of 16
|
||||
|
||||
|
||||
ZF Friedrichshafen AG Version: ZF 1.3.9 - VDA 1.3
|
||||
|
||||
RFF+AAU:91347273:10'
|
||||
Delivery note number #91347273 and line item #10
|
||||
Attention: the same delivery note line is required in both packaging
|
||||
groups
|
||||
DTM+171:20230712:102' Delivery note date
|
||||
UNT+54+1' End of message
|
||||
UNZ+1+00000008325381' End of data transmission
|
||||
|
||||
----------------Page (10) Break----------------
|
||||
ZF Global DESADV Page 12 of 16
|
||||
|
||||
|
||||
ZF Friedrichshafen AG Version: ZF 1.3.9 - VDA 1.3
|
||||
|
||||
3.5. Delivery with different articles, inner/outer packaging (mixed pallet)
|
||||
UNA:+.? ' Service string advice
|
||||
UNB+UNOC:3+SENDER-
|
||||
ID+0013000046ZFCI1+230712:0716+1++++++1'
|
||||
Header segment of the data exchange file
|
||||
UNH+1+DESADV:D:07A:UN:GAVF13' Message header segment
|
||||
BGM+351+00123457+9' Despatch note no.
|
||||
DTM+137:20230712:102' Date of message
|
||||
DTM+11:20230712:102' Despatch date
|
||||
DTM+132:20230713:102' Estimated arrival date
|
||||
MEA+AAX+AAD+KGM:912.504' Gross weight of shipment
|
||||
MEA+AAX+AAL+KGM:804.404' Net weight of shipment
|
||||
RFF+CRN:00123457' Shipment number
|
||||
NAD+BY+1234::91++ZF Braking Systems Poland Sp. z
|
||||
o.o+ul. Leonarda da Vinci+Gliwice++44-121+PL'
|
||||
Customer/buyer (ID) and address
|
||||
NAD+SE+161616::92++Supplier+Street+City++ZIP+DE' ZF Supplier number and address
|
||||
NAD+SF+161616::92++Supplier+Street+City++ZIP+DE' Ship from (despatcher)
|
||||
NAD+ST+4275::92++ZF Braking Systems Poland Sp. z
|
||||
o.o+ul. Leonarda da Vinci 7+Gliwice++44-121+PL'
|
||||
ZF Ship to code and address
|
||||
LOC+11+GR' Place of discharge = unloading point, ID and address
|
||||
TOD+6++FCA' Term of delivery (Free Carrier)
|
||||
TDT+12++30' Means of transport at departure (Road transport)
|
||||
CPS+1++3' Outer packaging type
|
||||
PAC+1+:35+PP000001208::92+' Number and type of outer packaging (pallet)
|
||||
PCI+17+++6J::5' Label type (master label)
|
||||
GIN+ML+919589569' Package unit number #919589569 (master label)
|
||||
GIN+AW+119589563+119589564+119589565' Contained package units on pallet
|
||||
PAC+1+:37+PL000001208::92' Packaging aid, number and type PL000001208
|
||||
CPS+2++1' First inner package group
|
||||
PAC+2+:35+PC000004314::92' 2 package units of type PC000004314
|
||||
QTY+52:400:PCE' Contained quantity per pack (400 pieces)
|
||||
PCI+17+++1J::5' Label type (single label)
|
||||
GIR+1+1B00860112:BX' Batch #1B00860112 of material (only if batch is required)
|
||||
GIN+ML+119589563+119589564' Package unit numbers (single labels)
|
||||
PAC+2+:37+PL000000043::92' Packaging aid type PL000000043
|
||||
LIN+10++32482779:IN' Part number of customer
|
||||
PIA+1+S0010881-00-999:SA' Part number of supplier
|
||||
IMD+++:::Flange' Description of parts
|
||||
QTY+12:800:PCE' Delivered quantity 800 (2x400) pieces
|
||||
ALI+DE' Country of origin: DE (Germany)
|
||||
RFF+ON:5500000812:000001' Scheduling agreement or order no. and line item
|
||||
RFF+AAU:0091347273:10' Delivery note number #0091347273 and line item #10
|
||||
DTM+171:20230712:102' Delivery note date
|
||||
CPS+3++1' Second inner package group
|
||||
PAC+1+:35+PC000004321::92' 1 package units of type PC000004321
|
||||
QTY+52:20000:C62' Contained quantity per pack (20000 pieces)
|
||||
PCI+17+++1J::5' Label type (single label)
|
||||
GIR+1+2205247:BX' Batch #2205247 of material (only if batch is required)
|
||||
GIN+ML+119589565' Package unit number (single label)
|
||||
PAC+1+:37+PL000000043::92' Packaging aid type PL000000043
|
||||
LIN+++32484663:IN' Part number of customer
|
||||
PIA+1+13491:SA' Part number of supplier
|
||||
IMD+++:::OR 7,5 X 2,5' Description of parts
|
||||
QTY+12:20000:C62' Delivered quantity 20000 (1x20000) pieces
|
||||
ALI+DE' Country of origin: DE (Germany)
|
||||
RFF+ON:5500001069:00001' Scheduling agreement or order no. and line item
|
||||
RFF+AAU:0091347273:20' Delivery note number #0091347273 and line item #20
|
||||
DTM+171:20230712:102' Delivery note date
|
||||
UNT+52+1' End of message
|
||||
----------------Page (11) Break----------------
|
||||
ZF Global DESADV Page 13 of 16
|
||||
|
||||
|
||||
ZF Friedrichshafen AG Version: ZF 1.3.9 - VDA 1.3
|
||||
|
||||
UNZ+1+00000008325381'
|
||||
End of data transmission
|
||||
|
||||
|
||||
----------------Page (12) Break----------------
|
||||
ZF Global DESADV Page 14 of 16
|
||||
|
||||
|
||||
ZF Friedrichshafen AG Version: ZF 1.3.9 - VDA 1.3
|
||||
|
||||
3.6. Delivery note with serial numbers
|
||||
UNA:+.? ' Service string advice
|
||||
UNB+UNOC:3+SENDER-
|
||||
ID+0013000046ZFCI1+230712:0716+1++++++1'
|
||||
Header segment of the data exchange file
|
||||
UNH+1+DESADV:D:07A:UN:GAVF13' Message header segment
|
||||
BGM+351+00123457+9' Despatch note no.
|
||||
DTM+137:20230712:102' Date of message
|
||||
DTM+11:20230712:102' Despatch date
|
||||
DTM+132:20230713:102' Estimated arrival date
|
||||
MEA+AAX+AAD+KGM:912.504' Gross weight of shipment
|
||||
MEA+AAX+AAL+KGM:804.404' Net weight of shipment
|
||||
RFF+CRN:923497' Shipment number
|
||||
NAD+BY+1234::91++ZF Braking Systems Poland Sp. z
|
||||
o.o+ul. Leonarda da Vinci+Gliwice++44-121+PL'
|
||||
Customer/buyer (ID) and address
|
||||
NAD+SE+161616::92++Supplier+Street+City++ZIP+DE' ZF Supplier number and address
|
||||
NAD+SF+161616::92++Supplier+Street+City++ZIP+DE' Ship from (despatcher)
|
||||
NAD+ST+4275::92++ZF Braking Systems Poland Sp. z
|
||||
o.o+ul. Leonarda da Vinci 7+Gliwice++44-121+PL'
|
||||
ZF Ship to code and address
|
||||
LOC+11+GR' Place of discharge = unloading point, ID and address
|
||||
TOD+6++FCA' Term of delivery (Free Carrier)
|
||||
TDT+12++30' Means of transport at departure (Road transport)
|
||||
CPS+1++3' Outer packaging type
|
||||
PAC+1+:35:AAD+PP000001208::92' Number and type PP000001208 of outer packaging (pallet)
|
||||
PCI+17+++6J::5' Label type (master label)
|
||||
GIN+ML+9999960' Package unit number #9999960 (master label)
|
||||
GIN+AW+9998219+9998218' Contained package units on pallet
|
||||
CPS+2++1' Inner package group
|
||||
PAC+2+:35:AAD+PC000004314::92' 5 package units of type PC000004314
|
||||
QTY+52:150:PCE' Contained quantity per pack (150 pieces)
|
||||
PCI+17+++1J::5' Label type (single label)
|
||||
GIR+1+1B00860112:BX' Batch #1B00860112 of material (only if batch is required)
|
||||
GIN+ML+9998219+9998218' Package unit numbers (single labels)
|
||||
PAC+2+:37:AAC+PL000000043::92' Packaging aid type PL000000043
|
||||
LIN+++00006030401799:IN' Part number of customer
|
||||
PIA+1+00077735:SA' Part number of supplier
|
||||
IMD+++:::PART X' Description of parts
|
||||
QTY+12:300:PCE' Delivered quantity 300 pieces (2x150)
|
||||
ALI+DE' Country of origin: DE (Germany)
|
||||
GIN+BN+SERN01+SERN02’ Serial number #SERN01 and #SERN02
|
||||
RFF+AAU:90112581:1' Delivery note number and line item
|
||||
DTM+171:20160502:102' Delivery note date
|
||||
RFF+ON:5500000001:00010' Scheduling agreement or order no. and line item
|
||||
UNT+37+1' End of message
|
||||
UNZ+1+1' End of data transmission
|
||||
|
||||
|
||||
----------------Page (13) Break----------------
|
||||
ZF Global DESADV Page 15 of 16
|
||||
|
||||
|
||||
ZF Friedrichshafen AG Version: ZF 1.3.9 - VDA 1.3
|
||||
|
||||
3.7. Delivery note without packaging information
|
||||
UNA:+.? ' Service string advice
|
||||
UNB+UNOC:3+SENDER-
|
||||
ID+0013000046ZFCI1+230712:0716+1++++++1'
|
||||
Header segment of the data exchange file
|
||||
UNH+1+DESADV:D:07A:UN:GAVF13' Message header segment
|
||||
BGM+351:::EDINOPACK+00123456+9' Despatch note no. and type (EDINOPACK = Without packaging)
|
||||
DTM+137:20230712:102' Date of message
|
||||
DTM+11:20230712:102' Despatch date
|
||||
DTM+132:20230713:102' Estimated arrival date
|
||||
MEA+AAX+AAD+KGM:912.504' Gross weight of shipment
|
||||
MEA+AAX+AAL+KGM:804.404' Net weight of shipment
|
||||
RFF+CRN:923497' Shipment number
|
||||
NAD+BY+1234::91++ZF Braking Systems Poland Sp. z
|
||||
o.o+ul. Leonarda da Vinci+Gliwice++44-121+PL'
|
||||
Customer/buyer (ID) and address
|
||||
NAD+SE+161616::92++Supplier+Street+City++ZIP+DE' ZF Supplier number and address
|
||||
NAD+SF+161616::92++Supplier+Street+City++ZIP+DE' Ship from (despatcher)
|
||||
NAD+ST+4275::92++ZF Braking Systems Poland Sp. z
|
||||
o.o+ul. Leonarda da Vinci 7+Gliwice++44-121+PL'
|
||||
ZF Ship to code and address
|
||||
LOC+11+GR' Place of discharge = unloading point, ID and address
|
||||
TOD+6++FCA' Term of delivery (Free Carrier)
|
||||
TDT+12++30' Means of transport at departure (Road transport)
|
||||
CPS+1++4' Inner package group #1
|
||||
LIN+++00006030401799:IN' Part number of customer
|
||||
PIA+1+00077735:SA' Part number of supplier
|
||||
IMD+++:::PART X' Description of parts
|
||||
QTY+12:300:PCE' Delivered quantity 300
|
||||
ALI+DE' Country of origin: DE (Germany)
|
||||
RFF+AAU:90112581:1' Delivery note number and line item
|
||||
DTM+171:20160502:102' Delivery note date
|
||||
RFF+ON:5500000001:00010' Scheduling agreement or order no. and line item
|
||||
CPS+2++4' Inner package group #2
|
||||
LIN+++0000603040179A:IN' Part number of customer
|
||||
PIA+1+00077735A:SA' Part number of supplier
|
||||
IMD+++:::PART Y' Description of parts
|
||||
QTY+12:100:PCE' Delivered quantity 300 pieces
|
||||
ALI+DE' Country of origin: DE (Germany)
|
||||
RFF+AAU:90112581:2' Delivery note number and line item
|
||||
DTM+171:20160502:102' Delivery note date
|
||||
RFF+ON:5500000700:00010' Scheduling agreement or order no. and line item
|
||||
UNT+46+1' End of message
|
||||
UNZ+1+1' End of data transmission
|
||||
|
||||
----------------Page (14) Break----------------
|
||||
|
||||
|
||||
|
||||
|
||||
ZF Friedrichshafen AG
|
||||
88038 Friedrichshafen
|
||||
EDI & Process-Integration
|
||||
Germany
|
||||
http://zf.com/EDI
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----------------Page (15) Break----------------
|
||||
8091
docs/zf_guideline.txt
Normal file
8091
docs/zf_guideline.txt
Normal file
File diff suppressed because it is too large
Load Diff
6
dump_delfor.js
Normal file
6
dump_delfor.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('samples/Bosch_ABRUF_KONSI EDIFACT_0190396.vda', 'utf8');
|
||||
const lines = content.replace(/'/g, "'\n").split('\n');
|
||||
lines.forEach(l => {
|
||||
if (l.startsWith('QTY+')) console.log(l);
|
||||
});
|
||||
29
extract-pdf.js
Normal file
29
extract-pdf.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const fs = require('fs');
|
||||
const PDFParser = require("pdf2json");
|
||||
|
||||
function extract(fileIn, fileOut) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const pdfParser = new PDFParser(this, 1);
|
||||
|
||||
pdfParser.on("pdfParser_dataError", errData => reject(errData.parserError));
|
||||
pdfParser.on("pdfParser_dataReady", pdfData => {
|
||||
fs.writeFileSync(fileOut, pdfParser.getRawTextContent());
|
||||
console.log(`Extracted: ${fileOut}`);
|
||||
resolve();
|
||||
});
|
||||
|
||||
pdfParser.loadPDF(fileIn);
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length >= 2) {
|
||||
await extract(args[0], args[1]);
|
||||
} else {
|
||||
await extract('./docs/ZF Global DESADV 1.3.9 EN Examples.pdf', './docs/zf_examples.txt');
|
||||
await extract('./docs/ZF Global DESADV 1.3.9 EN.pdf', './docs/zf_guideline.txt');
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
787
index.html
Normal file
787
index.html
Normal file
@@ -0,0 +1,787 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ERP EDI Bridge | VDA ↔ EDIFACT</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&family=Fira+Code&display=swap"
|
||||
rel="stylesheet">
|
||||
|
||||
<!-- Icons (External) - Error Handled -->
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
<script>
|
||||
window.onerror = function (msg, url, line) {
|
||||
console.error('Global Error: ' + msg + '\nline: ' + line);
|
||||
return false;
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="dashboard">
|
||||
<header>
|
||||
<h1>ERP EDI Bridge</h1>
|
||||
<p class="subtitle">Konvertieren Sie VDA 4913, 4905 und EDIFACT Nachrichten</p>
|
||||
</header>
|
||||
|
||||
<!-- ═══ Page Navigation ═══ -->
|
||||
<nav class="page-nav">
|
||||
<button class="page-nav-btn active" data-page="converter" onclick="app.showPage('converter')">
|
||||
<i data-lucide="repeat"></i>
|
||||
Converter
|
||||
</button>
|
||||
<button class="page-nav-btn" data-page="viewer" onclick="app.showPage('viewer')">
|
||||
<i data-lucide="file-text"></i>
|
||||
EDI Viewer
|
||||
</button>
|
||||
<button class="page-nav-btn" data-page="editor" onclick="app.showPage('editor')">
|
||||
<i data-lucide="edit-3"></i>
|
||||
Editor
|
||||
</button>
|
||||
<button class="page-nav-btn" data-page="history" onclick="app.showPage('history')">
|
||||
<i data-lucide="database"></i>
|
||||
Historie
|
||||
</button>
|
||||
<button class="page-nav-btn" data-page="settings" onclick="app.showPage('settings')">
|
||||
<i data-lucide="settings"></i>
|
||||
Settings
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<!-- Config Upload (Global) -->
|
||||
<div class="config-bar" style="max-width: 1200px; margin: 0 auto 20px auto;">
|
||||
<div class="config-status" id="configStatus">
|
||||
<i data-lucide="settings-2"></i>
|
||||
<span>Keine Konfiguration geladen</span>
|
||||
</div>
|
||||
<button class="btn-secondary" onclick="document.getElementById('configInput').click()">
|
||||
<i data-lucide="upload"></i>
|
||||
Konfig laden
|
||||
</button>
|
||||
<button class="btn-secondary" id="btnSaveConfig" onclick="app.saveConfig()" style="display:none">
|
||||
<i data-lucide="save"></i>
|
||||
Konfig speichern
|
||||
</button>
|
||||
<input type="file" id="configInput" accept=".txt,.ini,.cfg" class="hidden">
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||
<!-- PAGE: CONVERTER -->
|
||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||
<main class="main-card page-content" id="pageConverter">
|
||||
|
||||
<!-- Mode Toggle – 4 modes -->
|
||||
<div class="mode-toggle">
|
||||
<button id="btnOutboundBosch" class="mode-btn active" onclick="app.setMode('outbound-bosch')">
|
||||
<i data-lucide="arrow-up-right"></i>
|
||||
VDA 4913 → Bosch DESADV
|
||||
</button>
|
||||
<button id="btnOutboundZf" class="mode-btn" onclick="app.setMode('outbound-zf')">
|
||||
<i data-lucide="arrow-up-right"></i>
|
||||
VDA 4913 → ZF DESADV
|
||||
</button>
|
||||
<button id="btnOutboundIfm" class="mode-btn" onclick="app.setMode('outbound-ifm')">
|
||||
<i data-lucide="arrow-up-right"></i>
|
||||
VDA 4913 → IFM DELVRY03
|
||||
</button>
|
||||
<button id="btnInboundBosch" class="mode-btn" onclick="app.setMode('inbound-bosch')">
|
||||
<i data-lucide="arrow-down-left"></i>
|
||||
DELFOR → VDA 4905
|
||||
</button>
|
||||
<button id="btnInboundIfm" class="mode-btn" onclick="app.setMode('inbound-ifm')">
|
||||
<i data-lucide="arrow-down-left"></i>
|
||||
IFM DELFOR → VDA 4905
|
||||
</button>
|
||||
<button id="btnInboundInvrpt" class="mode-btn" onclick="app.setMode('inbound-invrpt')">
|
||||
<i data-lucide="arrow-down-left"></i>
|
||||
INVRPT → VDA 4913
|
||||
</button>
|
||||
<button id="btnValidateEdifact" class="mode-btn" onclick="app.setMode('validate-edifact')">
|
||||
<i data-lucide="check-circle"></i>
|
||||
EDIFACT Validieren
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Drop Zone -->
|
||||
<div id="dropZone" class="drop-zone">
|
||||
<i data-lucide="upload-cloud"></i>
|
||||
<h3>Datei hier ablegen oder klicken</h3>
|
||||
<p id="dropText">VDA 4913 Datei auswählen für Konvertierung</p>
|
||||
<input type="file" id="fileInput" class="hidden">
|
||||
</div>
|
||||
|
||||
<!-- Enrichment Section (shown after VDA upload in outbound-bosch mode) -->
|
||||
<div id="enrichmentSection" class="enrichment-section">
|
||||
|
||||
<!-- NAD Segment Editor -->
|
||||
<div class="preview-header" style="margin-bottom: 20px;">
|
||||
<h2>Partneradressen (NAD Segmente)</h2>
|
||||
<span class="badge badge-edi" id="nadSourceBadge">Manuell</span>
|
||||
</div>
|
||||
<div class="nad-grid">
|
||||
<!-- Buyer (BY) -->
|
||||
<div class="nad-card">
|
||||
<div class="nad-card-header"><span class="nad-qualifier">BY</span><span>Käufer (Buyer)</span>
|
||||
</div>
|
||||
<label>ID <input type="text" id="nad-by-id" class="nad-input" value="5060"></label>
|
||||
<label>Qualifier <input type="text" id="nad-by-qual" class="nad-input" value="92"></label>
|
||||
<label>Firma <input type="text" id="nad-by-name" class="nad-input wide"
|
||||
value="Robert Bosch spol. s.r.o."></label>
|
||||
<label>Firma 2 <input type="text" id="nad-by-name2" class="nad-input wide" value=""></label>
|
||||
<label>Straße <input type="text" id="nad-by-street" class="nad-input wide"
|
||||
value="Roberta Bosche 2678"></label>
|
||||
<label>Stadt <input type="text" id="nad-by-city" class="nad-input"
|
||||
value="Ceske Budejovicen"></label>
|
||||
<label>PLZ <input type="text" id="nad-by-zip" class="nad-input" value="37004"></label>
|
||||
<label>Land <input type="text" id="nad-by-country" class="nad-input" value="CZ"
|
||||
maxlength="2"></label>
|
||||
</div>
|
||||
|
||||
<!-- Seller (SE) -->
|
||||
<div class="nad-card">
|
||||
<div class="nad-card-header"><span class="nad-qualifier">SE</span><span>Lieferant
|
||||
(Seller)</span></div>
|
||||
<label>ID <input type="text" id="nad-se-id" class="nad-input" value="0000062671"></label>
|
||||
<label>Qualifier <input type="text" id="nad-se-qual" class="nad-input" value="92"></label>
|
||||
<label>Firma <input type="text" id="nad-se-name" class="nad-input wide"
|
||||
value="Roechling Precision Components"></label>
|
||||
<label>Firma 2 <input type="text" id="nad-se-name2" class="nad-input wide" value=""></label>
|
||||
<label>Straße <input type="text" id="nad-se-street" class="nad-input wide"
|
||||
value="Winter-Ring 3"></label>
|
||||
<label>Stadt <input type="text" id="nad-se-city" class="nad-input" value="Weidenberg"></label>
|
||||
<label>PLZ <input type="text" id="nad-se-zip" class="nad-input" value="95466"></label>
|
||||
<label>Land <input type="text" id="nad-se-country" class="nad-input" value="DE"
|
||||
maxlength="2"></label>
|
||||
</div>
|
||||
|
||||
<!-- Ship-To (ST) -->
|
||||
<div class="nad-card">
|
||||
<div class="nad-card-header"><span class="nad-qualifier">ST</span><span>Warenempfänger
|
||||
(Ship-To)</span></div>
|
||||
<label>ID <input type="text" id="nad-st-id" class="nad-input" value="5060"></label>
|
||||
<label>Qualifier <input type="text" id="nad-st-qual" class="nad-input" value="92"></label>
|
||||
<label>Firma <input type="text" id="nad-st-name" class="nad-input wide"
|
||||
value="Robert Bosch spol. s.r.o."></label>
|
||||
<label>Firma 2 <input type="text" id="nad-st-name2" class="nad-input wide" value=""></label>
|
||||
<label>Straße <input type="text" id="nad-st-street" class="nad-input wide"
|
||||
value="Roberta Bosche 2678"></label>
|
||||
<label>Stadt <input type="text" id="nad-st-city" class="nad-input"
|
||||
value="Ceske Budejovicen"></label>
|
||||
<label>PLZ <input type="text" id="nad-st-zip" class="nad-input" value="37004"></label>
|
||||
<label>Land <input type="text" id="nad-st-country" class="nad-input" value="CZ"
|
||||
maxlength="2"></label>
|
||||
</div>
|
||||
|
||||
<!-- Ship-From (SF) -->
|
||||
<div class="nad-card">
|
||||
<div class="nad-card-header"><span class="nad-qualifier">SF</span><span>Versender
|
||||
(Ship-From)</span></div>
|
||||
<label>ID <input type="text" id="nad-sf-id" class="nad-input" value="0000062671"></label>
|
||||
<label>Qualifier <input type="text" id="nad-sf-qual" class="nad-input" value="92"></label>
|
||||
<label>Firma <input type="text" id="nad-sf-name" class="nad-input wide"
|
||||
value="Roechling Precision Components"></label>
|
||||
<label>Firma 2 <input type="text" id="nad-sf-name2" class="nad-input wide" value=""></label>
|
||||
<label>Straße <input type="text" id="nad-sf-street" class="nad-input wide"
|
||||
value="Winter-Ring 3"></label>
|
||||
<label>Stadt <input type="text" id="nad-sf-city" class="nad-input" value="Weidenberg"></label>
|
||||
<label>PLZ <input type="text" id="nad-sf-zip" class="nad-input" value="95466"></label>
|
||||
<label>Land <input type="text" id="nad-sf-country" class="nad-input" value="DE"
|
||||
maxlength="2"></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Material & Weight Enrichment -->
|
||||
<div class="preview-header" style="margin-top: 40px;">
|
||||
<h2>Positionen & Gewichte</h2>
|
||||
<span class="badge badge-vda">VDA 4913</span>
|
||||
</div>
|
||||
<p class="subtitle">Artikel- und Packmittelgewichte können hier angepasst werden.</p>
|
||||
<div class="table-container">
|
||||
<table id="enrichmentTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Kunden-Mat.</th>
|
||||
<th>Lieferanten-Mat.</th>
|
||||
<th>Menge</th>
|
||||
<th>Einheit</th>
|
||||
<th>Artikelgewicht (kg/Stk)</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Packaging Weight & Dimensions Table -->
|
||||
<div class="preview-header" style="margin-top: 30px;">
|
||||
<h2>Packmittel (Gewichte & Maße)</h2>
|
||||
</div>
|
||||
<p class="subtitle">Alle Felder sind pflicht in DESADV. Fehlende Werte über Konfig-Datei ergänzen.</p>
|
||||
<div class="table-container">
|
||||
<table id="packagingTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Packmittel (Kd)</th>
|
||||
<th>Packmittel (Lief)</th>
|
||||
<th>Anz.</th>
|
||||
<th>Gewicht (kg)</th>
|
||||
<th>Länge (cm)</th>
|
||||
<th>Breite (cm)</th>
|
||||
<th>Höhe (cm)</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 30px; text-align: right;">
|
||||
<button class="btn-primary" onclick="app.generateOutput()">
|
||||
<i data-lucide="zap"></i>
|
||||
JETZT KONVERTIEREN
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Result Section -->
|
||||
<div id="resultSection" class="result-section">
|
||||
<!-- Stats Grid -->
|
||||
<div id="stats-grid" class="stats-grid"></div>
|
||||
|
||||
<div class="preview-header">
|
||||
<h2 id="resultTitle">Konvertierungsergebnis</h2>
|
||||
<div class="viewer-actions">
|
||||
<button class="btn-secondary" onclick="app.viewResult()">
|
||||
<i data-lucide="eye"></i>
|
||||
View
|
||||
</button>
|
||||
<button class="btn-secondary" onclick="app.editResult()">
|
||||
<i data-lucide="edit-3"></i>
|
||||
Edit
|
||||
</button>
|
||||
<button class="btn-primary" onclick="app.downloadResult()">
|
||||
<i data-lucide="download"></i>
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<pre id="previewArea"></pre>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||
<!-- PAGE: SETTINGS -->
|
||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||
<main class="main-card page-content" id="pageSettings" style="display:none">
|
||||
<h2 class="settings-title">
|
||||
<i data-lucide="folder-search"></i>
|
||||
Ankommende Dateien (Folder Watch)
|
||||
</h2>
|
||||
<p class="subtitle" style="margin-bottom: 30px;">Überwacht einen Netzwerkordner auf neue Dateien,
|
||||
konvertiert sie automatisch und schreibt die Ergebnisse in den Output-Ordner.</p>
|
||||
|
||||
<!-- General Settings & ID Mapping -->
|
||||
<div class="log-section" style="margin-top: 40px;">
|
||||
<h2 class="settings-title">
|
||||
<i data-lucide="user-check"></i>
|
||||
VDA 711 ID-Umschlüsselung
|
||||
</h2>
|
||||
<p class="subtitle" style="margin-bottom: 20px;">
|
||||
Weisen Sie extrahierten IDs (Datenempfänger) eine eigene Kundennummer für den VDA 711 Header zu.
|
||||
</p>
|
||||
|
||||
<div class="mapping-editor">
|
||||
<div class="folder-input-row" style="margin-bottom: 20px;">
|
||||
<input type="text" id="newIdMapOriginal" class="folder-path-input"
|
||||
placeholder="Alt (z.B. 8810)" style="max-width: 200px;">
|
||||
<input type="text" id="newIdMapTarget" class="folder-path-input"
|
||||
placeholder="Neu (z.B. 10305)" style="max-width: 200px;">
|
||||
<button class="btn-primary" onclick="app.addIdMapping711()">
|
||||
<i data-lucide="plus"></i>
|
||||
Hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<table id="idMapping711Table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Extrahierte ID (Alt)</th>
|
||||
<th>Eigene ID (Neu)</th>
|
||||
<th style="width: 100px;">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Populated by JS -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Electron notice -->
|
||||
<div id="electronNotice" class="electron-notice" style="display:none">
|
||||
<i data-lucide="alert-triangle"></i>
|
||||
<span>Ordnerüberwachung ist nur in der Electron-App verfügbar. Bitte starten Sie die App mit
|
||||
<code>npm start</code>.</span>
|
||||
</div>
|
||||
|
||||
<!-- Folder Config -->
|
||||
<div class="settings-grid">
|
||||
<div class="settings-card">
|
||||
<div class="settings-card-header">
|
||||
<i data-lucide="folder-input"></i>
|
||||
<span>Input-Ordner</span>
|
||||
</div>
|
||||
<div class="folder-input-row">
|
||||
<input type="text" id="inputDirPath" class="folder-path-input"
|
||||
placeholder="z.B. \\server\edi\input" readonly>
|
||||
<button class="btn-secondary" id="btnSelectInput" onclick="app.selectInputFolder()">
|
||||
<i data-lucide="folder-open"></i>
|
||||
Wählen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<div class="settings-card-header">
|
||||
<i data-lucide="folder-output"></i>
|
||||
<span>Output-Ordner</span>
|
||||
</div>
|
||||
<div class="folder-input-row">
|
||||
<input type="text" id="outputDirPath" class="folder-path-input"
|
||||
placeholder="z.B. \\server\edi\output" readonly>
|
||||
<button class="btn-secondary" id="btnSelectOutput" onclick="app.selectOutputFolder()">
|
||||
<i data-lucide="folder-open"></i>
|
||||
Wählen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Watcher Controls -->
|
||||
<div class="watcher-controls">
|
||||
<div class="watcher-status-display">
|
||||
<span class="watcher-status-dot" id="watcherDot"></span>
|
||||
<span id="watcherStatusText">Gestoppt</span>
|
||||
</div>
|
||||
<div class="watcher-buttons">
|
||||
<button class="btn-primary btn-start" id="btnStartWatcher" onclick="app.startWatcher()">
|
||||
<i data-lucide="play"></i>
|
||||
Starten
|
||||
</button>
|
||||
<button class="btn-secondary btn-pause" id="btnPauseWatcher" onclick="app.pauseWatcher()" disabled>
|
||||
<i data-lucide="pause"></i>
|
||||
Pausieren
|
||||
</button>
|
||||
<button class="btn-secondary btn-resume" id="btnResumeWatcher" onclick="app.resumeWatcher()"
|
||||
style="display:none">
|
||||
<i data-lucide="play"></i>
|
||||
Fortsetzen
|
||||
</button>
|
||||
<button class="btn-secondary btn-stop" id="btnStopWatcher" onclick="app.stopWatcher()" disabled>
|
||||
<i data-lucide="square"></i>
|
||||
Stoppen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Outbound Folder Config -->
|
||||
<div class="log-section" style="margin-top: 40px;">
|
||||
<h2 class="settings-title">
|
||||
<i data-lucide="folder-search"></i>
|
||||
Ausgehende Dateien (Folder Watch)
|
||||
</h2>
|
||||
<p class="subtitle" style="margin-bottom: 20px;">
|
||||
Überwacht einen Netzwerkordner auf ausgehende Dateien, konvertiert sie automatisch und schreibt die Ergebnisse in den Output-Ordner.
|
||||
</p>
|
||||
|
||||
<div class="settings-grid">
|
||||
<div class="settings-card">
|
||||
<div class="settings-card-header">
|
||||
<i data-lucide="folder-input"></i>
|
||||
<span>Input-Ordner (Ausgehend)</span>
|
||||
</div>
|
||||
<div class="folder-input-row">
|
||||
<input type="text" id="outboundInputPath" class="folder-path-input"
|
||||
placeholder="z.B. \\server\edi\outbound_in" readonly>
|
||||
<button class="btn-secondary" onclick="app.selectOutboundInput()">
|
||||
<i data-lucide="folder-open"></i>
|
||||
Wählen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-card">
|
||||
<div class="settings-card-header">
|
||||
<i data-lucide="folder-output"></i>
|
||||
<span>Output-Ordner (Ausgehend)</span>
|
||||
</div>
|
||||
<div class="folder-input-row">
|
||||
<input type="text" id="outboundOutputPath" class="folder-path-input"
|
||||
placeholder="z.B. \\server\edi\outbound_out" readonly>
|
||||
<button class="btn-secondary" onclick="app.selectOutboundOutput()">
|
||||
<i data-lucide="folder-open"></i>
|
||||
Wählen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="watcher-controls">
|
||||
<div class="watcher-status-display">
|
||||
<span class="watcher-status-dot" id="outboundWatcherDot"></span>
|
||||
<span id="outboundWatcherStatusText">Gestoppt</span>
|
||||
</div>
|
||||
<div class="watcher-buttons">
|
||||
<button class="btn-primary btn-start" id="btnStartOutboundWatcher"
|
||||
onclick="app.startOutboundWatcher()">
|
||||
<i data-lucide="play"></i>
|
||||
Starten
|
||||
</button>
|
||||
<button class="btn-secondary btn-pause" id="btnPauseOutboundWatcher"
|
||||
onclick="app.pauseOutboundWatcher()" disabled>
|
||||
<i data-lucide="pause"></i>
|
||||
Pausieren
|
||||
</button>
|
||||
<button class="btn-secondary btn-resume" id="btnResumeOutboundWatcher"
|
||||
onclick="app.resumeOutboundWatcher()" style="display:none">
|
||||
<i data-lucide="play"></i>
|
||||
Fortsetzen
|
||||
</button>
|
||||
<button class="btn-secondary btn-stop" id="btnStopOutboundWatcher" onclick="app.stopOutboundWatcher()"
|
||||
disabled>
|
||||
<i data-lucide="square"></i>
|
||||
Stoppen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Werksnummer Watcher Controls (New Section) -->
|
||||
<div class="log-section" style="margin-top: 40px;">
|
||||
<h2 class="settings-title">
|
||||
<i data-lucide="file-text"></i>
|
||||
Werksnummer Umbenennung
|
||||
</h2>
|
||||
<p class="subtitle" style="margin-bottom: 20px;">
|
||||
Überwacht VDA-Dateien (RA_VDA*) und benennt sie basierend auf der Werksnummer in Satzart 512 um
|
||||
(100 → LEIFERS, 280 → PITESTI).
|
||||
</p>
|
||||
|
||||
<div class="settings-grid">
|
||||
<div class="settings-card">
|
||||
<div class="settings-card-header">
|
||||
<i data-lucide="folder-input"></i>
|
||||
<span>Input-Ordner (Werksnummer)</span>
|
||||
</div>
|
||||
<div class="folder-input-row">
|
||||
<input type="text" id="werksInputPath" class="folder-path-input"
|
||||
placeholder="Input für Umbenennung" readonly>
|
||||
<button class="btn-secondary" onclick="app.selectWerksInput()">
|
||||
<i data-lucide="folder-open"></i>
|
||||
Wählen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-card">
|
||||
<div class="settings-card-header">
|
||||
<i data-lucide="folder-output"></i>
|
||||
<span>Output-Ordner (Werksnummer)</span>
|
||||
</div>
|
||||
<div class="folder-input-row">
|
||||
<input type="text" id="werksOutputPath" class="folder-path-input"
|
||||
placeholder="Output für Umbenennung" readonly>
|
||||
<button class="btn-secondary" onclick="app.selectWerksOutput()">
|
||||
<i data-lucide="folder-open"></i>
|
||||
Wählen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="watcher-controls">
|
||||
<div class="watcher-status-display">
|
||||
<span class="watcher-status-dot" id="werksWatcherDot"></span>
|
||||
<span id="werksWatcherStatusText">Gestoppt</span>
|
||||
</div>
|
||||
<div class="watcher-buttons">
|
||||
<button class="btn-primary btn-start" id="btnStartWerksWatcher"
|
||||
onclick="app.startWerksWatcher()">
|
||||
<i data-lucide="play"></i>
|
||||
Starten
|
||||
</button>
|
||||
<button class="btn-secondary btn-pause" id="btnPauseWerksWatcher"
|
||||
onclick="app.pauseWerksWatcher()" disabled>
|
||||
<i data-lucide="pause"></i>
|
||||
Pausieren
|
||||
</button>
|
||||
<button class="btn-secondary btn-resume" id="btnResumeWerksWatcher"
|
||||
onclick="app.resumeWerksWatcher()" style="display:none">
|
||||
<i data-lucide="play"></i>
|
||||
Fortsetzen
|
||||
</button>
|
||||
<button class="btn-secondary btn-stop" id="btnStopWerksWatcher" onclick="app.stopWerksWatcher()"
|
||||
disabled>
|
||||
<i data-lucide="square"></i>
|
||||
Stoppen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Customer Mappings -->
|
||||
<div class="log-section" style="margin-top: 40px;">
|
||||
<div class="preview-header">
|
||||
<h2>
|
||||
<i data-lucide="map"></i>
|
||||
Kundennummern-Zuordnung
|
||||
</h2>
|
||||
</div>
|
||||
<p class="subtitle" style="margin-bottom: 20px;">Weisen Sie Kundennummern automatisch einer
|
||||
Konvertierung zu.</p>
|
||||
|
||||
<div class="mapping-editor">
|
||||
<div class="folder-input-row" style="margin-bottom: 20px;">
|
||||
<input type="text" id="newMappingId" class="folder-path-input"
|
||||
placeholder="Kundennummer (z.B. 5060)" style="max-width: 200px;">
|
||||
<select id="newMappingMode" class="folder-path-input" style="max-width: 250px;">
|
||||
<option value="outbound-bosch">VDA 4913 → Bosch DESADV</option>
|
||||
<option value="outbound-zf">VDA 4913 → ZF DESADV</option>
|
||||
<option value="outbound-ifm">VDA 4913 → IFM DELVRY03</option>
|
||||
<option value="inbound-bosch">DELFOR → VDA 4905</option>
|
||||
<option value="inbound-ifm">IFM DELFOR → VDA 4905</option>
|
||||
<option value="inbound-invrpt">INVRPT → VDA 4913</option>
|
||||
<option value="validate-edifact">EDIFACT Validieren</option>
|
||||
</select>
|
||||
<button class="btn-primary" onclick="app.addMapping()">
|
||||
<i data-lucide="plus"></i>
|
||||
Hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<table id="mappingTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Kundennummer</th>
|
||||
<th>Konvertierungs-Modus</th>
|
||||
<th style="width: 100px;">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Will be populated by JS -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Conversion Log -->
|
||||
<div class="log-section">
|
||||
<div class="preview-header">
|
||||
<h2>Konvertierungs-Log</h2>
|
||||
<button class="btn-secondary" onclick="app.clearLog()">
|
||||
<i data-lucide="trash-2"></i>
|
||||
Leeren
|
||||
</button>
|
||||
</div>
|
||||
<div class="log-container" id="logContainer">
|
||||
<div class="log-empty">Noch keine Aktivitäten.</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||
<!-- PAGE: VIEWER -->
|
||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||
<main class="main-card page-content" id="pageViewer" style="display:none">
|
||||
<div class="preview-header no-print">
|
||||
<h2>EDI Dokument Viewer</h2>
|
||||
<div class="viewer-actions">
|
||||
<button class="btn-secondary" onclick="app.viewer.reset()" id="btnViewerBack" style="display:none">
|
||||
<i data-lucide="arrow-left"></i>
|
||||
Zurück
|
||||
</button>
|
||||
<button class="btn-primary" onclick="app.viewer.printDocument()" id="btnViewerPrint"
|
||||
style="display:none">
|
||||
<i data-lucide="printer"></i>
|
||||
Als PDF speichern / Drucken
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="viewerDropZone" class="drop-zone no-print">
|
||||
<i data-lucide="file-search"></i>
|
||||
<h3>Datei hier ablegen oder klicken zur Anzeige</h3>
|
||||
<p id="viewerDropText">Unterstützt DELFOR und VDA 4905/4913 Dateien</p>
|
||||
<input type="file" id="viewerFileInput" class="hidden">
|
||||
</div>
|
||||
|
||||
<!-- This container will hold the rendered HTML document -->
|
||||
<div id="viewerDocumentRender" class="document-render-area">
|
||||
<!-- Content injected by JS -->
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||
<!-- PAGE: EDITOR -->
|
||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||
<main class="main-card page-content" id="pageEditor" style="display:none">
|
||||
<div class="preview-header no-print">
|
||||
<h2>EDI Dokument Editor</h2>
|
||||
<div class="viewer-actions">
|
||||
<button class="btn-secondary" onclick="app.editor.reset()" id="btnEditorBack" style="display:none">
|
||||
<i data-lucide="arrow-left"></i>
|
||||
Zurück
|
||||
</button>
|
||||
<button class="btn-primary" onclick="app.editor.downloadDocument()" id="btnEditorDownload"
|
||||
style="display:none">
|
||||
<i data-lucide="download"></i>
|
||||
Speichern / Download
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="editorDropZone" class="drop-zone no-print">
|
||||
<i data-lucide="edit"></i>
|
||||
<h3>EDI Datei hier ablegen oder klicken zur Bearbeitung</h3>
|
||||
<p id="editorDropText">Unterstützt EDIFACT und VDA Dateien</p>
|
||||
<input type="file" id="editorFileInput" class="hidden">
|
||||
</div>
|
||||
|
||||
<!-- This container will hold the editable segments -->
|
||||
<div id="editorDocumentRender" class="editor-render-area" style="display:none;">
|
||||
<!-- Content injected by JS -->
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||
<!-- PAGE: HISTORY -->
|
||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||
<main class="main-card page-content" id="pageHistory" style="display:none">
|
||||
<h2 class="settings-title">
|
||||
<i data-lucide="database"></i>
|
||||
Konvertierungs-Historie
|
||||
</h2>
|
||||
<p class="subtitle" style="margin-bottom: 20px;">Vollständige Nachverfolgung aller durchgeführten
|
||||
Konvertierungen.</p>
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="stats-grid" id="historyStats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">-</div>
|
||||
<div class="stat-label">Gesamt</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">-</div>
|
||||
<div class="stat-label">Erfolgreich</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">-</div>
|
||||
<div class="stat-label">Fehlerhaft</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">-</div>
|
||||
<div class="stat-label">Heute</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Bar -->
|
||||
<div class="history-filter-bar">
|
||||
<div class="history-filter-buttons">
|
||||
<button class="btn-secondary history-filter-btn active" data-filter="ALL"
|
||||
onclick="app.historyManager.setFilter('ALL')">
|
||||
Alle
|
||||
</button>
|
||||
<button class="btn-secondary history-filter-btn" data-filter="ERFOLGREICH"
|
||||
onclick="app.historyManager.setFilter('ERFOLGREICH')">
|
||||
✅ Erfolgreich
|
||||
</button>
|
||||
<button class="btn-secondary history-filter-btn" data-filter="FEHLERHAFT"
|
||||
onclick="app.historyManager.setFilter('FEHLERHAFT')">
|
||||
❌ Fehlerhaft
|
||||
</button>
|
||||
</div>
|
||||
<div class="history-search-row">
|
||||
<input type="text" id="historySearch" class="folder-path-input"
|
||||
placeholder="🔍 Suche nach Dateiname, Kunden-ID..."
|
||||
oninput="app.historyManager.onSearch(this.value)" style="max-width:350px;">
|
||||
<button class="btn-secondary" onclick="app.historyManager.exportCSV()">
|
||||
<i data-lucide="download" style="width:14px;height:14px;"></i> CSV Export
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="history-table-wrapper">
|
||||
<table class="history-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:50px;">ID</th>
|
||||
<th style="width:140px;">Zeitstempel</th>
|
||||
<th>Dateiname</th>
|
||||
<th style="width:180px;">Modus</th>
|
||||
<th style="width:90px;">Status</th>
|
||||
<th style="width:50px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="historyTableBody">
|
||||
<tr>
|
||||
<td colspan="6"
|
||||
style="text-align:center; padding:40px; color: var(--text-dim); font-style:italic;">Lade
|
||||
Daten...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="history-pagination" id="historyPagination"></div>
|
||||
</main>
|
||||
|
||||
<!-- History Detail Modal -->
|
||||
<div id="historyDetailModal" class="history-detail-modal" style="display:none;">
|
||||
<div class="history-detail-modal-content">
|
||||
<div id="historyDetailContent"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SCRIPTS -->
|
||||
<script src="js/vda-parser.js"></script>
|
||||
<script src="js/edifact-logic.js"></script>
|
||||
<script src="js/config-parser.js"></script>
|
||||
<script src="js/vda4913-to-desadv.js"></script>
|
||||
<script src="js/vda4913-to-desadv-zf.js"></script>
|
||||
<script src="js/vda4913-to-delvry03.js"></script>
|
||||
<script src="js/delfor-parser.js"></script>
|
||||
<script src="js/vda4905-generator.js"></script>
|
||||
<script src="js/ifm_delfor-vda4905.js"></script>
|
||||
<script src="js/invrpt-to-vda4913.js"></script>
|
||||
<script src="js/watcher-bridge.js"></script>
|
||||
<script src="js/outbound-watcher-bridge.js"></script>
|
||||
<script src="js/werks-watcher-bridge.js"></script>
|
||||
<script src="js/viewer.js"></script>
|
||||
<script src="js/editor.js"></script>
|
||||
<script src="js/history.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
|
||||
<script>
|
||||
// Init App safely
|
||||
try {
|
||||
window.app = new window.EDIBridge.App();
|
||||
if (window.lucide) {
|
||||
try { lucide.createIcons(); } catch (e) { console.warn("Lucide Error", e); }
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("App Fatal Error:", e);
|
||||
alert("App Fatal Error: " + e.message);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
171
js/config-parser.js
Normal file
171
js/config-parser.js
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* INI-style Config Parser for NAD/Weight/Dimension lookup tables
|
||||
* All section lookups are case-insensitive
|
||||
*/
|
||||
window.EDIBridge = window.EDIBridge || {};
|
||||
|
||||
class ConfigParser {
|
||||
static parse(text) {
|
||||
const config = {};
|
||||
const sectionMap = {};
|
||||
let currentSection = null;
|
||||
|
||||
const lines = text.split(/\r?\n/);
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith(';')) continue;
|
||||
|
||||
const sectionMatch = trimmed.match(/^\[(.+)\]$/);
|
||||
if (sectionMatch) {
|
||||
currentSection = sectionMatch[1];
|
||||
config[currentSection] = config[currentSection] || {};
|
||||
sectionMap[currentSection.toUpperCase()] = currentSection;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentSection) {
|
||||
const eqIdx = trimmed.indexOf('=');
|
||||
if (eqIdx > 0) {
|
||||
const key = trimmed.substring(0, eqIdx).trim();
|
||||
const value = trimmed.substring(eqIdx + 1).trim();
|
||||
config[currentSection][key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.__sectionMap__ = sectionMap;
|
||||
return config;
|
||||
}
|
||||
|
||||
/** Case-insensitive section lookup */
|
||||
static getSection(config, name) {
|
||||
if (config[name]) return config[name];
|
||||
const map = config.__sectionMap__ || {};
|
||||
const realName = map[name.toUpperCase()];
|
||||
if (realName && config[realName]) return config[realName];
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Ensure a section exists, create if missing */
|
||||
static ensureSection(config, name) {
|
||||
if (!config[name]) {
|
||||
config[name] = {};
|
||||
if (!config.__sectionMap__) config.__sectionMap__ = {};
|
||||
config.__sectionMap__[name.toUpperCase()] = name;
|
||||
}
|
||||
return config[name];
|
||||
}
|
||||
|
||||
/** Find value trying multiple ID variations */
|
||||
static findInSection(config, sectionName, plantId) {
|
||||
const section = this.getSection(config, sectionName);
|
||||
if (!section) return '';
|
||||
const ids = [
|
||||
plantId,
|
||||
plantId.replace(/^0+/, ''),
|
||||
plantId.padStart(10, '0'),
|
||||
plantId.padStart(9, '0'),
|
||||
plantId.padStart(8, '0'),
|
||||
plantId.padStart(7, '0')
|
||||
];
|
||||
for (const id of ids) {
|
||||
if (section[id] !== undefined) return section[id];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/** Look up NAD record */
|
||||
static lookupNAD(config, qualifier, plantId) {
|
||||
const q = qualifier.toUpperCase();
|
||||
return {
|
||||
id: this.findInSection(config, `NAD_${q}_PARTY`, plantId) ||
|
||||
this.findInSection(config, `NAD_${q}_Party`, plantId) || plantId,
|
||||
qual: this.findInSection(config, `NAD_${q}_QUAL`, plantId) ||
|
||||
this.findInSection(config, `NAD_${q}_Qual`, plantId) || '92',
|
||||
name: this.findInSection(config, `NAD_${q}_NAME`, plantId) ||
|
||||
this.findInSection(config, `NAD_${q}_Name`, plantId),
|
||||
name2: this.findInSection(config, `NAD_${q}_NAME2`, plantId) ||
|
||||
this.findInSection(config, `NAD_${q}_Name2`, plantId) ||
|
||||
this.findInSection(config, `NAD_${q}_Name_2`, plantId),
|
||||
street: this.findInSection(config, `NAD_${q}_STREET`, plantId) ||
|
||||
this.findInSection(config, `NAD_${q}_Street`, plantId),
|
||||
city: this.findInSection(config, `NAD_${q}_CITY`, plantId) ||
|
||||
this.findInSection(config, `NAD_${q}_City`, plantId),
|
||||
zip: this.findInSection(config, `NAD_${q}_POSTCODE`, plantId) ||
|
||||
this.findInSection(config, `NAD_${q}_Postcode`, plantId),
|
||||
country: this.findInSection(config, `NAD_${q}_COUNTRY`, plantId) ||
|
||||
this.findInSection(config, `NAD_${q}_Country`, plantId)
|
||||
};
|
||||
}
|
||||
|
||||
static lookupPacWeight(config, packMatSupp) {
|
||||
const section = this.getSection(config, 'PAC_WEIGHT');
|
||||
if (!section) return 0;
|
||||
const val = section[packMatSupp] || section[packMatSupp.replace(/^0+/, '')];
|
||||
return val ? parseFloat(val) : 0;
|
||||
}
|
||||
|
||||
static lookupPacDimension(config, packMatSupp, dim) {
|
||||
// dim = 'LENGTH', 'WIDTH', 'HEIGHT'
|
||||
const section = this.getSection(config, 'PAC_' + dim);
|
||||
if (!section) return 0;
|
||||
const val = section[packMatSupp] || section[packMatSupp.replace(/^0+/, '')];
|
||||
return val ? parseFloat(val) : 0;
|
||||
}
|
||||
|
||||
static lookupLinWeight(config, suppMat) {
|
||||
const section = this.getSection(config, 'LIN_WEIGHT');
|
||||
if (!section) return 0;
|
||||
const val = section[suppMat] || section[suppMat.replace(/^0+/, '').replace(/ /g, '')];
|
||||
return val ? parseFloat(val) : 0;
|
||||
}
|
||||
|
||||
static lookupRffAnk(config, sellerId) {
|
||||
const section = this.getSection(config, 'RFF_ANK_SE');
|
||||
if (!section) return '';
|
||||
const ids = [sellerId, sellerId.replace(/^0+/, ''), sellerId.padStart(10, '0')];
|
||||
for (const id of ids) {
|
||||
if (section[id]) return section[id];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
static lookupGeneral(config, key, defaultValue = '') {
|
||||
const section = this.getSection(config, 'GENERAL');
|
||||
if (!section) return defaultValue;
|
||||
return section[key] || defaultValue;
|
||||
}
|
||||
|
||||
static lookupCustomerMode(config, customerId) {
|
||||
const id = customerId.trim();
|
||||
|
||||
if (config) {
|
||||
const section = this.getSection(config, 'CUSTOMER_MAPPING');
|
||||
if (section) {
|
||||
const mode = section[id] || section[id.replace(/^0+/, '')];
|
||||
if (mode) return mode;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Serialize config object back to INI text */
|
||||
static serialize(config) {
|
||||
const lines = [];
|
||||
const skipKeys = new Set(['__sectionMap__']);
|
||||
|
||||
for (const section of Object.keys(config)) {
|
||||
if (skipKeys.has(section)) continue;
|
||||
lines.push('');
|
||||
lines.push('[' + section + ']');
|
||||
const entries = config[section];
|
||||
for (const [key, value] of Object.entries(entries)) {
|
||||
lines.push(key + '=' + value);
|
||||
}
|
||||
}
|
||||
return lines.join('\r\n');
|
||||
}
|
||||
}
|
||||
|
||||
window.EDIBridge.ConfigParser = ConfigParser;
|
||||
238
js/db.js
Normal file
238
js/db.js
Normal file
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* ConversionDB – SQLite wrapper for conversion history
|
||||
* Used in the Electron main process only.
|
||||
* Refactored to use async 'sqlite3' to avoid native compilation issues.
|
||||
*/
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const path = require('path');
|
||||
|
||||
class ConversionDB {
|
||||
constructor(dbPath) {
|
||||
this.dbPath = dbPath;
|
||||
}
|
||||
|
||||
async init() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db = new sqlite3.Database(this.dbPath, (err) => {
|
||||
if (err) return reject(err);
|
||||
|
||||
this.db.run('PRAGMA journal_mode = WAL');
|
||||
|
||||
this.db.serialize(() => {
|
||||
this.db.run(`
|
||||
CREATE TABLE IF NOT EXISTS konvertierungen (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
zeitstempel DATETIME DEFAULT (datetime('now', 'localtime')),
|
||||
dateiname TEXT NOT NULL,
|
||||
ausgangsdateiname TEXT,
|
||||
quellformat TEXT,
|
||||
zielformat TEXT,
|
||||
konvertierungsmodus TEXT,
|
||||
kunden_id TEXT,
|
||||
eingang_daten TEXT,
|
||||
ausgang_daten TEXT,
|
||||
status TEXT DEFAULT 'ERFOLGREICH',
|
||||
fehlermeldung TEXT,
|
||||
quelle TEXT DEFAULT 'WATCHER'
|
||||
);
|
||||
`);
|
||||
|
||||
// Table for storing the latest configuration
|
||||
this.db.run(`
|
||||
CREATE TABLE IF NOT EXISTS konfigurationen (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
zeitstempel DATETIME DEFAULT (datetime('now', 'localtime')),
|
||||
config_daten TEXT NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
// Index for common queries
|
||||
this.db.run(`CREATE INDEX IF NOT EXISTS idx_zeitstempel ON konvertierungen(zeitstempel DESC);`);
|
||||
this.db.run(`CREATE INDEX IF NOT EXISTS idx_status ON konvertierungen(status);`, (err) => {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the latest configuration (overwrites any existing).
|
||||
* @param {string} configData Serialized JSON string
|
||||
*/
|
||||
async insertConfig(configData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.serialize(() => {
|
||||
// Keep only one configuration record by dropping the old one
|
||||
this.db.run('DELETE FROM konfigurationen');
|
||||
|
||||
const sql = `INSERT INTO konfigurationen (config_daten) VALUES (?)`;
|
||||
this.db.run(sql, [configData || ''], function (err) {
|
||||
if (err) reject(err);
|
||||
else resolve({ id: this.lastID });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the latest stored configuration.
|
||||
* @returns {Promise<string|null>}
|
||||
*/
|
||||
async getConfig() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.get('SELECT config_daten FROM konfigurationen ORDER BY id DESC LIMIT 1', [], (err, row) => {
|
||||
if (err) reject(err);
|
||||
else resolve(row ? row.config_daten : null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new conversion record.
|
||||
* @param {Object} record
|
||||
* @returns {Promise<Object>} { id }
|
||||
*/
|
||||
async insert(record) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const sql = `
|
||||
INSERT INTO konvertierungen
|
||||
(dateiname, ausgangsdateiname, quellformat, zielformat,
|
||||
konvertierungsmodus, kunden_id, eingang_daten, ausgang_daten,
|
||||
status, fehlermeldung, quelle)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
const params = [
|
||||
record.dateiname || '',
|
||||
record.ausgangsdateiname || null,
|
||||
record.quellformat || null,
|
||||
record.zielformat || null,
|
||||
record.konvertierungsmodus || null,
|
||||
record.kunden_id || null,
|
||||
record.eingang_daten || null,
|
||||
record.ausgang_daten || null,
|
||||
record.status || 'ERFOLGREICH',
|
||||
record.fehlermeldung || null,
|
||||
record.quelle || 'WATCHER'
|
||||
];
|
||||
|
||||
this.db.run(sql, params, function (err) {
|
||||
if (err) reject(err);
|
||||
else resolve({ id: this.lastID });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get paginated list of conversions (without full data blobs).
|
||||
*/
|
||||
async getAll(limit = 50, offset = 0, statusFilter = 'ALL', search = '', dateFilter = 'ALL') {
|
||||
return new Promise((resolve, reject) => {
|
||||
let where = '';
|
||||
const conditions = [];
|
||||
const params = [];
|
||||
|
||||
const cleanStatus = (statusFilter || 'ALL').toString().trim().toUpperCase();
|
||||
const cleanDate = (dateFilter || 'ALL').toString().trim().toUpperCase();
|
||||
|
||||
if (cleanStatus !== 'ALL') {
|
||||
conditions.push('status = ?');
|
||||
params.push(cleanStatus);
|
||||
}
|
||||
if (cleanDate === 'TODAY') {
|
||||
conditions.push("date(zeitstempel) = date('now', 'localtime')");
|
||||
}
|
||||
if (search) {
|
||||
conditions.push('(dateiname LIKE ? OR ausgangsdateiname LIKE ? OR kunden_id LIKE ?)');
|
||||
params.push(`%${search}%`, `%${search}%`, `%${search}%`);
|
||||
}
|
||||
if (conditions.length > 0) {
|
||||
where = 'WHERE ' + conditions.join(' AND ');
|
||||
}
|
||||
|
||||
const countSql = `SELECT COUNT(*) as total FROM konvertierungen ${where}`;
|
||||
|
||||
this.db.get(countSql, params, (err, countRow) => {
|
||||
if (err) return reject(err);
|
||||
|
||||
const dataSql = `
|
||||
SELECT id, zeitstempel, dateiname, ausgangsdateiname,
|
||||
quellformat, zielformat, konvertierungsmodus,
|
||||
kunden_id, status, fehlermeldung, quelle
|
||||
FROM konvertierungen
|
||||
${where}
|
||||
ORDER BY zeitstempel DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`;
|
||||
|
||||
this.db.all(dataSql, [...params, limit, offset], (err, rows) => {
|
||||
if (err) reject(err);
|
||||
else resolve({ rows, total: countRow.total });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single record by ID (including full data).
|
||||
*/
|
||||
async getById(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.get('SELECT * FROM konvertierungen WHERE id = ?', [id], (err, row) => {
|
||||
if (err) reject(err);
|
||||
else resolve(row || null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a record by ID.
|
||||
*/
|
||||
async deleteById(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.run('DELETE FROM konvertierungen WHERE id = ?', [id], function (err) {
|
||||
if (err) reject(err);
|
||||
else resolve(this.changes > 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get aggregated statistics.
|
||||
*/
|
||||
async getStats() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let total = 0, success = 0, error = 0, today = 0;
|
||||
|
||||
this.db.serialize(() => {
|
||||
this.db.get('SELECT COUNT(*) as c FROM konvertierungen', (err, row) => {
|
||||
if (!err && row) total = row.c;
|
||||
});
|
||||
this.db.get("SELECT COUNT(*) as c FROM konvertierungen WHERE status = 'ERFOLGREICH'", (err, row) => {
|
||||
if (!err && row) success = row.c;
|
||||
});
|
||||
this.db.get("SELECT COUNT(*) as c FROM konvertierungen WHERE status = 'FEHLERHAFT'", (err, row) => {
|
||||
if (!err && row) error = row.c;
|
||||
});
|
||||
this.db.get("SELECT COUNT(*) as c FROM konvertierungen WHERE date(zeitstempel) = date('now', 'localtime')", (err, row) => {
|
||||
if (err) return reject(err);
|
||||
today = row.c;
|
||||
resolve({ total, success, error, today });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the database connection.
|
||||
*/
|
||||
close() {
|
||||
if (this.db) {
|
||||
this.db.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ConversionDB;
|
||||
131
js/delfor-parser.js
Normal file
131
js/delfor-parser.js
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* EDIFACT Parser for DELFOR D04A
|
||||
* Parses raw EDI string into structured JSON
|
||||
*/
|
||||
window.EDIBridge = window.EDIBridge || {};
|
||||
|
||||
class DelforParser {
|
||||
static parse(content) {
|
||||
// Defaults
|
||||
let separator = '+';
|
||||
let subSeparator = ':';
|
||||
let decimal = '.';
|
||||
let release = '?';
|
||||
let terminator = "'";
|
||||
|
||||
// Clean raw content
|
||||
let raw = content.replace(/\r\n/g, '').replace(/\n/g, '').trim();
|
||||
|
||||
// 1. Detect UNA
|
||||
if (raw.startsWith('UNA')) {
|
||||
subSeparator = raw[3];
|
||||
separator = raw[4];
|
||||
decimal = raw[5];
|
||||
release = raw[6];
|
||||
terminator = raw[8];
|
||||
raw = raw.substring(9);
|
||||
}
|
||||
|
||||
// 2. Split Segments (Simple approach usually sufficient for well-formed EDI)
|
||||
// Advanced approach: Character-by-character to handle release chars
|
||||
const segments = [];
|
||||
let currentSeg = '';
|
||||
let escaped = false;
|
||||
|
||||
for (let i = 0; i < raw.length; i++) {
|
||||
const char = raw[i];
|
||||
|
||||
if (escaped) {
|
||||
currentSeg += char;
|
||||
escaped = false;
|
||||
} else if (char === release) {
|
||||
escaped = true;
|
||||
} else if (char === terminator) {
|
||||
if (currentSeg.trim().length > 0) {
|
||||
segments.push(this.parseSegment(currentSeg, separator, subSeparator, release));
|
||||
}
|
||||
currentSeg = '';
|
||||
} else {
|
||||
currentSeg += char;
|
||||
}
|
||||
}
|
||||
|
||||
// Add final segment if no terminator at end
|
||||
if (currentSeg.trim().length > 0) {
|
||||
segments.push(this.parseSegment(currentSeg, separator, subSeparator, release));
|
||||
}
|
||||
|
||||
return {
|
||||
meta: { separator, subSeparator, release, terminator },
|
||||
segments: segments
|
||||
};
|
||||
}
|
||||
|
||||
static parseSegment(segStr, sep, subSep, release) {
|
||||
// Tokenize by separator
|
||||
const tokenized = this.tokenize(segStr, sep, release);
|
||||
const tag = tokenized.shift(); // First token is always Tag (e.g. UNH)
|
||||
|
||||
const elements = tokenized.map(token => {
|
||||
// Check for sub-separator
|
||||
if (token.includes(subSep)) {
|
||||
// Determine if subSep is escaped?
|
||||
// tokenize logic handles simple split.
|
||||
// We should tokenize the token by subSep
|
||||
const components = this.tokenize(token, subSep, release);
|
||||
return components;
|
||||
}
|
||||
return token;
|
||||
});
|
||||
|
||||
return { tag, elements };
|
||||
}
|
||||
|
||||
// Helper: Split string by delimiter, respecting release char
|
||||
static tokenize(str, delim, release) {
|
||||
const tokens = [];
|
||||
let currentToken = '';
|
||||
let escaped = false;
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str[i];
|
||||
if (escaped) {
|
||||
currentToken += char;
|
||||
escaped = false;
|
||||
} else if (char === release) {
|
||||
escaped = true;
|
||||
} else if (char === delim) {
|
||||
tokens.push(currentToken);
|
||||
currentToken = '';
|
||||
} else {
|
||||
currentToken += char;
|
||||
}
|
||||
}
|
||||
tokens.push(currentToken);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a unique Customer/Sender ID for auto-detection
|
||||
*/
|
||||
static getCustomerId(content) {
|
||||
if (!content) return null;
|
||||
// Search for UNB sender
|
||||
const unbMatch = content.match(/UNB\s*[\+\:]([^\+']+)[\+\:]([^\+']+)/i);
|
||||
if (unbMatch) {
|
||||
let sender = unbMatch[2];
|
||||
if (sender.includes(':')) sender = sender.split(':')[0];
|
||||
const clean = sender.trim();
|
||||
if (clean && !clean.startsWith('000000')) return clean;
|
||||
}
|
||||
|
||||
// Fallback to NAD+BY
|
||||
const nadMatch = content.match(/NAD\+BY\+([^\+':]+)/i);
|
||||
if (nadMatch) {
|
||||
return nadMatch[1].trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
window.EDIBridge.DelforParser = DelforParser;
|
||||
46
js/edifact-logic.js
Normal file
46
js/edifact-logic.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* EDIFACT Logic Utilities
|
||||
*/
|
||||
window.EDIBridge = window.EDIBridge || {};
|
||||
|
||||
class EDIFACTLogic {
|
||||
constructor(options = {}) {
|
||||
this.segmentTerminator = options.segmentTerminator || "'";
|
||||
this.elementSeparator = options.elementSeparator || "+";
|
||||
this.componentSeparator = options.componentSeparator || ":";
|
||||
this.decimalSeparator = options.decimalSeparator || ".";
|
||||
this.escapeCharacter = options.escapeCharacter || "?";
|
||||
}
|
||||
|
||||
segment(tag, elements = []) {
|
||||
const formattedElements = elements.map(el => {
|
||||
if (Array.isArray(el)) {
|
||||
return el.map(comp => this.escape(comp)).join(this.componentSeparator);
|
||||
}
|
||||
return this.escape(el);
|
||||
});
|
||||
|
||||
return tag + this.elementSeparator + formattedElements.join(this.elementSeparator) + this.segmentTerminator;
|
||||
}
|
||||
|
||||
escape(val) {
|
||||
if (val === undefined || val === null) return '';
|
||||
const s = String(val);
|
||||
return s.replace(/[+':?]/g, match => this.escapeCharacter + match);
|
||||
}
|
||||
|
||||
static parseSegments(content) {
|
||||
const segmentTerminator = "'";
|
||||
return content.split(segmentTerminator)
|
||||
.map(s => s.trim())
|
||||
.filter(s => s.length > 0)
|
||||
.map(s => {
|
||||
const parts = s.split('+');
|
||||
const tag = parts[0];
|
||||
const elements = parts.slice(1).map(el => el.split(':'));
|
||||
return { tag, elements };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.EDIBridge.EDIFACTLogic = EDIFACTLogic;
|
||||
78
js/edifact-to-vda.js
Normal file
78
js/edifact-to-vda.js
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* DELFOR to VDA 4905 Mapping Logic
|
||||
*/
|
||||
window.EDIBridge = window.EDIBridge || {};
|
||||
|
||||
class DELFORToVDA4905 {
|
||||
static convert(ediContent) {
|
||||
const EDIFACTLogic = window.EDIBridge.EDIFACTLogic;
|
||||
const segments = EDIFACTLogic.parseSegments(ediContent);
|
||||
const records = [];
|
||||
|
||||
// Header Record 511
|
||||
records.push(this.format511({
|
||||
senderId: this.getNAD(segments, 'SE'),
|
||||
receiverId: this.getNAD(segments, 'BY'),
|
||||
date: this.getDTM(segments, '137')
|
||||
}));
|
||||
|
||||
let currentMaterial = null;
|
||||
for (const seg of segments) {
|
||||
if (seg.tag === 'LIN') {
|
||||
currentMaterial = seg.elements[2][0];
|
||||
}
|
||||
if (seg.tag === 'QTY' && currentMaterial) {
|
||||
records.push(this.format512({ material: currentMaterial }));
|
||||
records.push(this.format513({
|
||||
quantity: seg.elements[0][1],
|
||||
date: '240520'
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
records.push(this.format519(records.length + 1));
|
||||
|
||||
return records.join('\n');
|
||||
}
|
||||
|
||||
static getNAD(segments, qualifier) {
|
||||
const seg = segments.find(s => s.tag === 'NAD' && s.elements[0][0] === qualifier);
|
||||
return seg ? seg.elements[1][0] : 'UNKNOWN';
|
||||
}
|
||||
|
||||
static getDTM(segments, qualifier) {
|
||||
const seg = segments.find(s => s.tag === 'DTM' && s.elements[0][0] === qualifier);
|
||||
return seg ? seg.elements[0][1] : '240101';
|
||||
}
|
||||
|
||||
static format511(data) {
|
||||
let s = '511';
|
||||
s += '01';
|
||||
s += String(data.senderId).padEnd(9, ' ');
|
||||
s += String(data.receiverId).padEnd(9, ' ');
|
||||
s += '00001';
|
||||
s += String(data.date).substring(2, 8);
|
||||
return s.padEnd(128, ' ');
|
||||
}
|
||||
|
||||
static format512(data) {
|
||||
let s = '512';
|
||||
s += '01';
|
||||
s += String(data.material).padEnd(22, ' ');
|
||||
return s.padEnd(128, ' ');
|
||||
}
|
||||
|
||||
static format513(data) {
|
||||
let s = '513';
|
||||
s += '01';
|
||||
s += String(data.date).padEnd(6, ' ');
|
||||
s += String(Math.round(parseFloat(data.quantity))).padStart(12, '0');
|
||||
return s.padEnd(128, ' ');
|
||||
}
|
||||
|
||||
static format519(count) {
|
||||
return ('519' + '01' + String(count).padStart(7, '0')).padEnd(128, ' ');
|
||||
}
|
||||
}
|
||||
|
||||
window.EDIBridge.DELFORToVDA4905 = DELFORToVDA4905;
|
||||
218
js/edifact-validator.js
Normal file
218
js/edifact-validator.js
Normal file
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* EDIFACT Validator & Alternative Parser (Main Process only)
|
||||
* Uses ts-edifact library as a parallel layer alongside the custom DelforParser.
|
||||
*
|
||||
* IMPORTANT: This file runs in the Node.js Main Process only.
|
||||
* Do NOT include this file in index.html or use window.EDIBridge here.
|
||||
*
|
||||
* Exposed via IPC (see main.js):
|
||||
* 'validate-edifact' → validateEdifact(content)
|
||||
* 'parse-edifact-lib' → parseWithLib(content)
|
||||
*
|
||||
* ts-edifact API:
|
||||
* const { Reader } = require('ts-edifact');
|
||||
* const reader = new Reader(); // instanciate per call (stateful)
|
||||
* const segments = reader.parse(str); // returns { name, elements: string[][] }[]
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Parse EDIFACT content using ts-edifact Reader.
|
||||
* Returns a clean segment array for comparison with the custom DelforParser.
|
||||
*
|
||||
* Segment format: { name: 'UNH', elements: [['1'], ['DELFOR','D','04A','UN']] }
|
||||
*
|
||||
* @param {string} content - Raw EDIFACT content (string)
|
||||
* @returns {{ segments: Array, segmentCount: number, error?: string }}
|
||||
*/
|
||||
function parseWithLib(content) {
|
||||
try {
|
||||
const { Reader, NullValidator, Parser } = require('ts-edifact');
|
||||
|
||||
// Normalize line endings
|
||||
const normalized = content.replace(/\r\n/g, '').replace(/\r/g, '').trim();
|
||||
|
||||
// Each Reader instance is stateful — create a new one per call
|
||||
const reader = new Reader();
|
||||
|
||||
// --- FIX: Disable strict segment validation & Enable UNOC ---
|
||||
const validator = new NullValidator();
|
||||
if (typeof validator.define !== 'function') validator.define = () => { };
|
||||
|
||||
reader.validator = validator;
|
||||
reader.parser = new Parser(validator);
|
||||
reader.defined = true;
|
||||
reader.encoding('UNOC'); // Allow lowercase and special chars
|
||||
|
||||
// Re-setup Reader callbacks for the new parser
|
||||
const result = reader.result;
|
||||
let elements = reader.elements;
|
||||
let components = reader.components;
|
||||
reader.parser.onOpenSegment = function (segment) {
|
||||
elements = [];
|
||||
result.push({ name: segment, elements: elements });
|
||||
};
|
||||
reader.parser.onElement = function () {
|
||||
components = [];
|
||||
elements.push(components);
|
||||
};
|
||||
reader.parser.onComponent = function (value) {
|
||||
components.push(value);
|
||||
};
|
||||
|
||||
const segments = reader.parse(normalized);
|
||||
|
||||
return {
|
||||
segments,
|
||||
segmentCount: segments.length
|
||||
};
|
||||
|
||||
} catch (e) {
|
||||
return {
|
||||
segments: [],
|
||||
segmentCount: 0,
|
||||
error: e.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate EDIFACT content using ts-edifact library + structural checks.
|
||||
*
|
||||
* Performs:
|
||||
* 1. Syntax parsing via ts-edifact Reader (catches malformed EDI)
|
||||
* 2. Structural checks: UNB/UNZ envelope, UNH/UNT balance, BGM presence
|
||||
* 3. UNT segment count verification
|
||||
* 4. Message type & version detection
|
||||
*
|
||||
* @param {string} content - Raw EDIFACT content
|
||||
* @returns {{ valid: boolean, errors: string[], warnings: string[], stats: object }}
|
||||
*/
|
||||
function validateEdifact(content) {
|
||||
const errors = [];
|
||||
const warnings = [];
|
||||
const stats = {};
|
||||
|
||||
try {
|
||||
const { Reader, NullValidator, Parser } = require('ts-edifact');
|
||||
|
||||
// --- Step 1: Parse with ts-edifact ---
|
||||
let segments = null;
|
||||
try {
|
||||
const normalized = content.replace(/\r\n/g, '').replace(/\r/g, '').trim();
|
||||
const reader = new Reader();
|
||||
|
||||
// --- FIX: Disable strict segment validation & Enable UNOC ---
|
||||
const validator = new NullValidator();
|
||||
if (typeof validator.define !== 'function') validator.define = () => { };
|
||||
|
||||
reader.validator = validator;
|
||||
reader.parser = new Parser(validator);
|
||||
reader.defined = true;
|
||||
reader.encoding('UNOC');
|
||||
|
||||
// Re-setup Reader callbacks
|
||||
const result = reader.result;
|
||||
let elements = reader.elements;
|
||||
let components = reader.components;
|
||||
reader.parser.onOpenSegment = function (segment) {
|
||||
elements = [];
|
||||
result.push({ name: segment, elements: elements });
|
||||
};
|
||||
reader.parser.onElement = function () {
|
||||
components = [];
|
||||
elements.push(components);
|
||||
};
|
||||
reader.parser.onComponent = function (value) {
|
||||
components.push(value);
|
||||
};
|
||||
|
||||
segments = reader.parse(normalized);
|
||||
} catch (parseErr) {
|
||||
errors.push('Syntaxfehler: ' + parseErr.message);
|
||||
}
|
||||
|
||||
if (!segments || !Array.isArray(segments)) {
|
||||
return { valid: errors.length === 0, errors, warnings, stats };
|
||||
}
|
||||
|
||||
// Build a flat tag list for quick lookups
|
||||
const tags = segments.map(s => s.name);
|
||||
stats.segmentCount = segments.length;
|
||||
stats.allSegments = tags;
|
||||
|
||||
// --- Step 2: Interchange envelope (UNB / UNZ) ---
|
||||
if (!tags.includes('UNB')) {
|
||||
warnings.push('Kein UNB-Segment (Interchange-Header) gefunden.');
|
||||
} else {
|
||||
const unb = segments.find(s => s.name === 'UNB');
|
||||
if (unb && unb.elements[1]) stats.sender = unb.elements[1][0] || '';
|
||||
if (unb && unb.elements[2]) stats.receiver = unb.elements[2][0] || '';
|
||||
}
|
||||
if (!tags.includes('UNZ')) warnings.push('Kein UNZ-Segment (Interchange-Trailer) gefunden.');
|
||||
|
||||
// --- Step 3: Message envelope (UNH / UNT) ---
|
||||
const unhCount = tags.filter(t => t === 'UNH').length;
|
||||
const untCount = tags.filter(t => t === 'UNT').length;
|
||||
stats.messageCount = unhCount;
|
||||
|
||||
if (unhCount === 0) {
|
||||
errors.push('Kein UNH-Segment (Message-Header) gefunden.');
|
||||
} else {
|
||||
// Detect message type from first UNH+<ref>+<type>:<ver>:<release>:<ctrl>
|
||||
const unh = segments.find(s => s.name === 'UNH');
|
||||
if (unh && unh.elements[1]) {
|
||||
stats.messageType = unh.elements[1][0] || '';
|
||||
stats.messageVersion = (unh.elements[1][1] || '') + (unh.elements[1][2] || '');
|
||||
stats.controlRef = unh.elements[0]?.[0] || '';
|
||||
}
|
||||
}
|
||||
|
||||
if (untCount === 0) {
|
||||
errors.push('Kein UNT-Segment (Message-Trailer) gefunden.');
|
||||
}
|
||||
|
||||
if (unhCount !== untCount) {
|
||||
errors.push(`UNH/UNT-Anzahl stimmt nicht überein: ${unhCount} × UNH vs ${untCount} × UNT.`);
|
||||
}
|
||||
|
||||
// --- Step 4: UNT segment count check ---
|
||||
// UNT+<segCount>+<msgRef>' — segCount includes UNH and UNT itself
|
||||
const unt = segments.find(s => s.name === 'UNT');
|
||||
if (unt && unt.elements[0]) {
|
||||
const declared = parseInt(unt.elements[0][0]);
|
||||
// Find the slice from UNH to UNT (inclusive)
|
||||
const unhIdx = segments.findIndex(s => s.name === 'UNH');
|
||||
const untIdx = segments.findIndex(s => s.name === 'UNT');
|
||||
if (!isNaN(declared) && unhIdx >= 0 && untIdx >= 0) {
|
||||
const actual = untIdx - unhIdx + 1;
|
||||
if (declared !== actual) {
|
||||
warnings.push(
|
||||
`UNT deklariert ${declared} Segmente, gezählt (UNH→UNT): ${actual}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Step 5: BGM check ---
|
||||
if (!tags.includes('BGM')) {
|
||||
warnings.push('Kein BGM-Segment (Nachrichtenbeginn) gefunden.');
|
||||
} else {
|
||||
const bgm = segments.find(s => s.name === 'BGM');
|
||||
if (bgm && bgm.elements[0]) stats.documentCode = bgm.elements[0][0] || '';
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
errors.push('Unerwarteter Validierungsfehler: ' + e.message);
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
warnings,
|
||||
stats
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { validateEdifact, parseWithLib };
|
||||
345
js/editor.js
Normal file
345
js/editor.js
Normal file
@@ -0,0 +1,345 @@
|
||||
/**
|
||||
* EDI Document Editor
|
||||
* Allows parsing, visualizing, editing and re-exporting EDI/VDA files
|
||||
*/
|
||||
window.EDIBridge = window.EDIBridge || {};
|
||||
|
||||
class Editor {
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
this.initElements();
|
||||
this.bindEvents();
|
||||
this.currentDoc = null; // Store { type: 'edifact'|'vda', data: ..., raw: ... }
|
||||
}
|
||||
|
||||
initElements() {
|
||||
this.dropZone = document.getElementById('editorDropZone');
|
||||
this.fileInput = document.getElementById('editorFileInput');
|
||||
this.renderArea = document.getElementById('editorDocumentRender');
|
||||
this.btnDownload = document.getElementById('btnEditorDownload');
|
||||
this.btnBack = document.getElementById('btnEditorBack');
|
||||
this.dropText = document.getElementById('editorDropText');
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
if (this.dropZone) {
|
||||
this.dropZone.onclick = (e) => { e.stopPropagation(); if (this.fileInput) this.fileInput.click(); };
|
||||
this.dropZone.ondragover = (e) => { e.preventDefault(); this.dropZone.classList.add('dragover'); };
|
||||
this.dropZone.ondragleave = () => this.dropZone.classList.remove('dragover');
|
||||
this.dropZone.ondrop = (e) => {
|
||||
e.preventDefault();
|
||||
this.dropZone.classList.remove('dragover');
|
||||
this.handleFile(e.dataTransfer.files[0]);
|
||||
};
|
||||
}
|
||||
|
||||
if (this.fileInput) {
|
||||
this.fileInput.onchange = (e) => this.handleFile(e.target.files[0]);
|
||||
}
|
||||
}
|
||||
|
||||
handleFile(file) {
|
||||
if (!file) return;
|
||||
this.fileName = file.name;
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
this.processContent(e.target.result);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
processContent(content) {
|
||||
const trimmed = content.trim();
|
||||
const upper = trimmed.toUpperCase();
|
||||
|
||||
try {
|
||||
if (upper.startsWith('UNA') || upper.startsWith('UNB')) {
|
||||
this.parseAndRenderEdifact(trimmed);
|
||||
} else if (upper.startsWith('711') || upper.startsWith('511')) {
|
||||
this.parseAndRenderVDA(trimmed);
|
||||
} else {
|
||||
alert('Unbekanntes Dateiformat (Weder EDIFACT noch VDA).');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert('Fehler beim Laden/Parsen der Datei im Editor: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
parseAndRenderEdifact(content) {
|
||||
const parser = window.EDIBridge.DelforParser;
|
||||
if (!parser) throw new Error('DelforParser nicht geladen.');
|
||||
|
||||
const parsed = parser.parse(content);
|
||||
|
||||
this.currentDoc = {
|
||||
type: 'edifact',
|
||||
meta: parsed.meta,
|
||||
segments: parsed.segments,
|
||||
raw: content
|
||||
};
|
||||
|
||||
this.renderEdifactEditor();
|
||||
}
|
||||
|
||||
parseAndRenderVDA(content) {
|
||||
const lines = content.split(/\r?\n/).filter(l => l.length >= 3);
|
||||
const segments = lines.map(l => {
|
||||
return {
|
||||
tag: l.substring(0, 3),
|
||||
rawLine: l,
|
||||
fields: this.splitVdaLine(l)
|
||||
};
|
||||
});
|
||||
|
||||
this.currentDoc = {
|
||||
type: 'vda',
|
||||
segments: segments,
|
||||
raw: content
|
||||
};
|
||||
|
||||
this.renderVdaEditor();
|
||||
}
|
||||
|
||||
// A helper to roughly split VDA into fields (since VDA is fixed length, this is a bit arbitrary for editing unless we know exact positions. For generic editing, we can let user deal with the raw string or fixed chunks if we define them. Let's do raw string editing for VDA segments initially to prevent destroying structure, or simple chunking)
|
||||
splitVdaLine(line) {
|
||||
// As a simple generic VDA editor, we split it into Tag and the rest of the string
|
||||
return [
|
||||
{ label: 'Satzart', value: line.substring(0, 3), readonly: true },
|
||||
{ label: 'Daten', value: line.substring(3), length: line.length - 3 }
|
||||
];
|
||||
}
|
||||
|
||||
renderEdifactEditor() {
|
||||
let html = `
|
||||
<div style="margin-bottom: 20px; font-size: 0.9rem; color: var(--text-dim);">
|
||||
Datei: <strong>${this.fileName}</strong> | Typ: EDIFACT | Segmente: ${this.currentDoc.segments.length}
|
||||
</div>
|
||||
<div class="editor-segments-list">
|
||||
`;
|
||||
|
||||
this.currentDoc.segments.forEach((seg, sIdx) => {
|
||||
html += `
|
||||
<div class="editor-segment" data-segment-index="${sIdx}">
|
||||
<div class="editor-segment-header">
|
||||
<div>Segment <span class="editor-segment-tag">${seg.tag}</span></div>
|
||||
</div>
|
||||
<div class="editor-segment-fields">
|
||||
`;
|
||||
|
||||
seg.elements.forEach((el, elIdx) => {
|
||||
if (Array.isArray(el)) {
|
||||
el.forEach((subEl, subIdx) => {
|
||||
html += `
|
||||
<div class="editor-field">
|
||||
<label>E${elIdx + 1}.${subIdx + 1}</label>
|
||||
<input type="text" class="editor-field-input"
|
||||
data-seg="${sIdx}" data-el="${elIdx}" data-sub="${subIdx}"
|
||||
value="${this.escapeHtml(subEl)}"
|
||||
onchange="app.editor.updateEdifactField(this)">
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
} else {
|
||||
html += `
|
||||
<div class="editor-field">
|
||||
<label>E${elIdx + 1}</label>
|
||||
<input type="text" class="editor-field-input"
|
||||
data-seg="${sIdx}" data-el="${elIdx}" data-sub="-1"
|
||||
value="${this.escapeHtml(el)}"
|
||||
onchange="app.editor.updateEdifactField(this)">
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
});
|
||||
|
||||
html += `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += `</div>`;
|
||||
this.showEditor(html);
|
||||
}
|
||||
|
||||
renderVdaEditor() {
|
||||
let html = `
|
||||
<div style="margin-bottom: 20px; font-size: 0.9rem; color: var(--text-dim);">
|
||||
Datei: <strong>${this.fileName}</strong> | Typ: VDA | Sätze: ${this.currentDoc.segments.length}
|
||||
</div>
|
||||
<div class="editor-segments-list">
|
||||
`;
|
||||
|
||||
this.currentDoc.segments.forEach((seg, sIdx) => {
|
||||
html += `
|
||||
<div class="editor-segment" data-segment-index="${sIdx}">
|
||||
<div class="editor-segment-header">
|
||||
<div>Satzart <span class="editor-segment-tag">${seg.tag}</span></div>
|
||||
<div style="font-size: 0.75rem; color: var(--text-dim); font-weight: normal;">Länge: <span id="vda-len-${sIdx}">${seg.rawLine.length}</span></div>
|
||||
</div>
|
||||
<div class="editor-segment-fields" style="flex-direction: column;">
|
||||
`;
|
||||
|
||||
seg.fields.forEach((field, fIdx) => {
|
||||
if (field.readonly) {
|
||||
html += `
|
||||
<div class="editor-field" style="flex-direction: row; align-items: center;">
|
||||
<label style="width: 60px;">${field.label}</label>
|
||||
<input type="text" class="editor-field-input" value="${field.value}" readonly disabled style="opacity: 0.6; width: 60px;">
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
html += `
|
||||
<div class="editor-field">
|
||||
<label>${field.label} (Original Länge: ${field.length})</label>
|
||||
<input type="text" class="editor-field-input" style="font-family: 'Fira Code', monospace; width: 100%; min-width: 500px;"
|
||||
data-seg="${sIdx}" data-field="${fIdx}"
|
||||
value="${this.escapeHtml(field.value)}"
|
||||
oninput="app.editor.updateVdaFieldLength(${sIdx}, this.value)"
|
||||
onchange="app.editor.updateVdaField(this)">
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
});
|
||||
|
||||
html += `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += `</div>`;
|
||||
this.showEditor(html);
|
||||
}
|
||||
|
||||
updateEdifactField(inputElem) {
|
||||
const sIdx = parseInt(inputElem.getAttribute('data-seg'));
|
||||
const elIdx = parseInt(inputElem.getAttribute('data-el'));
|
||||
const subIdx = parseInt(inputElem.getAttribute('data-sub'));
|
||||
const val = inputElem.value;
|
||||
|
||||
if (subIdx === -1) {
|
||||
this.currentDoc.segments[sIdx].elements[elIdx] = val;
|
||||
} else {
|
||||
this.currentDoc.segments[sIdx].elements[elIdx][subIdx] = val;
|
||||
}
|
||||
}
|
||||
|
||||
updateVdaFieldLength(sIdx, value) {
|
||||
const lenSpan = document.getElementById(`vda-len-${sIdx}`);
|
||||
if (lenSpan) {
|
||||
lenSpan.textContent = value.length + 3; // +3 for tag
|
||||
// Suggesting length warnings could be added here later
|
||||
}
|
||||
}
|
||||
|
||||
updateVdaField(inputElem) {
|
||||
const sIdx = parseInt(inputElem.getAttribute('data-seg'));
|
||||
const fIdx = parseInt(inputElem.getAttribute('data-field'));
|
||||
const val = inputElem.value;
|
||||
this.currentDoc.segments[sIdx].fields[fIdx].value = val;
|
||||
|
||||
// Update rawLine
|
||||
const tag = this.currentDoc.segments[sIdx].fields[0].value;
|
||||
this.currentDoc.segments[sIdx].rawLine = tag + val;
|
||||
}
|
||||
|
||||
showEditor(html) {
|
||||
this.renderArea.innerHTML = html;
|
||||
this.dropZone.style.display = 'none';
|
||||
this.renderArea.style.display = 'block';
|
||||
if (this.btnDownload) this.btnDownload.style.display = 'inline-flex';
|
||||
if (this.btnBack) this.btnBack.style.display = 'inline-flex';
|
||||
|
||||
if (window.lucide) {
|
||||
lucide.createIcons();
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.currentDoc = null;
|
||||
this.renderArea.innerHTML = '';
|
||||
this.renderArea.style.display = 'none';
|
||||
this.dropZone.style.display = 'flex';
|
||||
if (this.btnDownload) this.btnDownload.style.display = 'none';
|
||||
if (this.btnBack) this.btnBack.style.display = 'none';
|
||||
if (this.fileInput) this.fileInput.value = '';
|
||||
}
|
||||
|
||||
downloadDocument() {
|
||||
if (!this.currentDoc) return;
|
||||
|
||||
let output = '';
|
||||
if (this.currentDoc.type === 'edifact') {
|
||||
output = this.rebuildEdifact();
|
||||
} else if (this.currentDoc.type === 'vda') {
|
||||
output = this.rebuildVda();
|
||||
}
|
||||
|
||||
const blob = new Blob([output], { type: 'text/plain;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
|
||||
let outName = this.fileName;
|
||||
if (!outName.includes('_edited')) {
|
||||
const parts = outName.split('.');
|
||||
if (parts.length > 1) {
|
||||
const ext = parts.pop();
|
||||
outName = parts.join('.') + '_edited.' + ext;
|
||||
} else {
|
||||
outName += '_edited';
|
||||
}
|
||||
}
|
||||
|
||||
a.href = url;
|
||||
a.download = outName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
rebuildEdifact() {
|
||||
const meta = this.currentDoc.meta;
|
||||
let out = '';
|
||||
|
||||
// Add UNA if it was present
|
||||
if (this.currentDoc.raw.startsWith('UNA')) {
|
||||
out += `UNA${meta.subSeparator}${meta.separator}${meta.decimal}${meta.release} ${meta.terminator}`;
|
||||
}
|
||||
|
||||
this.currentDoc.segments.forEach(seg => {
|
||||
let segStr = seg.tag;
|
||||
seg.elements.forEach(el => {
|
||||
segStr += meta.separator;
|
||||
if (Array.isArray(el)) {
|
||||
segStr += el.join(meta.subSeparator);
|
||||
} else {
|
||||
segStr += el;
|
||||
}
|
||||
});
|
||||
out += segStr + meta.terminator + "\\r\\n";
|
||||
});
|
||||
|
||||
// Very basic rebuilding: standardizing newlines
|
||||
return out.replace(/\\r\\n/g, '\r\n');
|
||||
}
|
||||
|
||||
rebuildVda() {
|
||||
return this.currentDoc.segments.map(s => s.rawLine).join('\r\n');
|
||||
}
|
||||
|
||||
escapeHtml(unsafe) {
|
||||
if (unsafe === undefined || unsafe === null) return '';
|
||||
return unsafe
|
||||
.toString()
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
}
|
||||
|
||||
window.EDIBridge.Editor = Editor;
|
||||
413
js/history.js
Normal file
413
js/history.js
Normal file
@@ -0,0 +1,413 @@
|
||||
/**
|
||||
* HistoryManager – Frontend for conversion history
|
||||
* Renders paginated table, detail view, stats, and filters.
|
||||
*/
|
||||
window.EDIBridge = window.EDIBridge || {};
|
||||
|
||||
class HistoryManager {
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
this.currentPage = 1;
|
||||
this.pageSize = 20;
|
||||
this.statusFilter = 'ALL';
|
||||
this.dateFilter = 'ALL';
|
||||
this.searchTerm = '';
|
||||
this.totalRows = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the History page becomes visible.
|
||||
*/
|
||||
async activate() {
|
||||
await this.loadStats();
|
||||
await this.loadTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and render statistics cards.
|
||||
*/
|
||||
async loadStats() {
|
||||
if (!window.electronAPI) return;
|
||||
const stats = await window.electronAPI.dbGetStats();
|
||||
const container = document.getElementById('historyStats');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="stat-card ${this.statusFilter === 'ALL' && this.dateFilter === 'ALL' ? 'active' : ''}"
|
||||
onclick="app.historyManager.setFilter('ALL')" style="cursor:pointer;">
|
||||
<div class="stat-value">${stats.total}</div>
|
||||
<div class="stat-label">Gesamt</div>
|
||||
</div>
|
||||
<div class="stat-card ${this.statusFilter === 'ERFOLGREICH' ? 'active' : ''}"
|
||||
onclick="app.historyManager.setFilter('ERFOLGREICH')" style="cursor:pointer; border-color: var(--success);">
|
||||
<div class="stat-value" style="color: var(--success);">${stats.success}</div>
|
||||
<div class="stat-label">Erfolgreich</div>
|
||||
</div>
|
||||
<div class="stat-card ${this.statusFilter === 'FEHLERHAFT' ? 'active' : ''}"
|
||||
onclick="app.historyManager.setFilter('FEHLERHAFT')" style="cursor:pointer; border-color: var(--danger);">
|
||||
<div class="stat-value" style="color: var(--danger);">${stats.error}</div>
|
||||
<div class="stat-label">Fehlerhaft</div>
|
||||
</div>
|
||||
<div class="stat-card ${this.dateFilter === 'TODAY' ? 'active' : ''}"
|
||||
onclick="app.historyManager.setDateFilter('TODAY')" style="cursor:pointer;">
|
||||
<div class="stat-value" style="color: var(--accent);">${stats.today}</div>
|
||||
<div class="stat-label">Heute</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and render the conversion table.
|
||||
*/
|
||||
async loadTable() {
|
||||
if (!window.electronAPI) return;
|
||||
|
||||
const offset = (this.currentPage - 1) * this.pageSize;
|
||||
const params = {
|
||||
limit: this.pageSize,
|
||||
offset,
|
||||
statusFilter: this.statusFilter,
|
||||
dateFilter: this.dateFilter,
|
||||
search: this.searchTerm
|
||||
};
|
||||
console.log('[History] Loading table with params:', params);
|
||||
const result = await window.electronAPI.dbGetAll(params);
|
||||
|
||||
this.totalRows = result.total;
|
||||
const totalPages = Math.max(1, Math.ceil(result.total / this.pageSize));
|
||||
|
||||
const tbody = document.getElementById('historyTableBody');
|
||||
const pagination = document.getElementById('historyPagination');
|
||||
if (!tbody) return;
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
tbody.innerHTML = `<tr><td colspan="6" style="text-align:center; padding:40px; color: var(--text-dim); font-style:italic;">Keine Einträge gefunden.</td></tr>`;
|
||||
} else {
|
||||
tbody.innerHTML = result.rows.map(row => {
|
||||
const ts = this._formatTimestamp(row.zeitstempel);
|
||||
const statusIcon = row.status === 'ERFOLGREICH'
|
||||
? '<span style="color: var(--success);">✅</span>'
|
||||
: '<span style="color: var(--danger);">❌</span>';
|
||||
const modus = this._shortModus(row.konvertierungsmodus, row.quellformat, row.zielformat);
|
||||
const source = row.quelle === 'WATCHER'
|
||||
? '<span class="badge badge-config" style="font-size:0.65rem;">AUTO</span>'
|
||||
: '<span class="badge badge-edi" style="font-size:0.65rem;">GUI</span>';
|
||||
|
||||
return `
|
||||
<tr class="history-row" onclick="app.historyManager.showDetail(${row.id})" style="cursor:pointer;">
|
||||
<td style="color: var(--text-dim); font-size:0.8rem;">${row.id}</td>
|
||||
<td>${ts}</td>
|
||||
<td>
|
||||
<div style="font-weight:600;">${this._escapeHtml(row.dateiname)}</div>
|
||||
${row.ausgangsdateiname ? `<div style="font-size:0.75rem; color:var(--text-dim);">→ ${this._escapeHtml(row.ausgangsdateiname)}</div>` : ''}
|
||||
</td>
|
||||
<td>${modus}</td>
|
||||
<td>${statusIcon} ${source}</td>
|
||||
<td>
|
||||
<button class="btn-secondary" style="padding:4px 8px; font-size:0.7rem;"
|
||||
onclick="event.stopPropagation(); app.historyManager.deleteEntry(${row.id})">
|
||||
<i data-lucide="trash-2" style="width:12px;height:12px;"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
if (pagination) {
|
||||
pagination.innerHTML = `
|
||||
<button class="btn-secondary" ${this.currentPage <= 1 ? 'disabled' : ''}
|
||||
onclick="app.historyManager.goToPage(${this.currentPage - 1})">
|
||||
<i data-lucide="chevron-left" style="width:14px;height:14px;"></i> Zurück
|
||||
</button>
|
||||
<span style="color: var(--text-dim); font-size:0.85rem;">
|
||||
Seite ${this.currentPage} von ${totalPages} (${result.total} Einträge)
|
||||
</span>
|
||||
<button class="btn-secondary" ${this.currentPage >= totalPages ? 'disabled' : ''}
|
||||
onclick="app.historyManager.goToPage(${this.currentPage + 1})">
|
||||
Weiter <i data-lucide="chevron-right" style="width:14px;height:14px;"></i>
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
// Re-render icons
|
||||
if (window.lucide) {
|
||||
try { lucide.createIcons(); } catch (e) { }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show detail view for a single conversion.
|
||||
*/
|
||||
async showDetail(id) {
|
||||
if (!window.electronAPI) return;
|
||||
const record = await window.electronAPI.dbGetById(id);
|
||||
if (!record) return;
|
||||
|
||||
const modal = document.getElementById('historyDetailModal');
|
||||
const content = document.getElementById('historyDetailContent');
|
||||
if (!modal || !content) return;
|
||||
|
||||
const statusBadge = record.status === 'ERFOLGREICH'
|
||||
? '<span style="color:var(--success); font-weight:700;">✅ ERFOLGREICH</span>'
|
||||
: '<span style="color:var(--danger); font-weight:700;">❌ FEHLERHAFT</span>';
|
||||
|
||||
content.innerHTML = `
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
|
||||
<h3 style="margin:0; font-size:1.1rem;">Konvertierung #${record.id}</h3>
|
||||
<button class="btn-secondary" onclick="app.historyManager.closeDetail()">
|
||||
<i data-lucide="x" style="width:14px;height:14px;"></i> Schließen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="history-detail-grid">
|
||||
<div class="history-detail-item">
|
||||
<span class="label">Zeitstempel</span>
|
||||
<strong>${this._formatTimestamp(record.zeitstempel)}</strong>
|
||||
</div>
|
||||
<div class="history-detail-item">
|
||||
<span class="label">Status</span>
|
||||
${statusBadge}
|
||||
</div>
|
||||
<div class="history-detail-item">
|
||||
<span class="label">Eingangsdatei</span>
|
||||
<strong>${this._escapeHtml(record.dateiname)}</strong>
|
||||
</div>
|
||||
<div class="history-detail-item">
|
||||
<span class="label">Ausgangsdatei</span>
|
||||
<strong>${this._escapeHtml(record.ausgangsdateiname || '-')}</strong>
|
||||
</div>
|
||||
<div class="history-detail-item">
|
||||
<span class="label">Quellformat</span>
|
||||
<strong>${this._escapeHtml(record.quellformat || '-')}</strong>
|
||||
</div>
|
||||
<div class="history-detail-item">
|
||||
<span class="label">Zielformat</span>
|
||||
<strong>${this._escapeHtml(record.zielformat || '-')}</strong>
|
||||
</div>
|
||||
<div class="history-detail-item">
|
||||
<span class="label">Konvertierungsmodus</span>
|
||||
<strong>${this._escapeHtml(record.konvertierungsmodus || '-')}</strong>
|
||||
</div>
|
||||
<div class="history-detail-item">
|
||||
<span class="label">Kunden-ID</span>
|
||||
<strong>${this._escapeHtml(record.kunden_id || '-')}</strong>
|
||||
</div>
|
||||
<div class="history-detail-item">
|
||||
<span class="label">Quelle</span>
|
||||
<strong>${record.quelle === 'WATCHER' ? '🤖 Automatisch (Watcher)' : '👤 Manuell (GUI)'}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${record.fehlermeldung ? `
|
||||
<div style="background: rgba(239,68,68,0.1); border:1px solid rgba(239,68,68,0.3); border-radius:8px; padding:12px; margin:15px 0; color: #fca5a5;">
|
||||
<strong>Fehlermeldung:</strong><br>
|
||||
${this._escapeHtml(record.fehlermeldung)}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="history-detail-tabs">
|
||||
<button class="btn-secondary history-tab-btn active" data-tab="eingang" onclick="app.historyManager.switchTab('eingang')">
|
||||
📄 Eingangsdaten
|
||||
</button>
|
||||
<button class="btn-secondary history-tab-btn" data-tab="ausgang" onclick="app.historyManager.switchTab('ausgang')">
|
||||
📄 Ausgangsdaten
|
||||
</button>
|
||||
${record.ausgang_daten ? `
|
||||
<button class="btn-secondary" onclick="app.historyManager.openInViewer(${record.id})">
|
||||
<i data-lucide="eye" style="width:14px;height:14px;"></i> Im Viewer öffnen
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
<div id="historyTabEingang" class="history-tab-content">
|
||||
<pre style="max-height:400px; overflow:auto; font-size:0.8rem;">${this._escapeHtml(record.eingang_daten || 'Keine Eingangsdaten gespeichert.')}</pre>
|
||||
</div>
|
||||
<div id="historyTabAusgang" class="history-tab-content" style="display:none;">
|
||||
<pre style="max-height:400px; overflow:auto; font-size:0.8rem;">${this._escapeHtml(record.ausgang_daten || 'Keine Ausgangsdaten gespeichert.')}</pre>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.style.display = 'flex';
|
||||
|
||||
if (window.lucide) {
|
||||
try { lucide.createIcons(); } catch (e) { }
|
||||
}
|
||||
}
|
||||
|
||||
switchTab(tab) {
|
||||
const eingang = document.getElementById('historyTabEingang');
|
||||
const ausgang = document.getElementById('historyTabAusgang');
|
||||
const buttons = document.querySelectorAll('.history-tab-btn');
|
||||
|
||||
buttons.forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.tab === tab);
|
||||
});
|
||||
|
||||
if (tab === 'eingang') {
|
||||
if (eingang) eingang.style.display = 'block';
|
||||
if (ausgang) ausgang.style.display = 'none';
|
||||
} else {
|
||||
if (eingang) eingang.style.display = 'none';
|
||||
if (ausgang) ausgang.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
async openInViewer(id) {
|
||||
const record = await window.electronAPI.dbGetById(id);
|
||||
if (!record || !record.ausgang_daten) return;
|
||||
|
||||
this.closeDetail();
|
||||
// Switch to viewer page and process the content
|
||||
if (this.app) {
|
||||
this.app.showPage('viewer');
|
||||
if (this.app.viewer) {
|
||||
this.app.viewer.processContent(record.ausgang_daten);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closeDetail() {
|
||||
const modal = document.getElementById('historyDetailModal');
|
||||
if (modal) modal.style.display = 'none';
|
||||
}
|
||||
|
||||
async deleteEntry(id) {
|
||||
if (!confirm('Eintrag wirklich löschen?')) return;
|
||||
await window.electronAPI.dbDelete(id);
|
||||
await this.loadStats();
|
||||
await this.loadTable();
|
||||
}
|
||||
|
||||
goToPage(page) {
|
||||
if (page < 1) return;
|
||||
this.currentPage = page;
|
||||
this.loadTable();
|
||||
}
|
||||
|
||||
setFilter(status) {
|
||||
this.statusFilter = status;
|
||||
this.dateFilter = 'ALL';
|
||||
this.searchTerm = ''; // Clear search when picking a card
|
||||
this.currentPage = 1;
|
||||
|
||||
// Reset search input
|
||||
const searchInput = document.getElementById('historySearch');
|
||||
if (searchInput) searchInput.value = '';
|
||||
|
||||
// Update active button state
|
||||
document.querySelectorAll('.history-filter-btn').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.filter === status);
|
||||
});
|
||||
|
||||
this.loadStats();
|
||||
this.loadTable();
|
||||
}
|
||||
|
||||
setDateFilter(dateType) {
|
||||
this.dateFilter = dateType;
|
||||
this.statusFilter = 'ALL'; // Reset status when picking date
|
||||
this.searchTerm = ''; // Clear search when picking a card
|
||||
this.currentPage = 1;
|
||||
|
||||
// Reset filter bar buttons
|
||||
document.querySelectorAll('.history-filter-btn').forEach(btn => {
|
||||
btn.classList.toggle('active', false);
|
||||
});
|
||||
|
||||
// Reset search input
|
||||
const searchInput = document.getElementById('historySearch');
|
||||
if (searchInput) searchInput.value = '';
|
||||
|
||||
this.loadStats();
|
||||
this.loadTable();
|
||||
}
|
||||
|
||||
onSearch(value) {
|
||||
this.searchTerm = value;
|
||||
this.currentPage = 1;
|
||||
// Debounce
|
||||
clearTimeout(this._searchTimer);
|
||||
this._searchTimer = setTimeout(() => this.loadTable(), 300);
|
||||
}
|
||||
|
||||
async exportCSV() {
|
||||
if (!window.electronAPI) return;
|
||||
|
||||
// Fetch all rows (no pagination)
|
||||
const result = await window.electronAPI.dbGetAll({
|
||||
limit: 99999,
|
||||
offset: 0,
|
||||
statusFilter: this.statusFilter,
|
||||
search: this.searchTerm
|
||||
});
|
||||
|
||||
if (!result.rows || result.rows.length === 0) {
|
||||
alert('Keine Daten zum Exportieren.');
|
||||
return;
|
||||
}
|
||||
|
||||
const headers = ['ID', 'Zeitstempel', 'Dateiname', 'Ausgangsdatei', 'Quellformat', 'Zielformat', 'Modus', 'Kunden-ID', 'Status', 'Fehlermeldung', 'Quelle'];
|
||||
const csvRows = [headers.join(';')];
|
||||
|
||||
result.rows.forEach(row => {
|
||||
csvRows.push([
|
||||
row.id,
|
||||
row.zeitstempel,
|
||||
`"${(row.dateiname || '').replace(/"/g, '""')}"`,
|
||||
`"${(row.ausgangsdateiname || '').replace(/"/g, '""')}"`,
|
||||
row.quellformat || '',
|
||||
row.zielformat || '',
|
||||
row.konvertierungsmodus || '',
|
||||
row.kunden_id || '',
|
||||
row.status,
|
||||
`"${(row.fehlermeldung || '').replace(/"/g, '""')}"`,
|
||||
row.quelle
|
||||
].join(';'));
|
||||
});
|
||||
|
||||
const csv = csvRows.join('\n');
|
||||
const blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `konvertierungen_${new Date().toISOString().slice(0, 10)}.csv`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────
|
||||
|
||||
_formatTimestamp(ts) {
|
||||
if (!ts) return '-';
|
||||
try {
|
||||
const d = new Date(ts.replace(' ', 'T'));
|
||||
return d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })
|
||||
+ ' ' + d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
} catch (e) {
|
||||
return ts;
|
||||
}
|
||||
}
|
||||
|
||||
_shortModus(modus, quellformat, zielformat) {
|
||||
if (quellformat && zielformat) {
|
||||
// Shorten for display
|
||||
const qf = quellformat.replace('EDIFACT ', '').replace('D04A', '').trim();
|
||||
const zf = zielformat.replace('EDIFACT ', '').trim();
|
||||
return `<span style="font-size:0.8rem;">${this._escapeHtml(qf)} → ${this._escapeHtml(zf)}</span>`;
|
||||
}
|
||||
if (modus) return `<span style="font-size:0.8rem;">${this._escapeHtml(modus)}</span>`;
|
||||
return '<span style="color:var(--text-dim);">-</span>';
|
||||
}
|
||||
|
||||
_escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
}
|
||||
|
||||
window.EDIBridge.HistoryManager = HistoryManager;
|
||||
467
js/ifm_delfor-vda4905.js
Normal file
467
js/ifm_delfor-vda4905.js
Normal file
@@ -0,0 +1,467 @@
|
||||
/**
|
||||
* ifm electronic DELFOR D04A to VDA 4905 Converter
|
||||
* Converts EDIFACT DELFOR D04A (ifm electronic format) to VDA 4905
|
||||
*
|
||||
* Uses:
|
||||
* - DelforParser: Parses EDIFACT DELFOR into structured segments
|
||||
* - VDA4905Generator: Generates VDA 4905 output
|
||||
*
|
||||
* Based on ifm_electronic_DELFOR_D04A specification
|
||||
*/
|
||||
window.EDIBridge = window.EDIBridge || {};
|
||||
|
||||
class DelforToVDA4905Converter {
|
||||
|
||||
/**
|
||||
* Main conversion function
|
||||
* @param {string} delforContent - Raw EDIFACT DELFOR D04A content
|
||||
* @returns {string} VDA 4905 formatted output
|
||||
*/
|
||||
static convert(delforContent) {
|
||||
const DelforParser = window.EDIBridge.DelforParser;
|
||||
const VDA4905Generator = window.EDIBridge.VDA4905Generator;
|
||||
|
||||
if (!DelforParser) {
|
||||
throw new Error('DelforParser not found. Please include delfor-parser.js');
|
||||
}
|
||||
if (!VDA4905Generator) {
|
||||
throw new Error('VDA4905Generator not found. Please include vda4905-generator.js');
|
||||
}
|
||||
|
||||
// Step 1: Parse DELFOR
|
||||
const parsed = DelforParser.parse(delforContent);
|
||||
|
||||
// Step 2: Transform to VDA4905-compatible structure
|
||||
const transformed = this.transformDelforStructure(parsed);
|
||||
|
||||
// Step 3: Generate VDA 4905
|
||||
return VDA4905Generator.generate(transformed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform DELFOR D04A structure to match VDA4905Generator expectations
|
||||
* Handles ifm electronic specific segment mappings
|
||||
*/
|
||||
static transformDelforStructure(parsed) {
|
||||
const segments = parsed.segments;
|
||||
const result = { segments: [] };
|
||||
|
||||
// Helper to get segment value
|
||||
const getVal = (seg, elIdx, compIdx) => {
|
||||
if (!seg || !seg.elements) return '';
|
||||
const el = seg.elements[elIdx];
|
||||
if (!el) return '';
|
||||
if (Array.isArray(el)) return el[compIdx] || '';
|
||||
if (compIdx === 0) return el;
|
||||
return '';
|
||||
};
|
||||
|
||||
// Copy all segments but enhance/transform as needed
|
||||
for (const seg of segments) {
|
||||
const transformed = this.transformSegment(seg, segments);
|
||||
if (transformed) {
|
||||
result.segments.push(transformed);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform individual segment according to ifm electronic DELFOR D04A spec
|
||||
*/
|
||||
static transformSegment(seg, allSegments) {
|
||||
// Most segments pass through unchanged
|
||||
// Add specific transformations based on ifm electronic requirements
|
||||
|
||||
switch (seg.tag) {
|
||||
case 'UNH':
|
||||
// Message Header - verify DELFOR type
|
||||
return this.validateUNH(seg);
|
||||
|
||||
case 'BGM':
|
||||
// Beginning of Message - Document code 241 expected
|
||||
return this.transformBGM(seg);
|
||||
|
||||
case 'DTM':
|
||||
// Date/Time/Period - handle various qualifiers
|
||||
return this.transformDTM(seg);
|
||||
|
||||
case 'RFF':
|
||||
// Reference - handle ON (Order Number), AAN, etc.
|
||||
return this.transformRFF(seg);
|
||||
|
||||
case 'NAD':
|
||||
// Name and Address - BY, SE, etc.
|
||||
return this.transformNAD(seg);
|
||||
|
||||
case 'LIN':
|
||||
// Line Item - Article information
|
||||
return this.transformLIN(seg);
|
||||
|
||||
case 'QTY':
|
||||
// Quantity - handle 194 (cumulative), 113 (schedule), etc.
|
||||
return this.transformQTY(seg);
|
||||
|
||||
case 'SCC':
|
||||
// Scheduling Conditions
|
||||
return this.transformSCC(seg);
|
||||
|
||||
case 'GEI':
|
||||
// Processing Information
|
||||
return this.transformGEI(seg);
|
||||
|
||||
case 'LOC':
|
||||
// Location - Plant, Unloading Point
|
||||
return seg;
|
||||
|
||||
case 'CTA':
|
||||
case 'COM':
|
||||
// Contact information - pass through
|
||||
return seg;
|
||||
|
||||
default:
|
||||
return seg;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate UNH segment - must be DELFOR:D:04A:UN
|
||||
*/
|
||||
static validateUNH(seg) {
|
||||
const elements = seg.elements || [];
|
||||
// UNH+172354030+DELFOR:D:04A:UN
|
||||
// elements[0] = message reference number
|
||||
// elements[1] = [DELFOR, D, 04A, UN]
|
||||
|
||||
if (elements[1]) {
|
||||
const msgType = Array.isArray(elements[1]) ? elements[1][0] : elements[1];
|
||||
if (msgType !== 'DELFOR') {
|
||||
console.warn('Warning: Expected DELFOR message type, got:', msgType);
|
||||
}
|
||||
}
|
||||
|
||||
return seg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform BGM segment
|
||||
* Expected: BGM+241+177754999+9
|
||||
* - 241 = Delivery schedule message
|
||||
* - Document identifier
|
||||
* - 9 = Original
|
||||
*/
|
||||
static transformBGM(seg) {
|
||||
// Pass through - VDA4905Generator handles this
|
||||
return seg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform DTM segment
|
||||
* Handle ifm electronic specific date qualifiers:
|
||||
* - 137: Document date
|
||||
* - 4: Order date
|
||||
* - 171: Reference date/time
|
||||
* - 64: Earliest delivery date
|
||||
*/
|
||||
static transformDTM(seg) {
|
||||
const elements = seg.elements || [];
|
||||
const qualifier = Array.isArray(elements[0]) ? elements[0][0] : elements[0];
|
||||
|
||||
// Map ifm electronic qualifiers to VDA4905 expected qualifiers
|
||||
const qualifierMap = {
|
||||
'137': '137', // Document date -> Document date
|
||||
'4': '4', // Order date
|
||||
'171': '171', // Reference date
|
||||
'64': '64', // Earliest delivery date
|
||||
'63': '63', // Latest delivery date
|
||||
'2': '2', // Delivery date
|
||||
'10': '10', // Shipment date
|
||||
'11': '11', // Dispatch date
|
||||
'50': '50', // Goods receipt date
|
||||
'131': '131', // Tax point date
|
||||
'192': '192', // Previous document date
|
||||
'242': '242' // Actual date
|
||||
};
|
||||
|
||||
return seg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform RFF segment
|
||||
* Handle ifm electronic references:
|
||||
* - ON: Order number (5500002000)
|
||||
* - AAN: Delivery schedule number
|
||||
* - AIF: Previous release number
|
||||
*/
|
||||
static transformRFF(seg) {
|
||||
const elements = seg.elements || [];
|
||||
const qualifier = Array.isArray(elements[0]) ? elements[0][0] : elements[0];
|
||||
|
||||
// RFF+ON:5500002000 -> Order reference
|
||||
// RFF+AAN:55 -> Delivery schedule number
|
||||
|
||||
return seg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform NAD segment
|
||||
* ifm electronic format:
|
||||
* NAD+BY+1100+ifm electronic gmbh+ifm electronic gmbh+ifm-Straße 1:Halle 16+Tettnang+88069+DE
|
||||
*/
|
||||
static transformNAD(seg) {
|
||||
const elements = seg.elements || [];
|
||||
const qualifier = Array.isArray(elements[0]) ? elements[0][0] : elements[0];
|
||||
|
||||
// Ensure proper structure for VDA4905Generator
|
||||
// BY = Buyer, SE = Seller, ST = Ship-to
|
||||
|
||||
return seg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform LIN segment
|
||||
* ifm electronic format:
|
||||
* LIN+00010++AK2258921:BP
|
||||
* - Line item number
|
||||
* - (empty)
|
||||
* - Item identifier:BP (Buyer's part number)
|
||||
*/
|
||||
static transformLIN(seg) {
|
||||
const elements = seg.elements || [];
|
||||
|
||||
// Ensure item identifier is properly extracted
|
||||
// elements[2] should contain [ItemNumber, 'BP']
|
||||
|
||||
return seg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform QTY segment
|
||||
* ifm electronic qualifiers:
|
||||
* - 194: Cumulative quantity received
|
||||
* - 113: Quantity to be delivered (schedule)
|
||||
* - 1: Discrete quantity
|
||||
* - 12: Despatch quantity
|
||||
* - 21: Ordered quantity
|
||||
* - 48: Received quantity
|
||||
* - 70: Minimum stock quantity
|
||||
*/
|
||||
static transformQTY(seg) {
|
||||
const elements = seg.elements || [];
|
||||
const qualifier = Array.isArray(elements[0]) ? elements[0][0] : elements[0];
|
||||
|
||||
// Map ifm electronic quantity qualifiers
|
||||
const qualifierMap = {
|
||||
'194': '194', // Cumulative received
|
||||
'113': '113', // Schedule quantity (maps to VDA 4905 schedule)
|
||||
'1': '1', // Discrete quantity
|
||||
'12': '12', // Despatch quantity
|
||||
'21': '21', // Ordered quantity
|
||||
'48': '48', // Received quantity
|
||||
'70': '70' // Minimum stock
|
||||
};
|
||||
|
||||
return seg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform SCC segment
|
||||
* Scheduling Conditions:
|
||||
* SCC+1 = Firm schedule
|
||||
* SCC+4 = Planning schedule
|
||||
*/
|
||||
static transformSCC(seg) {
|
||||
return seg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform GEI segment
|
||||
* Processing Information:
|
||||
* GEI+3++35 = Process type
|
||||
*/
|
||||
static transformGEI(seg) {
|
||||
return seg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract schedule data from DELFOR for VDA 4905 records 513/514
|
||||
* Groups QTY segments with their associated DTM dates
|
||||
*/
|
||||
static extractSchedules(segments) {
|
||||
const schedules = [];
|
||||
let currentLin = null;
|
||||
|
||||
const getVal = (seg, elIdx, compIdx) => {
|
||||
if (!seg || !seg.elements) return '';
|
||||
const el = seg.elements[elIdx];
|
||||
if (!el) return '';
|
||||
if (Array.isArray(el)) return el[compIdx] || '';
|
||||
if (compIdx === 0) return el;
|
||||
return '';
|
||||
};
|
||||
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
const seg = segments[i];
|
||||
|
||||
if (seg.tag === 'LIN') {
|
||||
currentLin = {
|
||||
itemNumber: getVal(seg, 2, 0),
|
||||
schedules: []
|
||||
};
|
||||
}
|
||||
|
||||
if (seg.tag === 'QTY' && currentLin) {
|
||||
const qualifier = getVal(seg, 0, 0);
|
||||
const quantity = getVal(seg, 0, 1);
|
||||
const unit = getVal(seg, 0, 2);
|
||||
|
||||
// Look for associated DTM
|
||||
let deliveryDate = '';
|
||||
for (let j = i + 1; j < segments.length; j++) {
|
||||
const next = segments[j];
|
||||
if (next.tag === 'QTY' || next.tag === 'LIN' || next.tag === 'UNT') break;
|
||||
if (next.tag === 'DTM') {
|
||||
const dtmQual = getVal(next, 0, 0);
|
||||
if (['64', '2', '10', '11', '171'].includes(dtmQual)) {
|
||||
deliveryDate = getVal(next, 0, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only include schedule quantities (113, 1, 12, 21)
|
||||
if (['113', '1', '12', '21'].includes(qualifier)) {
|
||||
currentLin.schedules.push({
|
||||
qualifier,
|
||||
quantity,
|
||||
unit,
|
||||
date: deliveryDate
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return schedules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate DELFOR content before conversion
|
||||
*/
|
||||
static validate(delforContent) {
|
||||
const errors = [];
|
||||
const warnings = [];
|
||||
|
||||
// Check for required segments
|
||||
const requiredSegments = ['UNH', 'BGM', 'DTM', 'NAD', 'LIN', 'QTY', 'UNT'];
|
||||
|
||||
for (const tag of requiredSegments) {
|
||||
if (!delforContent.includes(tag + '+') && !delforContent.includes(tag + "'")) {
|
||||
errors.push(`Missing required segment: ${tag}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for DELFOR message type
|
||||
if (!delforContent.includes('DELFOR')) {
|
||||
errors.push('Not a DELFOR message');
|
||||
}
|
||||
|
||||
// Check for D04A version (optional warning)
|
||||
if (!delforContent.includes('D:04A') && !delforContent.includes(':D:04A:')) {
|
||||
warnings.push('Expected DELFOR D04A version');
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
warnings
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert with validation
|
||||
*/
|
||||
static convertWithValidation(delforContent) {
|
||||
const validation = this.validate(delforContent);
|
||||
|
||||
if (!validation.valid) {
|
||||
return {
|
||||
success: false,
|
||||
errors: validation.errors,
|
||||
warnings: validation.warnings,
|
||||
output: null
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const output = this.convert(delforContent);
|
||||
return {
|
||||
success: true,
|
||||
errors: [],
|
||||
warnings: validation.warnings,
|
||||
output
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
success: false,
|
||||
errors: [e.message],
|
||||
warnings: validation.warnings,
|
||||
output: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse DELFOR and return structured data for inspection
|
||||
*/
|
||||
static parseOnly(delforContent) {
|
||||
const DelforParser = window.EDIBridge.DelforParser;
|
||||
if (!DelforParser) {
|
||||
throw new Error('DelforParser not found');
|
||||
}
|
||||
return DelforParser.parse(delforContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mapping info for debugging
|
||||
*/
|
||||
static getMappingInfo() {
|
||||
return {
|
||||
name: 'DELFOR D04A to VDA 4905',
|
||||
source: 'EDIFACT DELFOR D04A (ifm electronic)',
|
||||
target: 'VDA 4905',
|
||||
version: '1.0.0',
|
||||
segments: {
|
||||
'UNH': 'Message Header -> VDA 511 (Header)',
|
||||
'BGM': 'Beginning of Message -> VDA 511',
|
||||
'DTM': 'Date/Time -> VDA 511, 512, 513',
|
||||
'RFF': 'Reference -> VDA 511, 512',
|
||||
'NAD': 'Name and Address -> VDA 511 (Customer/Supplier)',
|
||||
'LIN': 'Line Item -> VDA 512 (Article)',
|
||||
'QTY': 'Quantity -> VDA 513/514 (Schedules)',
|
||||
'SCC': 'Scheduling Conditions -> VDA 513/514',
|
||||
'UNT': 'Message Trailer -> VDA 519'
|
||||
},
|
||||
quantityQualifiers: {
|
||||
'194': 'Cumulative received -> VDA 513 (Cumulative)',
|
||||
'113': 'Schedule quantity -> VDA 513/514 (Schedule lines)',
|
||||
'1': 'Discrete quantity -> VDA 513/514',
|
||||
'12': 'Despatch quantity -> VDA 513',
|
||||
'48': 'Received quantity -> VDA 513',
|
||||
'70': 'Minimum stock -> VDA 513 (EFZ)'
|
||||
},
|
||||
dateQualifiers: {
|
||||
'137': 'Document date -> VDA 511',
|
||||
'171': 'Reference date -> VDA 513',
|
||||
'64': 'Earliest delivery -> VDA 513/514',
|
||||
'192': 'Previous document date -> VDA 512'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Register in EDIBridge namespace
|
||||
window.EDIBridge.DelforToVDA4905Converter = DelforToVDA4905Converter;
|
||||
|
||||
// Convenience alias
|
||||
window.EDIBridge.convertDelforToVDA4905 = (content) => DelforToVDA4905Converter.convert(content);
|
||||
398
js/invrpt-to-vda4913.js
Normal file
398
js/invrpt-to-vda4913.js
Normal file
@@ -0,0 +1,398 @@
|
||||
/**
|
||||
* INVRPT D13A → VDA 4913 EDL36/35 Converter
|
||||
*
|
||||
* Converts EDIFACT INVRPT (Inventory Report) messages into
|
||||
* VDA 4913 format with EDL36 (delivery note) and EDL35 (stock) records.
|
||||
*
|
||||
* Record structure produced:
|
||||
* 711 Header
|
||||
* 712 Transport (one per INVRPT message)
|
||||
* 713 Delivery note header (EDL36 or EDL35)
|
||||
* 714 Position (material / qty)
|
||||
* 719 Trailer
|
||||
*/
|
||||
window.EDIBridge = window.EDIBridge || {};
|
||||
|
||||
class InvrptToVDA4913 {
|
||||
|
||||
// ─── Public API ──────────────────────────────────────────────────
|
||||
static async convertAsync(edifactContent, config) {
|
||||
let parsed;
|
||||
// Try rigorous parsing via ts-edifact if available
|
||||
if (window.electronAPI && window.electronAPI.parseEdifactLib) {
|
||||
try {
|
||||
const libResult = await window.electronAPI.parseEdifactLib(edifactContent);
|
||||
if (libResult.success && libResult.data) {
|
||||
parsed = { segments: libResult.data };
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('ts-edifact parsing fall-back to basic parser:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to basic parser
|
||||
if (!parsed) {
|
||||
const parser = window.EDIBridge.DelforParser;
|
||||
if (!parser) throw new Error('DelforParser nicht geladen.');
|
||||
parsed = parser.parse(edifactContent);
|
||||
}
|
||||
|
||||
return this.processParsedData(parsed.segments, config);
|
||||
}
|
||||
|
||||
// For backwards compatibility if called synchronously without await
|
||||
static convert(edifactContent, config) {
|
||||
const parser = window.EDIBridge.DelforParser;
|
||||
if (!parser) throw new Error('DelforParser nicht geladen.');
|
||||
const parsed = parser.parse(edifactContent);
|
||||
return this.processParsedData(parsed.segments, config);
|
||||
}
|
||||
|
||||
static processParsedData(segments, config) {
|
||||
const getVal = (seg, elIdx, compIdx) => {
|
||||
if (!seg || !seg.elements) return '';
|
||||
const el = seg.elements[elIdx];
|
||||
if (!el) return '';
|
||||
if (Array.isArray(el)) return el[compIdx] || '';
|
||||
if (compIdx === 0) return el;
|
||||
if (elIdx === 0 && compIdx === 1) return seg.elements[1] || '';
|
||||
return '';
|
||||
};
|
||||
|
||||
const groups = this.splitMessages(segments, getVal);
|
||||
const firstGroup = groups[0] || [];
|
||||
const nadWH = firstGroup.find(s => s.tag === 'NAD' && getVal(s, 0, 0) === 'WH');
|
||||
const nadGM = firstGroup.find(s => s.tag === 'NAD' && getVal(s, 0, 0) === 'GM');
|
||||
const dtm137 = firstGroup.find(s => s.tag === 'DTM' && getVal(s, 0, 0) === '137');
|
||||
|
||||
const targetId = nadWH ? (getVal(nadWH, 1, 0) || getVal(nadWH, 0, 1)) : '';
|
||||
const sourceId = nadGM ? (getVal(nadGM, 1, 0) || getVal(nadGM, 0, 1)) : '';
|
||||
const msgDateRaw = dtm137 ? getVal(dtm137, 0, 1) : '';
|
||||
const msgDate = msgDateRaw.length === 8 ? msgDateRaw.slice(2) : (msgDateRaw || '000000');
|
||||
|
||||
const unz = segments.find(s => s.tag === 'UNZ');
|
||||
const unzRef = unz ? getVal(unz, 1, 0) : '00001';
|
||||
const transNo1 = unzRef;
|
||||
const transNo2 = String((parseInt(unzRef) || 0) + 1);
|
||||
|
||||
const records = [];
|
||||
records.push(this.build711(targetId, sourceId, transNo1, transNo2, msgDate, config));
|
||||
|
||||
let totalRecords712 = 0;
|
||||
let totalRecords713 = 0;
|
||||
let totalRecords714 = 0;
|
||||
|
||||
for (const group of groups) {
|
||||
const bgm = group.find(s => s.tag === 'BGM');
|
||||
const bgmType = bgm ? getVal(bgm, 0, 0) : '';
|
||||
|
||||
// We now process BGM+35 (Inventory Report) and BGM+78 (Inventory Movement)
|
||||
|
||||
// Re-group into LIN blocks
|
||||
let currentLin = null;
|
||||
let currentInv = null;
|
||||
let linBlocks = [];
|
||||
|
||||
for (const seg of group) {
|
||||
if (seg.tag === 'LIN') {
|
||||
currentLin = { seg, invs: [] };
|
||||
linBlocks.push(currentLin);
|
||||
currentInv = null;
|
||||
} else if (seg.tag === 'INV' && currentLin) {
|
||||
currentInv = { seg, details: [] };
|
||||
currentLin.invs.push(currentInv);
|
||||
} else if (currentInv) {
|
||||
currentInv.details.push(seg);
|
||||
} else if (currentLin) {
|
||||
currentLin.invs.push({ seg: null, details: [seg] }); // segments under LIN but before INV
|
||||
}
|
||||
}
|
||||
|
||||
for (const linBlock of linBlocks) {
|
||||
const lin = linBlock.seg;
|
||||
const material = getVal(lin, 2, 0);
|
||||
|
||||
for (const invBlock of linBlock.invs) {
|
||||
const inv = invBlock.seg;
|
||||
const details = invBlock.details;
|
||||
|
||||
const qty156 = details.find(s => s.tag === 'QTY' && getVal(s, 0, 0) === '156');
|
||||
const qty145 = details.find(s => s.tag === 'QTY' && getVal(s, 0, 0) === '145');
|
||||
const qtyOnHand = qty156 ? (parseFloat(getVal(qty156, 0, 1)) || 0) : 0;
|
||||
const qtyCumulative = qty145 ? (parseFloat(getVal(qty145, 0, 1)) || 0) : 0;
|
||||
|
||||
let unitObj = qty156 || qty145;
|
||||
const unit = unitObj ? (getVal(unitObj, 0, 2) || 'PCE') : 'PCE';
|
||||
const vdaUnit = (unit === 'PCE' || unit === 'C62' || unit === 'EA') ? 'ST' :
|
||||
(unit === 'KGM' ? 'KG' : (unit === 'MTR' ? 'M ' : 'ST'));
|
||||
|
||||
const dtm179 = details.find(s => s.tag === 'DTM' && getVal(s, 0, 0) === '179');
|
||||
const invDateRaw = dtm179 ? getVal(dtm179, 0, 1) : msgDateRaw;
|
||||
const invDate = invDateRaw.length === 8 ? invDateRaw.slice(2) : (invDateRaw || '000000');
|
||||
|
||||
// Global or detail refs
|
||||
const rffDQ = details.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'DQ') || group.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'DQ');
|
||||
const rffTN = details.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'TN') || group.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'TN');
|
||||
const rffAAU = details.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'AAU') || group.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'AAU');
|
||||
const rffON = details.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'ON') || group.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'ON');
|
||||
|
||||
const dqNo = rffDQ ? getVal(rffDQ, 0, 1) : '';
|
||||
const tnNo = rffTN ? getVal(rffTN, 0, 1) : '';
|
||||
const aauNo = rffAAU ? getVal(rffAAU, 0, 1) : '';
|
||||
const onNo = rffON ? getVal(rffON, 0, 1) : '';
|
||||
|
||||
// Lieferschein-Nr (current reference)
|
||||
const currentDn = aauNo || tnNo || dqNo;
|
||||
|
||||
const groupLoc11 = group.find(s => s.tag === 'LOC' && getVal(s, 0, 0) === '11');
|
||||
const unloadingPoint = groupLoc11 ? (getVal(groupLoc11, 1, 0) || '').replace(/^W/, '') : '';
|
||||
|
||||
// Get specific storage location for this stock
|
||||
const loc18 = details.find(s => s.tag === 'LOC' && getVal(s, 0, 0) === '18');
|
||||
const storageLocation = loc18 ? getVal(loc18, 1, 0) : '';
|
||||
|
||||
// EDL36 (Movements) logic
|
||||
if (qty156 || currentDn) {
|
||||
// Determine transaction key (Vorgangsschlüssel) based on INV and QTY
|
||||
// Default to 36 (Abgangsmeldung)
|
||||
let vdaKey = '36';
|
||||
|
||||
if (inv) {
|
||||
const direction = getVal(inv, 0, 0); // DE 4501
|
||||
const reason = getVal(inv, 2, 0); // DE 4499
|
||||
|
||||
if (direction === '2' || reason === '1') {
|
||||
vdaKey = '30'; // Eingangsmeldung
|
||||
} else if (reason === '4' || reason === '3') {
|
||||
vdaKey = '32'; // Differenz-/Verlustmeldung
|
||||
} else if (reason === '2' || reason === '11') {
|
||||
vdaKey = '36'; // Abgangsmeldung
|
||||
}
|
||||
}
|
||||
|
||||
records.push(this.build712(msgDate));
|
||||
totalRecords712++;
|
||||
|
||||
records.push(this.build713(currentDn || '00000000', invDate, unloadingPoint, vdaKey, targetId, onNo));
|
||||
totalRecords713++;
|
||||
|
||||
records.push(this.build714(material, qtyOnHand, vdaUnit, dqNo || currentDn));
|
||||
totalRecords714++;
|
||||
}
|
||||
|
||||
// EDL35 (Stock balances) generated for QTY+145
|
||||
if (qty145) {
|
||||
records.push(this.build712(msgDate));
|
||||
totalRecords712++;
|
||||
|
||||
// Stock reports use Vorgangsschlüssel 35
|
||||
records.push(this.build713('00000000', invDate, storageLocation || unloadingPoint, '35', targetId, onNo));
|
||||
totalRecords713++;
|
||||
|
||||
records.push(this.build714(material, qtyCumulative, vdaUnit, dqNo));
|
||||
totalRecords714++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
records.push(this.build719(
|
||||
totalRecords712, totalRecords713, totalRecords714,
|
||||
totalRecords712, totalRecords713, totalRecords714
|
||||
));
|
||||
|
||||
return records.join('\r\n') + '\r\n';
|
||||
}
|
||||
|
||||
// ─── Split EDIFACT segments into UNH message groups ──────────────
|
||||
static splitMessages(segments, getVal) {
|
||||
const groups = [];
|
||||
let current = null;
|
||||
|
||||
for (const seg of segments) {
|
||||
if (seg.tag === 'UNH') {
|
||||
if (current) groups.push(current);
|
||||
current = [seg];
|
||||
} else if (seg.tag === 'UNT') {
|
||||
if (current) {
|
||||
current.push(seg);
|
||||
groups.push(current);
|
||||
current = null;
|
||||
}
|
||||
} else if (current) {
|
||||
current.push(seg);
|
||||
}
|
||||
}
|
||||
if (current) groups.push(current);
|
||||
return groups;
|
||||
}
|
||||
|
||||
// ─── Format Helpers ──────────────────────────────────────────────
|
||||
static padN(val, len) {
|
||||
let v = String(val || '').replace(/[^0-9]/g, '');
|
||||
return v.padStart(len, '0').slice(-len);
|
||||
}
|
||||
|
||||
static padA(val, len) {
|
||||
return String(val || '').padEnd(len, ' ').slice(0, len);
|
||||
}
|
||||
|
||||
static padID(val, len) {
|
||||
let v = String(val || '').replace(/[^0-9]/g, '');
|
||||
// Prefer the front of the ID if it exceeds VDA limits
|
||||
return v.padEnd(len, ' ').slice(0, len).trim().padStart(len, '0');
|
||||
}
|
||||
|
||||
// ─── Record Builders ─────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 711 - Vorsatz Lieferschein- und Transportdaten
|
||||
* Version 03
|
||||
* Pos 01: 1-3 (3) Konstant "711"
|
||||
* Pos 02: 4-5 (2) Version "03"
|
||||
* Pos 03: 6-14 (9) Daten-Empfänger-Nummer
|
||||
* Pos 04: 15-23 (9) Daten-Sender-Nummer
|
||||
* Pos 05: 24-28 (5) Übertragungs-Nummer-Alt
|
||||
* Pos 06: 29-33 (5) Übertragungs-Nummer-Neu
|
||||
* Pos 07: 34-39 (6) Übertragungsdatum (JJMMTT)
|
||||
*/
|
||||
static build711(targetId, sourceId, transNo1, transNo2, date, config) {
|
||||
let r = '71102';
|
||||
|
||||
// Custom override for targetId (e.g. "10305") via Mapping or Setting
|
||||
let target = targetId;
|
||||
if (config && window.EDIBridge.ConfigParser) {
|
||||
const parser = window.EDIBridge.ConfigParser;
|
||||
// 1. Try specific mapping (e.g. 8810 -> 10305)
|
||||
const mapped = parser.findInSection(config, 'VDA711_ID_MAPPING', targetId);
|
||||
if (mapped) {
|
||||
target = mapped;
|
||||
} else {
|
||||
// 2. Fallback to global setting if no specific mapping exists
|
||||
target = parser.lookupGeneral(config, 'OWN_CUSTOMER_NUMBER', targetId);
|
||||
}
|
||||
}
|
||||
|
||||
r += this.padA(target, 9); // Daten-Empfänger
|
||||
r += this.padA(sourceId, 9); // Daten-Sender
|
||||
r += this.padN(transNo1, 5);
|
||||
r += this.padN(transNo2, 5);
|
||||
r += this.padN(date, 6);
|
||||
r += ''.padEnd(128 - r.length, ' ');
|
||||
return r.slice(0, 128);
|
||||
}
|
||||
|
||||
/**
|
||||
* 712 - Einmalige Daten des Transports
|
||||
* Version 03
|
||||
* Pos 01: 1-3 (3) Konstant "712"
|
||||
* Pos 02: 4-5 (2) Version "03"
|
||||
* Pos 03: 6-13 (8) Sendungs-Ladungs-Bezugs-Nummer
|
||||
* Pos 04: 14-16 (3) Werk Lieferant
|
||||
* Pos 05: 17-30 (14) Frachtführer Name/Nummer
|
||||
* Pos 06: 31-36 (6) Übergabedatum (JJMMTT)
|
||||
* Pos 07: 37-40 (4) Übergabezeit (HHMM)
|
||||
* Pos 08: 41-47 (7) Sendungs-Gewicht Brutto (kg)
|
||||
* Pos 09: 48-54 (7) Sendungs-Gewicht Netto (kg)
|
||||
*/
|
||||
static build712(date) {
|
||||
let r = '71202';
|
||||
r += this.padN('0', 8); // Sendungs-Ladungs-Bezug-Nr
|
||||
r += this.padA('', 3); // Werk Lieferant
|
||||
r += this.padA('', 14); // Frachtführer
|
||||
r += this.padN(date, 6); // Datum
|
||||
r += this.padN('0', 4); // Zeit
|
||||
r += this.padN('0', 7); // Brutto
|
||||
r += this.padN('0', 7); // Netto
|
||||
r += ''.padEnd(128 - r.length, ' ');
|
||||
return r.slice(0, 128);
|
||||
}
|
||||
|
||||
/**
|
||||
* 713 - Einmalige Daten des Lieferscheins (Kopf)
|
||||
* Version 03
|
||||
* Pos 01: 1-3 (3) Konstant "713"
|
||||
* Pos 02: 4-5 (2) Version "03"
|
||||
* Pos 03: 6-13 (8) Lieferschein-Nummer
|
||||
* Pos 04: 14-19 (6) Versanddatum (JJMMTT)
|
||||
* Pos 05: 20-24 (5) Abladestelle
|
||||
* Pos 06: 25-26 (2) Versandart
|
||||
* Pos 08: 31-42 (12) Bestellnummer
|
||||
* Pos 09: 43-44 (2) Vorgangs-Schlüssel (EDL)
|
||||
* Pos 11: 49-51 (3) Werk Kunde
|
||||
* Pos 16: 77-85 (9) Lieferanten-Nummer
|
||||
*/
|
||||
static build713(dnNo, date, unloadingPoint, edlType, targetId, orderNo) {
|
||||
let r = '713';
|
||||
// User requested shifting DN left by 2 chars (occupying the version field area to allow 10 digits)
|
||||
r += this.padID(dnNo, 10); // Lieferschein-Nr (4-13)
|
||||
r += this.padN(date, 6); // Datum (14-19)
|
||||
r += this.padA(unloadingPoint, 5); // Abladestelle (20-24)
|
||||
r += '00'; // Versandart (25-26)
|
||||
r += ' '; // Filler (27-30)
|
||||
r += this.padA(orderNo, 12); // Bestellnummer (31-42)
|
||||
const validKeys = ['30', '32', '33', '35', '36', '40'];
|
||||
r += this.padN(validKeys.includes(edlType) ? edlType : '00', 2); // Vorgangsschlüssel (43-44)
|
||||
r += ' '; // Filler (45-48)
|
||||
r += this.padA(targetId.slice(0, 3), 3); // Werk Kunde (49-51)
|
||||
r += ''.padEnd(25, ' '); // Filler to index 76 (52-76)
|
||||
r += this.padA(targetId, 9); // Lieferanten-Nummer (Pos 16, index 76-84)
|
||||
r += ''.padEnd(25, ' '); // Filler to index 110 (85-109)
|
||||
r += this.padA(dnNo, 14); // Dokument-Nr. Kunde (Pos 20, index 110-123)
|
||||
r += ''.padEnd(4, ' '); // Filler to 128
|
||||
return r.slice(0, 128);
|
||||
}
|
||||
|
||||
/**
|
||||
* 714 - Lieferscheinpositionsdaten
|
||||
* Version 03
|
||||
* Pos 01: 1-3 (3) Konstant "714"
|
||||
* Pos 02: 4-5 (2) Version "03"
|
||||
* Pos 03: 6-27 (22) Sachnummer-Kunde
|
||||
* Pos 04: 28-49 (22) Sachnummer-Lieferant
|
||||
* Pos 05: 50-52 (3) Ursprungsland
|
||||
* Pos 06: 53-65 (13) Liefermenge (mit 3 impliziten Dezimalstellen, VDA Standard)
|
||||
* Pos 07: 66-67 (2) Mengeneinheit
|
||||
* Pos 12: 87-89 (3) Positions-Nummer Lieferschein
|
||||
* Pos 14: 91-105 (15) Chargen-Nummer
|
||||
* Pos 22: 121-128 (8) Ursprung-Lieferschein-Nr
|
||||
*/
|
||||
static build714(custMat, qty, unit, refNo) {
|
||||
const qtyEncoded = Math.round(qty * 1000);
|
||||
let r = '71403';
|
||||
r += this.padA(custMat, 22); // Sachnummer-Kunde
|
||||
r += this.padA('', 22); // Sachnummer-Lieferant
|
||||
r += '000'; // Ursprungsland
|
||||
r += this.padN(String(qtyEncoded), 13); // Liefermenge (13 Stellen)
|
||||
r += this.padA(unit, 2); // ME
|
||||
r += ''.padEnd(19, ' '); // Filler to 86
|
||||
r += this.padN('0', 3); // Pos-Nr (87-89)
|
||||
r += ' '; // Filler
|
||||
r += this.padA('', 15); // Charge (91-105)
|
||||
r += ' '; // Filler to 120
|
||||
r += this.padID(refNo, 8); // Ursprung-LS (121-128)
|
||||
return r.slice(0, 128);
|
||||
}
|
||||
|
||||
/**
|
||||
* 719 - Nachsatz Lieferschein- und Transportdaten
|
||||
* Version 02
|
||||
*/
|
||||
static build719(cnt712, cnt713, cnt714, tot712, tot713, tot714) {
|
||||
let r = '71902';
|
||||
r += this.padN('0', 8);
|
||||
r += this.padN(String(1), 7); // Zähler 711
|
||||
r += this.padN(String(cnt712), 7);
|
||||
r += this.padN(String(cnt713), 7);
|
||||
r += this.padN(String(cnt714), 7);
|
||||
r += this.padN('0', 7); // Zähler 715
|
||||
r += this.padN('0', 7); // Zähler 716
|
||||
r += this.padN('0', 7); // Zähler 718
|
||||
r += this.padN(String(1), 7); // Zähler 719
|
||||
r += this.padN('0', 7); // Zähler 717
|
||||
r += ''.padEnd(128 - r.length, ' ');
|
||||
return r.slice(0, 128);
|
||||
}
|
||||
}
|
||||
|
||||
window.EDIBridge.InvrptToVDA4913 = InvrptToVDA4913;
|
||||
348
js/outbound-watcher-bridge.js
Normal file
348
js/outbound-watcher-bridge.js
Normal file
@@ -0,0 +1,348 @@
|
||||
/**
|
||||
* Watcher Bridge – Renderer-side logic for folder watcher
|
||||
* Communicates with Electron main process via window.electronAPI
|
||||
*
|
||||
* Flow:
|
||||
* 1. Main process detects new file via chokidar
|
||||
* 2. Main sends file content to renderer via 'watcher-file-detected'
|
||||
* 3. This bridge converts using the EDIBridge converters
|
||||
* 4. Calls back to main to write output file
|
||||
* 5. Calls back to main to move source file to archiv/
|
||||
*/
|
||||
window.EDIBridge = window.EDIBridge || {};
|
||||
|
||||
class OutboundWatcherBridge {
|
||||
constructor() {
|
||||
this.status = 'stopped'; // 'stopped', 'running', 'paused'
|
||||
this.log = [];
|
||||
this.maxLogEntries = 200;
|
||||
this.onStatusChange = null;
|
||||
this.onLogEntry = null;
|
||||
this.config = null;
|
||||
|
||||
this._setupListeners();
|
||||
}
|
||||
|
||||
_setupListeners() {
|
||||
if (!window.electronAPI) return;
|
||||
|
||||
window.electronAPI.onOutboundFileDetected((data) => {
|
||||
this._processDetectedFile(data);
|
||||
});
|
||||
|
||||
window.electronAPI.onOutboundWatcherError((data) => {
|
||||
this._addLog('error', data.filePath || '', data.error || 'Unbekannter Fehler');
|
||||
});
|
||||
|
||||
window.electronAPI.onOutboundWatcherReady((data) => {
|
||||
this._addLog('info', '', `Watcher bereit. Überwache: ${data.inputDir}`);
|
||||
});
|
||||
}
|
||||
|
||||
async _processDetectedFile(data) {
|
||||
const { filePath, fileName, content, outputDir } = data;
|
||||
this._addLog('info', fileName, 'Datei erkannt, starte Konvertierung...');
|
||||
|
||||
let konvertierungsmodus = 'Unbekannt';
|
||||
let quellformat = null;
|
||||
let zielformat = null;
|
||||
let convertedContent = null;
|
||||
let pError = null;
|
||||
let outFileName = null;
|
||||
|
||||
try {
|
||||
// Try to use a global config if this.config is not set (e.g. from app.js)
|
||||
if (!this.config && window.app) {
|
||||
this.config = window.app.config;
|
||||
}
|
||||
|
||||
const result = this._convertContent(content, fileName);
|
||||
|
||||
if (result.error) {
|
||||
this._addLog('error', fileName, result.error);
|
||||
pError = result.error;
|
||||
} else {
|
||||
konvertierungsmodus = result.type;
|
||||
quellformat = result.format === 'vda' ? 'EDIFACT DELFOR' : 'VDA 4913';
|
||||
zielformat = result.targetFormat || (result.format === 'vda' ? 'VDA 4905' : (result.format === 'xml' ? 'IFM DELVRY03' : 'Bosch DESADV'));
|
||||
convertedContent = result.output;
|
||||
|
||||
// Build output filename
|
||||
const baseName = fileName.replace(/\.[^.]+$/, '');
|
||||
const outExt = result.format === 'vda' ? '.vda' : (result.format === 'xml' ? '.xml' : '.edi');
|
||||
outFileName = baseName + '_converted' + outExt;
|
||||
const outPath = outputDir + '\\' + outFileName;
|
||||
|
||||
// Write converted file
|
||||
const writeResult = await window.electronAPI.writeFile(outPath, result.output);
|
||||
|
||||
if (writeResult.error) {
|
||||
this._addLog('error', fileName, `Schreibfehler: ${writeResult.error}`);
|
||||
pError = writeResult.error;
|
||||
} else {
|
||||
this._addLog('success', fileName, `Konvertiert → ${outFileName} (${result.type})`);
|
||||
|
||||
// Move source file to archiv
|
||||
const archiveResult = await window.electronAPI.moveToArchive(filePath);
|
||||
|
||||
if (archiveResult.error) {
|
||||
this._addLog('error', fileName, `Archiv-Fehler: ${archiveResult.error}`);
|
||||
} else {
|
||||
this._addLog('info', fileName, 'Quelldatei nach archiv/ verschoben.');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
this._addLog('error', fileName, `Fehler: ${e.message}`);
|
||||
pError = e.message;
|
||||
}
|
||||
|
||||
// Log to database
|
||||
if (window.app && typeof window.app._logConversion === 'function') {
|
||||
await window.app._logConversion({
|
||||
dateiname: fileName,
|
||||
ausgangsdateiname: outFileName,
|
||||
quellformat: quellformat,
|
||||
zielformat: zielformat,
|
||||
konvertierungsmodus: konvertierungsmodus,
|
||||
eingang_daten: content,
|
||||
ausgang_daten: convertedContent,
|
||||
status: pError ? 'FEHLERHAFT' : 'ERFOLGREICH',
|
||||
fehlermeldung: pError,
|
||||
quelle: 'WATCHER' // Auto tag
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_convertContent(content, fileName) {
|
||||
const trimmed = content.trim();
|
||||
const upper = trimmed.toUpperCase();
|
||||
|
||||
// Detect file type and choose converter
|
||||
if (upper.startsWith('UNA') || upper.startsWith('UNB')) {
|
||||
// EDIFACT inbound → VDA 4905
|
||||
return this._convertInbound(content, upper);
|
||||
} else if (/^[0-9]{3}/.test(trimmed) || /^[57][0-9]{2}/.test(trimmed)) {
|
||||
// VDA record (starts with 3-digit record type, e.g. 511, 711) → EDIFACT outbound
|
||||
return this._convertOutbound(content);
|
||||
} else {
|
||||
return { error: `Dateiformat nicht erkannt (weder EDIFACT noch VDA). Erste Zeichen: "${trimmed.substring(0, 20)}..."` };
|
||||
}
|
||||
}
|
||||
|
||||
_convertInbound(content, upper) {
|
||||
try {
|
||||
const ConfigParser = window.EDIBridge.ConfigParser;
|
||||
const DelforParser = window.EDIBridge.DelforParser;
|
||||
let preferredMode = null;
|
||||
|
||||
if (ConfigParser && DelforParser) {
|
||||
const customerId = DelforParser.getCustomerId(content);
|
||||
console.log("Watcher detected Inbound Customer ID:", customerId);
|
||||
if (customerId) {
|
||||
preferredMode = ConfigParser.lookupCustomerMode(this.config, customerId);
|
||||
console.log("Watcher mapped Inbound mode for ID", customerId, ":", preferredMode);
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Prioritize mapped mode
|
||||
if (preferredMode === 'inbound-ifm') {
|
||||
const converter = window.EDIBridge.DelforToVDA4905Converter;
|
||||
if (converter) {
|
||||
const result = converter.convert(content);
|
||||
return { output: result, type: 'IFM DELFOR → VDA 4905', format: 'vda' };
|
||||
}
|
||||
} else if (preferredMode === 'inbound-bosch') {
|
||||
const parser = window.EDIBridge.DelforParser;
|
||||
const generator = window.EDIBridge.VDA4905Generator;
|
||||
if (parser && generator) {
|
||||
const parsed = parser.parse(content);
|
||||
const vda4905 = generator.generate(parsed);
|
||||
return { output: vda4905, type: 'DELFOR → VDA 4905', format: 'vda' };
|
||||
}
|
||||
} else if (preferredMode === 'inbound-invrpt') {
|
||||
const converter = window.EDIBridge.InvrptToVDA4913;
|
||||
if (converter) {
|
||||
const vda4913 = converter.convert(content);
|
||||
return { output: vda4913, type: 'INVRPT → VDA 4913', format: 'vda' };
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Fallback to hardcoded logic if no mapping
|
||||
if (upper.includes('INVRPT')) {
|
||||
const converter = window.EDIBridge.InvrptToVDA4913;
|
||||
if (converter) {
|
||||
const vda4913 = converter.convert(content);
|
||||
return { output: vda4913, type: 'INVRPT → VDA 4913', format: 'vda' };
|
||||
}
|
||||
}
|
||||
|
||||
if (upper.includes('DELFOR') && upper.includes('D:04A')) {
|
||||
const converter = window.EDIBridge.DelforToVDA4905Converter;
|
||||
if (converter) {
|
||||
const result = converter.convert(content);
|
||||
return { output: result, type: 'IFM DELFOR D:04A → VDA 4905', format: 'vda' };
|
||||
}
|
||||
}
|
||||
|
||||
const parser = window.EDIBridge.DelforParser;
|
||||
const generator = window.EDIBridge.VDA4905Generator;
|
||||
if (parser && generator) {
|
||||
const parsed = parser.parse(content);
|
||||
const vda4905 = generator.generate(parsed);
|
||||
return { output: vda4905, type: 'DELFOR → VDA 4905', format: 'vda' };
|
||||
}
|
||||
|
||||
return { error: 'Passendes Inbound-Modul nicht geladen oder Typ unbekannt.' };
|
||||
} catch (e) {
|
||||
return { error: `Inbound-Konvertierung fehlgeschlagen: ${e.message}` };
|
||||
}
|
||||
}
|
||||
|
||||
_convertOutbound(content) {
|
||||
try {
|
||||
const VDAParser = window.EDIBridge.VDAParser;
|
||||
if (!VDAParser) return { error: 'VDA Parser nicht geladen.' };
|
||||
|
||||
const vda = VDAParser.parse(content);
|
||||
if (!vda.interchanges || vda.interchanges.length === 0) {
|
||||
return { error: 'Keine gültigen VDA 4913 Daten gefunden.' };
|
||||
}
|
||||
|
||||
// Check for customer mapping in config
|
||||
const ConfigParser = window.EDIBridge.ConfigParser;
|
||||
const customerId = VDAParser.getCustomerId(content);
|
||||
console.log("Watcher detected Customer ID:", customerId);
|
||||
let preferredMode = null;
|
||||
|
||||
if (ConfigParser && customerId) {
|
||||
preferredMode = ConfigParser.lookupCustomerMode(this.config, customerId);
|
||||
console.log("Watcher mapped mode for ID", customerId, ":", preferredMode);
|
||||
}
|
||||
|
||||
if (preferredMode === 'outbound-ifm') {
|
||||
const delvry = window.EDIBridge.VDA4913ToDELVRY03;
|
||||
if (delvry) {
|
||||
const idocs = delvry.convert(vda, this.config || {});
|
||||
const output = delvry.toXML(idocs);
|
||||
return { output, type: 'VDA 4913 → IFM DELVRY03', format: 'xml' };
|
||||
}
|
||||
} else if (preferredMode === 'outbound-zf') {
|
||||
const desadvZf = window.EDIBridge.VDA4913ToDESADVZF;
|
||||
if (desadvZf) {
|
||||
const output = desadvZf.convert(content, {}, { linWeights: {}, pacWeights: {}, pacDims: {} }, this.config);
|
||||
return { output, type: 'VDA 4913 → ZF DESADV', format: 'zf-edi', targetFormat: 'ZF DESADV' };
|
||||
}
|
||||
} else if (preferredMode === 'outbound-bosch') {
|
||||
const desadv = window.EDIBridge.VDA4913ToDESADV;
|
||||
if (desadv) {
|
||||
const output = desadv.convert(content, {}, { linWeights: {}, pacWeights: {}, pacDims: {} }, this.config);
|
||||
return { output, type: 'VDA 4913 → Bosch DESADV', format: 'edi' };
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to original logic if no mapping or mapped converter missing
|
||||
// Try IFM DELVRY03 first
|
||||
const delvry = window.EDIBridge.VDA4913ToDELVRY03;
|
||||
if (delvry) {
|
||||
const idocs = delvry.convert(vda, this.config || {});
|
||||
const output = delvry.toXML(idocs);
|
||||
return { output, type: 'VDA 4913 → IFM DELVRY03', format: 'xml' };
|
||||
}
|
||||
|
||||
// Fallback to Bosch DESADV
|
||||
const desadv = window.EDIBridge.VDA4913ToDESADV;
|
||||
if (desadv) {
|
||||
const output = desadv.convert(content, {}, { linWeights: {}, pacWeights: {}, pacDims: {} }, this.config);
|
||||
return { output, type: 'VDA 4913 → Bosch DESADV', format: 'edi' };
|
||||
}
|
||||
|
||||
return { error: 'Kein Outbound-Converter geladen.' };
|
||||
} catch (e) {
|
||||
return { error: `Outbound-Konvertierung fehlgeschlagen: ${e.message}` };
|
||||
}
|
||||
}
|
||||
|
||||
_addLog(level, fileName, message) {
|
||||
const entry = {
|
||||
time: new Date().toLocaleTimeString('de-DE'),
|
||||
level,
|
||||
fileName,
|
||||
message
|
||||
};
|
||||
this.log.unshift(entry);
|
||||
if (this.log.length > this.maxLogEntries) this.log.pop();
|
||||
if (this.onLogEntry) this.onLogEntry(entry);
|
||||
}
|
||||
|
||||
async start(inputDir, outputDir) {
|
||||
if (!window.electronAPI) {
|
||||
return { error: 'Electron API nicht verfügbar. App läuft im Browser-Modus.' };
|
||||
}
|
||||
const result = await window.electronAPI.startOutboundWatcher({ inputDir, outputDir });
|
||||
if (result.success) {
|
||||
this.status = 'running';
|
||||
this._addLog('info', '', `Watcher gestartet. Überwache: ${inputDir}`);
|
||||
if (this.onStatusChange) this.onStatusChange(this.status);
|
||||
} else if (result.error) {
|
||||
this._addLog('error', '', result.error);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async stop() {
|
||||
if (!window.electronAPI) return;
|
||||
const result = await window.electronAPI.stopOutboundWatcher();
|
||||
if (result.success) {
|
||||
this.status = 'stopped';
|
||||
this._addLog('info', '', 'Watcher gestoppt.');
|
||||
if (this.onStatusChange) this.onStatusChange(this.status);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async pause() {
|
||||
if (!window.electronAPI) return;
|
||||
const result = await window.electronAPI.pauseOutboundWatcher();
|
||||
if (result.success) {
|
||||
this.status = 'paused';
|
||||
this._addLog('info', '', 'Watcher pausiert.');
|
||||
if (this.onStatusChange) this.onStatusChange(this.status);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async resume() {
|
||||
if (!window.electronAPI) return;
|
||||
const result = await window.electronAPI.resumeOutboundWatcher();
|
||||
if (result.success) {
|
||||
this.status = 'running';
|
||||
this._addLog('info', '', 'Watcher fortgesetzt.');
|
||||
if (this.onStatusChange) this.onStatusChange(this.status);
|
||||
} else if (result.error) {
|
||||
this._addLog('error', '', result.error);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
if (!window.electronAPI) return { inputDir: '', outputDir: '', mode: 'auto' };
|
||||
return await window.electronAPI.loadSettings();
|
||||
}
|
||||
|
||||
async saveSettings(settings) {
|
||||
if (!window.electronAPI) return;
|
||||
return await window.electronAPI.saveSettings(settings);
|
||||
}
|
||||
|
||||
async selectFolder() {
|
||||
if (!window.electronAPI) return null;
|
||||
return await window.electronAPI.selectFolder();
|
||||
}
|
||||
|
||||
isElectron() {
|
||||
return !!window.electronAPI;
|
||||
}
|
||||
}
|
||||
|
||||
window.EDIBridge.OutboundWatcherBridge = OutboundWatcherBridge;
|
||||
246
js/vda-parser.js
Normal file
246
js/vda-parser.js
Normal file
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* VDA 4913 Parser - Field positions verified against real data
|
||||
*/
|
||||
window.EDIBridge = window.EDIBridge || {};
|
||||
|
||||
class VDAParser {
|
||||
static parse(content) {
|
||||
const lines = content.split(/\r?\n/).filter(l => l.length >= 3);
|
||||
const result = { interchanges: [] };
|
||||
let curI = null, curT = null, curDN = null;
|
||||
|
||||
for (const line of lines) {
|
||||
const rt = line.substring(0, 3);
|
||||
switch (rt) {
|
||||
// VDA 4905 (DELFOR)
|
||||
case '511':
|
||||
curI = { header: this.parse511(line), articles: [], transports: [] };
|
||||
result.interchanges.push(curI);
|
||||
break;
|
||||
case '512':
|
||||
if (!curI) break;
|
||||
// In 4905, articles are children of the interchange
|
||||
curT = { article: this.parse512(line), schedules: [], deliveries: [] };
|
||||
curI.articles.push(curT);
|
||||
break;
|
||||
case '513':
|
||||
if (!curT || !curT.article) break; // Ensure we are in an article context
|
||||
const s513 = this.parse513(line);
|
||||
curT.schedules.push(...s513.schedules);
|
||||
if (s513.lastDn && s513.lastDn !== '00000000' && s513.lastDn.trim() !== '') {
|
||||
curT.deliveries.push({
|
||||
docNo: s513.lastDn,
|
||||
date: s513.lastDate,
|
||||
qty: s513.lastQty,
|
||||
unit: curT.article.unit || 'ST'
|
||||
});
|
||||
}
|
||||
curT.efz = s513.efz;
|
||||
curT.nullStellung = s513.nullStellung || '';
|
||||
break;
|
||||
case '514':
|
||||
if (!curT || !curT.article) break;
|
||||
curT.schedules.push(...this.parse514(line));
|
||||
break;
|
||||
case '519':
|
||||
if (curI) curI.trailer = this.parse519(line);
|
||||
break;
|
||||
|
||||
// VDA 4913 (DESADV)
|
||||
case '711':
|
||||
curI = { header: this.parse711(line), transports: [], articles: [] };
|
||||
result.interchanges.push(curI);
|
||||
break;
|
||||
case '712':
|
||||
if (!curI) break;
|
||||
curT = { data: this.parse712(line), deliveryNotes: [] };
|
||||
curI.transports.push(curT);
|
||||
break;
|
||||
case '713':
|
||||
if (!curT) break;
|
||||
curDN = { header: this.parse713(line), positions: [], texts: [] };
|
||||
curT.deliveryNotes.push(curDN);
|
||||
break;
|
||||
case '714':
|
||||
if (!curDN) break;
|
||||
curDN.positions.push({ data: this.parse714(line), packaging: [] });
|
||||
break;
|
||||
case '715':
|
||||
if (!curDN) break;
|
||||
const lastP = curDN.positions[curDN.positions.length - 1];
|
||||
if (lastP) lastP.packaging.push(this.parse715(line));
|
||||
break;
|
||||
case '716':
|
||||
if (curDN) {
|
||||
curDN.texts.push(line.substring(5).trim());
|
||||
const revLvl = line.substring(5, 7).trim();
|
||||
if (revLvl && curDN.positions.length > 0) {
|
||||
const lastPos = curDN.positions[curDN.positions.length - 1];
|
||||
if (!lastPos.data.revisionLevel) lastPos.data.revisionLevel = revLvl;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '719':
|
||||
if (curI) curI.trailer = this.parse719(line);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static getCustomerId(content) {
|
||||
if (!content) return null;
|
||||
const lines = content.split(/\r?\n/).filter(l => l.length >= 3);
|
||||
for (const line of lines) {
|
||||
const rt = line.substring(0, 3);
|
||||
if (rt === '711' || rt === '511') {
|
||||
return line.substring(5, 14).trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- VDA 4905 Parsers ---
|
||||
static parse511(l) {
|
||||
return {
|
||||
version: l.substring(3, 5).trim(),
|
||||
customerId: l.substring(5, 14).trim(),
|
||||
supplierId: l.substring(14, 23).trim(),
|
||||
oldTransNo: l.substring(23, 28).trim(),
|
||||
newTransNo: l.substring(28, 33).trim(),
|
||||
date: l.substring(33, 39).trim()
|
||||
};
|
||||
}
|
||||
|
||||
static parse512(l) {
|
||||
return {
|
||||
plant: l.substring(5, 8).trim(),
|
||||
docNum: l.substring(8, 17).trim(),
|
||||
docDate: l.substring(17, 23).trim(),
|
||||
custMat: l.substring(38, 60).trim(),
|
||||
suppMat: l.substring(60, 82).trim(),
|
||||
orderNo: l.substring(82, 94).trim(),
|
||||
unloadingPoint: l.substring(94, 99).trim(),
|
||||
unit: l.substring(103, 105).trim() || 'ST'
|
||||
};
|
||||
}
|
||||
|
||||
static parse513(l) {
|
||||
const weDate = l.substring(5, 11).trim();
|
||||
const lastDn = l.substring(11, 19).trim();
|
||||
const lastDate = l.substring(19, 25).trim();
|
||||
// VDA 4905: Letzte Liefermenge hat 3 implizite Dezimalstellen → /1000
|
||||
const lastQty = (parseInt(l.substring(25, 37), 10) || 0) / 1000;
|
||||
// VDA 4905: EFZ hat ebenfalls 3 implizite Dezimalstellen → /1000
|
||||
const efz = (parseInt(l.substring(37, 47), 10) || 0) / 1000;
|
||||
|
||||
const schedules = [];
|
||||
let offset = 47;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const dateRaw = l.substring(offset, offset + 6);
|
||||
const qtyStr = l.substring(offset + 6, offset + 15).trim();
|
||||
if (dateRaw.trim() && dateRaw.trim() !== '000000') {
|
||||
// VDA 4905: Abrufmengen haben 3 implizite Dezimalstellen → /1000
|
||||
schedules.push({ date: dateRaw, qty: (parseInt(qtyStr, 10) || 0) / 1000 });
|
||||
}
|
||||
offset += 15;
|
||||
}
|
||||
// Null-Stellung (6 Zeichen JJMMTT) steht nach den 5 Abrufzeilen
|
||||
const nullStellungRaw = l.substring(offset, offset + 6).trim();
|
||||
const nullStellung = (nullStellungRaw && nullStellungRaw !== '000000') ? nullStellungRaw : '';
|
||||
return { weDate, lastDn, lastDate, lastQty, efz, nullStellung, schedules };
|
||||
}
|
||||
|
||||
static parse514(l) {
|
||||
const schedules = [];
|
||||
let offset = 5;
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const dateRaw = l.substring(offset, offset + 6);
|
||||
const qtyStr = l.substring(offset + 6, offset + 15).trim();
|
||||
if (dateRaw.trim() && dateRaw.trim() !== '000000') {
|
||||
// VDA 4905: Abrufmengen haben 3 implizite Dezimalstellen → /1000
|
||||
schedules.push({ date: dateRaw, qty: (parseInt(qtyStr, 10) || 0) / 1000 });
|
||||
}
|
||||
offset += 15;
|
||||
}
|
||||
return schedules;
|
||||
}
|
||||
|
||||
static parse519(l) {
|
||||
return {
|
||||
count511: parseInt(l.substring(5, 12), 10) || 0,
|
||||
count512: parseInt(l.substring(12, 19), 10) || 0,
|
||||
count513: parseInt(l.substring(19, 26), 10) || 0,
|
||||
count514: parseInt(l.substring(26, 33), 10) || 0
|
||||
};
|
||||
}
|
||||
|
||||
// --- VDA 4913 Parsers ---
|
||||
|
||||
static parse711(l) {
|
||||
return {
|
||||
version: l.substring(3, 5).trim(),
|
||||
targetId: l.substring(5, 14).trim(),
|
||||
sourceId: l.substring(14, 23).trim(),
|
||||
transNo1: l.substring(23, 28).trim(),
|
||||
transNo2: l.substring(28, 33).trim(),
|
||||
date: l.substring(33, 39).trim(),
|
||||
altId: l.substring(39, 48).trim()
|
||||
};
|
||||
}
|
||||
|
||||
static parse712(l) {
|
||||
return {
|
||||
refNo: l.substring(5, 13).trim(),
|
||||
carrierName: l.substring(16, 30).trim(),
|
||||
date: l.substring(30, 36).trim(),
|
||||
time: l.substring(36, 40).trim(),
|
||||
grossWeight: parseInt(l.substring(40, 47)) || 0,
|
||||
netWeight: parseInt(l.substring(47, 54)) || 0,
|
||||
};
|
||||
}
|
||||
|
||||
static parse713(l) {
|
||||
return {
|
||||
dnNo: l.substring(5, 13).trim(),
|
||||
date: l.substring(13, 19).trim(),
|
||||
unloadingPoint: l.substring(19, 24).trim(),
|
||||
qualifier: l.substring(24, 30).trim(),
|
||||
orderNo: l.substring(30, 38).trim(),
|
||||
};
|
||||
}
|
||||
|
||||
static parse714(l) {
|
||||
// Qty: [53-65] with 3 implied decimals (VDA Standard) → /1000
|
||||
const qtyRaw = parseInt(l.substring(52, 65)) || 0;
|
||||
const qty = qtyRaw / 1000;
|
||||
return {
|
||||
custMat: l.substring(5, 27).trim(),
|
||||
suppMat: l.substring(27, 49).trim(),
|
||||
qty: qty,
|
||||
unit: l.substring(65, 67).trim(),
|
||||
refNo: l.substring(77, 90).trim(),
|
||||
// VDA 4913 standard uses different fields for batch/refs, keeping compatibility
|
||||
batchNo: l.substring(90, 105).trim() || l.substring(113, 127).trim(),
|
||||
};
|
||||
}
|
||||
|
||||
static parse715(l) {
|
||||
const count = parseInt(l.substring(59, 62)) || 1;
|
||||
const labelFrom = l.substring(78, 87).trim();
|
||||
const labelTo = l.substring(87, 96).trim();
|
||||
return {
|
||||
packMatCust: l.substring(5, 27).trim(),
|
||||
packMatSupp: l.substring(27, 49).trim(),
|
||||
qty: count,
|
||||
labelFrom: labelFrom,
|
||||
labelTo: labelTo,
|
||||
};
|
||||
}
|
||||
|
||||
static parse719(l) {
|
||||
return { lineCount: parseInt(l.substring(5, 12)) || 0 };
|
||||
}
|
||||
}
|
||||
|
||||
window.EDIBridge.VDAParser = VDAParser;
|
||||
339
js/vda4905-generator.js
Normal file
339
js/vda4905-generator.js
Normal file
@@ -0,0 +1,339 @@
|
||||
/**
|
||||
* VDA 4905 Generator
|
||||
* Strictly follows BSH VDA 4905 Spec (Feb 2017)
|
||||
*/
|
||||
window.EDIBridge = window.EDIBridge || {};
|
||||
|
||||
class VDA4905Generator {
|
||||
static generate(parsed) {
|
||||
const segments = parsed.segments;
|
||||
const records = [];
|
||||
|
||||
let count511 = 0, count512 = 0, count513 = 0, count514 = 0;
|
||||
|
||||
// --- Helpers ---
|
||||
const getVal = (seg, elIdx, compIdx) => {
|
||||
if (!seg || !seg.elements) return '';
|
||||
const el = seg.elements[elIdx];
|
||||
if (!el) return '';
|
||||
if (Array.isArray(el)) return el[compIdx] || '';
|
||||
if (compIdx === 0) return el;
|
||||
// Fallback for flat elements: if asking for comp 1 of el 0, but el 0 is flat, maybe it's in el 1
|
||||
if (elIdx === 0 && compIdx === 1) return seg.elements[1] || '';
|
||||
return '';
|
||||
};
|
||||
|
||||
const findSeg = (tag, qual) => segments.find(s => s.tag === tag && getVal(s, 0, 0) === qual);
|
||||
|
||||
// --- 1. Header (Record 511) ---
|
||||
const nadBY = findSeg('NAD', 'BY');
|
||||
const nadSE = findSeg('NAD', 'SE');
|
||||
|
||||
const custId = getVal(nadBY, 1, 0) || getVal(nadBY, 2, 0); // Try el 1 or 2
|
||||
const suppId = getVal(nadSE, 1, 0) || getVal(nadSE, 2, 0);
|
||||
|
||||
const bgm = segments.find(s => s.tag === 'BGM');
|
||||
const docNum = bgm ? (getVal(bgm, 1, 0) || getVal(bgm, 0, 1)) : '';
|
||||
|
||||
const dtm137 = findSeg('DTM', '137');
|
||||
const docVerify = dtm137 ? getVal(dtm137, 0, 1) : '';
|
||||
const docDateYYMMDD = docVerify.length === 8 ? docVerify.slice(2, 8) : (docVerify || '000000');
|
||||
|
||||
// Old Release Number: Prioritize AIF (Previous)
|
||||
let rffOld = segments.find(s => s.tag === 'RFF' && ['AIF', 'AAN', 'AAL', 'PL'].includes(getVal(s, 0, 0)));
|
||||
let oldDocNum = rffOld ? getVal(rffOld, 0, 1) : '00000';
|
||||
|
||||
// Old Release Date (DTM+192)
|
||||
const dtm192 = findSeg('DTM', '192');
|
||||
const oldDocVerify = dtm192 ? getVal(dtm192, 0, 1) : '';
|
||||
const oldDocDateYYMMDD = oldDocVerify.length === 8 ? oldDocVerify.slice(2, 8) : (oldDocVerify || '000000');
|
||||
|
||||
records.push(this.build511(custId, suppId, docNum, docDateYYMMDD, oldDocNum));
|
||||
count511++;
|
||||
|
||||
// --- 2. Loop through Articles (LIN) ---
|
||||
let currentLinData = null;
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
const s = segments[i];
|
||||
if (s.tag === 'LIN') {
|
||||
if (currentLinData) {
|
||||
const added514 = this.processLin(currentLinData, records, custId, docNum, docDateYYMMDD, oldDocNum, oldDocDateYYMMDD);
|
||||
count512++;
|
||||
count513++;
|
||||
count514 += added514;
|
||||
} else {
|
||||
for (let k = i + 1; k < segments.length && segments[k].tag !== 'LIN'; k++) {
|
||||
if (segments[k].tag === 'RFF' && getVal(segments[k], 0, 0) === 'AIF') {
|
||||
oldDocNum = getVal(segments[k], 0, 1);
|
||||
records[0] = this.build511(custId, suppId, docNum, docDateYYMMDD, oldDocNum);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
currentLinData = { lin: s, qtyDtms: [], rffs: [], pia: null, imd: null, locs: [], dtms: [] };
|
||||
} else if (currentLinData) {
|
||||
if (s.tag === 'QTY') {
|
||||
let date = '';
|
||||
let rffs = [];
|
||||
for (let j = i + 1; j < segments.length; j++) {
|
||||
const next = segments[j];
|
||||
if (next.tag === 'QTY' || next.tag === 'LIN' || next.tag === 'UNS' || next.tag === 'CNT') break;
|
||||
if (next.tag === 'DTM') {
|
||||
const q = getVal(next, 0, 0);
|
||||
if (['10', '2', '11', '64', '63', '69', '171', '50', '137', '131', '242'].includes(q)) {
|
||||
date = getVal(next, 0, 1);
|
||||
}
|
||||
}
|
||||
if (next.tag === 'RFF' || next.tag === 'DOC') {
|
||||
rffs.push(next);
|
||||
}
|
||||
}
|
||||
currentLinData.qtyDtms.push({ qtySeg: s, date, rffs });
|
||||
}
|
||||
else if (s.tag === 'RFF') currentLinData.rffs.push(s);
|
||||
else if (s.tag === 'PIA') currentLinData.pia = s;
|
||||
else if (s.tag === 'IMD') currentLinData.imd = s;
|
||||
else if (s.tag === 'LOC') currentLinData.locs.push(s);
|
||||
else if (s.tag === 'DTM') currentLinData.dtms.push(s);
|
||||
}
|
||||
}
|
||||
if (currentLinData) {
|
||||
const added514 = this.processLin(currentLinData, records, custId, docNum, docDateYYMMDD, oldDocNum, oldDocDateYYMMDD);
|
||||
count512++;
|
||||
count513++;
|
||||
count514 += added514;
|
||||
}
|
||||
|
||||
// --- 3. Trailer (Record 519) ---
|
||||
records.push(this.build519(count511, count512, count513, count514));
|
||||
|
||||
return records.map(r => r + '\r\n').join('');
|
||||
}
|
||||
|
||||
static processLin(data, records, custId, docNum, docDate, headerOldDoc, headerOldDate) {
|
||||
const getVal = (seg, elIdx, compIdx) => {
|
||||
if (!seg || !seg.elements) return '';
|
||||
const el = seg.elements[elIdx];
|
||||
if (!el) return '';
|
||||
if (Array.isArray(el)) return el[compIdx] || '';
|
||||
if (compIdx === 0) return el;
|
||||
if (elIdx === 0 && compIdx === 1) return seg.elements[1] || '';
|
||||
return '';
|
||||
};
|
||||
|
||||
const loc11 = data.locs.find(l => getVal(l, 0, 0) === '11');
|
||||
const plant = loc11 ? getVal(loc11, 1, 0) : ' ';
|
||||
|
||||
const custMat = getVal(data.lin, 2, 0);
|
||||
|
||||
const rffON = data.rffs.find(r => getVal(r, 0, 0) === 'ON');
|
||||
const orderNo = rffON ? getVal(rffON, 0, 1) : '';
|
||||
|
||||
const rffAif = data.rffs.find(r => getVal(r, 0, 0) === 'AIF');
|
||||
const oldDocNum = rffAif ? getVal(rffAif, 0, 1) : headerOldDoc;
|
||||
|
||||
const dtm192 = data.dtms.find(d => getVal(d, 0, 0) === '192');
|
||||
let oldDocDate = dtm192 ? getVal(dtm192, 0, 1) : headerOldDate;
|
||||
if (oldDocDate.length === 8) oldDocDate = oldDocDate.slice(2);
|
||||
|
||||
const loc159 = data.locs.find(l => getVal(l, 0, 0) === '159');
|
||||
let unloading = loc159 ? getVal(loc159, 1, 0) : '';
|
||||
if (!unloading && loc11) unloading = getVal(loc11, 1, 0);
|
||||
|
||||
let unit = 'ST';
|
||||
if (data.qtyDtms.length > 0) {
|
||||
const u = getVal(data.qtyDtms[0].qtySeg, 0, 2);
|
||||
if (u === 'PCE' || u === 'C62') unit = 'ST';
|
||||
else if (u === 'KGM') unit = 'KG';
|
||||
else if (u === 'MTR') unit = 'M ';
|
||||
}
|
||||
|
||||
records.push(this.build512(plant, docNum, docDate, custMat, orderNo, unloading, unit, oldDocNum, oldDocDate));
|
||||
|
||||
const schedules = [];
|
||||
for (const item of data.qtyDtms) {
|
||||
const qual = getVal(item.qtySeg, 0, 0);
|
||||
const qty = getVal(item.qtySeg, 0, 1);
|
||||
|
||||
if (['1', '12', '11', '21', '113'].includes(qual)) {
|
||||
let d = item.date;
|
||||
if (!d) d = docDate;
|
||||
if (d.length === 8) d = d.slice(2);
|
||||
// VDA 4905: Quantities are usually whole numbers or scaled as needed.
|
||||
// Removed implicit x1000 scaling as per user feedback (15000 should be 15000).
|
||||
const mappedQty = String(Math.round(parseFloat(qty) || 0));
|
||||
schedules.push({ date: d || '000000', qty: mappedQty, qual });
|
||||
}
|
||||
}
|
||||
|
||||
schedules.sort((a, b) => {
|
||||
if (a.qual === '12' && b.qual !== '12') return -1;
|
||||
if (a.qual !== '12' && b.qual === '12') return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
const chunk513 = schedules.slice(0, 5);
|
||||
const chunkRest = schedules.slice(5);
|
||||
|
||||
// EFZ (Eingangs-Fortschrittszahl) = QTY+70
|
||||
// QTY+194 sind einzelne Lieferschein-Mengen, NICHT die Fortschrittszahl!
|
||||
// VDA 4905: Quantities are usually whole numbers as per user requirements
|
||||
const qty70 = data.qtyDtms.find(q => getVal(q.qtySeg, 0, 0) === '70');
|
||||
const efzRaw = qty70 ? (parseFloat(getVal(qty70.qtySeg, 0, 1)) || 0) : 0;
|
||||
const efz = String(Math.round(efzRaw));
|
||||
|
||||
// Pick latest candidate for head of 513
|
||||
// 48=Received, 194=Received (alt), 12=In-Transit
|
||||
let candidates = data.qtyDtms.filter(q => ['48', '194', '12'].includes(getVal(q.qtySeg, 0, 0)));
|
||||
if (candidates.length === 0) candidates = data.qtyDtms.filter(q => getVal(q.qtySeg, 0, 0) === '1');
|
||||
const lastRec = candidates.length > 0 ? candidates[candidates.length - 1] : null;
|
||||
|
||||
let weDate = lastRec && lastRec.date ? lastRec.date : '';
|
||||
if (!weDate && data.dtms) {
|
||||
const dtmRec = data.dtms.find(d => ['131', '50', '171', '137', '11'].includes(getVal(d, 0, 0)));
|
||||
weDate = dtmRec ? getVal(dtmRec, 0, 1) : '';
|
||||
}
|
||||
if (weDate.length === 8) weDate = weDate.slice(2);
|
||||
|
||||
// lastQty likewise without x1000 scaling
|
||||
const lastQtyRaw = lastRec ? (parseFloat(getVal(lastRec.qtySeg, 0, 1)) || 0) : 0;
|
||||
const lastQty = String(Math.round(lastQtyRaw));
|
||||
|
||||
let lastDn = '';
|
||||
const dnQuals = ['AAU', 'AAK', 'VS', 'VN', 'IF', 'SI', 'RE'];
|
||||
if (lastRec && lastRec.rffs && lastRec.rffs.length > 0) {
|
||||
const rr = lastRec.rffs.find(seg => dnQuals.includes(getVal(seg, 0, 0)));
|
||||
if (rr) lastDn = getVal(rr, 0, 1);
|
||||
}
|
||||
if (!lastDn) {
|
||||
const rff = data.rffs.find(r => dnQuals.includes(getVal(r, 0, 0)));
|
||||
lastDn = rff ? getVal(rff, 0, 1) : '';
|
||||
}
|
||||
|
||||
let lastDate = lastRec && lastRec.date ? lastRec.date : '';
|
||||
if (!lastDate && data.dtms) {
|
||||
const dtmL = data.dtms.find(d => ['11', '171', '137', '131', '50'].includes(getVal(d, 0, 0)));
|
||||
lastDate = dtmL ? getVal(dtmL, 0, 1) : '';
|
||||
}
|
||||
if (lastDate.length === 8) lastDate = lastDate.slice(2);
|
||||
|
||||
// Null-Stellung: DTM+51 Datum
|
||||
let nullStellung = '';
|
||||
if (qty70) {
|
||||
// DTM+51 folgt direkt nach QTY+70 in der DELFOR
|
||||
const qty70idx = data.qtyDtms.indexOf(qty70);
|
||||
// Suche DTM+51 in den Artikel-DTMs
|
||||
const dtm51 = data.dtms ? data.dtms.find(d => getVal(d, 0, 0) === '51') : null;
|
||||
if (dtm51) {
|
||||
nullStellung = getVal(dtm51, 0, 1);
|
||||
if (nullStellung.length === 8) nullStellung = nullStellung.slice(2);
|
||||
}
|
||||
}
|
||||
|
||||
records.push(this.build513(chunk513, weDate, lastDn, lastDate, lastQty, efz, nullStellung));
|
||||
|
||||
let count514 = 0;
|
||||
for (let i = 0; i < chunkRest.length; i += 8) {
|
||||
const chunk = chunkRest.slice(i, i + 8);
|
||||
records.push(this.build514(chunk));
|
||||
count514++;
|
||||
}
|
||||
return count514;
|
||||
}
|
||||
|
||||
// --- Format Helpers ---
|
||||
static padN(val, len) {
|
||||
let v = String(val || '').replace(/[^0-9]/g, '');
|
||||
if (v.length > len) v = v.replace(/^0+/, '');
|
||||
return v.padStart(len, '0').slice(-len);
|
||||
}
|
||||
static padA(val, len) { return String(val || '').padEnd(len, ' ').slice(0, len); }
|
||||
|
||||
// --- Builders ---
|
||||
|
||||
static build511(cust, supp, docNum, date, oldDocNum) {
|
||||
let r = '51101';
|
||||
r += this.padA(cust, 9);
|
||||
r += this.padA(supp, 9);
|
||||
r += this.padN(oldDocNum || '00000', 5); // Old Trans No
|
||||
r += this.padN(docNum, 5); // New Trans No
|
||||
r += this.padN(date, 6);
|
||||
r += ''.padEnd(89, ' ');
|
||||
return r;
|
||||
}
|
||||
|
||||
static build512(plant, docNum, docDate, custMat, orderNo, unloading, unit, oldDocNum, oldDocDate) {
|
||||
let r = '51201';
|
||||
r += this.padA(plant, 3);
|
||||
r += this.padN(docNum, 9);
|
||||
r += this.padN(docDate, 6);
|
||||
r += this.padN(oldDocNum || '000000000', 9);
|
||||
r += this.padN(oldDocDate || '000000', 6);
|
||||
r += this.padA(custMat, 22);
|
||||
r += this.padA('', 22);
|
||||
r += this.padA(orderNo, 12);
|
||||
r += this.padA(unloading, 5);
|
||||
r += ' ';
|
||||
r += this.padA(unit, 2);
|
||||
r += 'L';
|
||||
r += ' ';
|
||||
r += 'S';
|
||||
r += 'D ';
|
||||
r += '01 ';
|
||||
r += ' ';
|
||||
return r;
|
||||
}
|
||||
|
||||
static build513(schedules5, weDate, lastDn, lastDate, lastQty, efz, nullStellung) {
|
||||
let r = '51301';
|
||||
r += this.padN(weDate || '000000', 6);
|
||||
r += this.padN(lastDn || '00000000', 8);
|
||||
r += this.padN(lastDate || '000000', 6);
|
||||
r += this.padN(lastQty || '0', 12);
|
||||
r += this.padN(efz || '0', 10);
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (i < schedules5.length) {
|
||||
const s = schedules5[i];
|
||||
r += this.padN(s.date, 6);
|
||||
r += this.padN(s.qty, 9);
|
||||
} else {
|
||||
r += ' ';
|
||||
r += ' ';
|
||||
}
|
||||
}
|
||||
// Null-Stellung (6 Zeichen JJMMTT) am Ende von 513
|
||||
r += this.padN(nullStellung || '000000', 6);
|
||||
return r;
|
||||
}
|
||||
|
||||
static build514(schedules8) {
|
||||
let r = '51401';
|
||||
for (let i = 0; i < 8; i++) {
|
||||
if (i < schedules8.length) {
|
||||
const s = schedules8[i];
|
||||
r += this.padN(s.date, 6);
|
||||
r += this.padN(s.qty, 9);
|
||||
} else {
|
||||
r += ' ';
|
||||
r += ' ';
|
||||
}
|
||||
}
|
||||
r += ' ';
|
||||
return r;
|
||||
}
|
||||
|
||||
static build519(c511, c512, c513, c514) {
|
||||
let r = '51901';
|
||||
r += this.padN(c511, 7);
|
||||
r += this.padN(c512, 7);
|
||||
r += this.padN(c513, 7);
|
||||
r += this.padN(c514, 7);
|
||||
r += '0000000';
|
||||
r += '0000000';
|
||||
r += '0000001';
|
||||
r += ''.padEnd(74, ' ');
|
||||
return r;
|
||||
}
|
||||
}
|
||||
window.EDIBridge.VDA4905Generator = VDA4905Generator;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user