Erster Commit

This commit is contained in:
zed
2026-03-13 09:53:40 +01:00
commit 5ebcad02ed
3945 changed files with 974582 additions and 0 deletions

25
README.md Normal file
View 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
View 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

Binary file not shown.

BIN
db/Cache/Cache_Data/data_1 Normal file

Binary file not shown.

BIN
db/Cache/Cache_Data/data_2 Normal file

Binary file not shown.

BIN
db/Cache/Cache_Data/data_3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
db/Cache/Cache_Data/index Normal file

Binary file not shown.

BIN
db/Code Cache/js/index Normal file

Binary file not shown.

Binary file not shown.

BIN
db/Code Cache/wasm/index Normal file

Binary file not shown.

Binary file not shown.

BIN
db/DawnCache/data_0 Normal file

Binary file not shown.

BIN
db/DawnCache/data_1 Normal file

Binary file not shown.

BIN
db/DawnCache/data_2 Normal file

Binary file not shown.

BIN
db/DawnCache/data_3 Normal file

Binary file not shown.

BIN
db/DawnCache/index Normal file

Binary file not shown.

BIN
db/GPUCache/data_0 Normal file

Binary file not shown.

BIN
db/GPUCache/data_1 Normal file

Binary file not shown.

BIN
db/GPUCache/data_2 Normal file

Binary file not shown.

BIN
db/GPUCache/data_3 Normal file

Binary file not shown.

BIN
db/GPUCache/index Normal file

Binary file not shown.

1
db/Local State Normal file
View File

@@ -0,0 +1 @@
{"os_crypt":{"audit_enabled":true,"encrypted_key":"RFBBUEkBAAAA0Iyd3wEV0RGMegDAT8KX6wEAAADlg9LsmyQgSas6BLrKsuNKEAAAABIAAABDAGgAcgBvAG0AaQB1AG0AAAADZgAAwAAAABAAAABW5H24fwlGzdIMuEZMgPO1AAAAAASAAACgAAAAEAAAABXpSj0+k5Z5x/lVB1S2EFooAAAAOTpEM4uLHkoAPs0kM30IwQMAcT4uTAhxKOZJ8UK4UpO4ujpesN41kRQAAACqOTDi6xE58RHtBNdRHsjAHKOn/w=="}}

Binary file not shown.

View File

@@ -0,0 +1 @@
MANIFEST-000001

View File

View 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

View 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

Binary file not shown.

BIN
db/Network/Cookies Normal file

Binary file not shown.

View File

View 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"}}}

View File

View 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

Binary file not shown.

View File

1
db/Preferences Normal file
View File

@@ -0,0 +1 @@
{"spellcheck":{"dictionaries":["de"],"dictionary":""}}

Binary file not shown.

View File

@@ -0,0 +1 @@
MANIFEST-000001

0
db/Session Storage/LOCK Normal file
View File

3
db/Session Storage/LOG Normal file
View 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

View 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

Binary file not shown.

BIN
db/Shared Dictionary/cache/index vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
db/Shared Dictionary/db Normal file

Binary file not shown.

View File

0
db/SharedStorage Normal file
View File

BIN
db/conversions.sqlite3 Normal file

Binary file not shown.

BIN
db/conversions.sqlite3-shm Normal file

Binary file not shown.

View File

5
db/watcher-settings.json Normal file
View 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
View 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);

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

77
docs/changelog.md Normal file
View 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.

Binary file not shown.

Binary file not shown.

101
docs/implementation_plan.md Normal file
View 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
View 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.

Binary file not shown.

View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

6
dump_delfor.js Normal file
View 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
View 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
View 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>

1474
js/app.js Normal file

File diff suppressed because it is too large Load Diff

171
js/config-parser.js Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
}
window.EDIBridge.Editor = Editor;

413
js/history.js Normal file
View 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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
}
window.EDIBridge.HistoryManager = HistoryManager;

467
js/ifm_delfor-vda4905.js Normal file
View 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
View 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;

View 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
View 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
View 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