====== EDI/EDIFACT-Guide — Elektronischer Datenaustausch mit Lyx ======
Dieser Guide erklärt den elektronischen B2B-Datenaustausch mit der Lyx-EDI-Bibliothek: von den Grundbegriffen über den Nachrichtenaufbau bis zu vollständigen Workflows für Bestellungen, Rechnungen, Lieferscheine und Zahlungsabwicklung.
→ [[lyx_-_programmiersprache:units:edi|std.edi — Übersicht]] · [[lyx_-_programmiersprache:guides:welche-unit|Welche Unit?]]
----
===== EDI und EDIFACT — Was ist der Unterschied? =====
Die Begriffe werden im Alltag oft synonym verwendet, meinen aber unterschiedliche Dinge:
**EDI** (Electronic Data Interchange) ist ein **Oberbegriff** für den maschinellen Austausch von Geschäftsdokumenten zwischen Computersystemen — ohne manuelle Erfassung. EDI beschreibt das Konzept, nicht das Format. Unter dem Begriff EDI laufen viele Standards:
^ Standard ^ Ursprung ^ Verbreitet in ^
| **UN/EDIFACT** | UNO, seit 1987 | Europa, Asien, Logistik, Handel |
| **ANSI X12** | USA, seit 1979 | Nordamerika, Gesundheitswesen, Handel |
| **TRADACOMS** | GS1 UK, seit 1982 | Britischer Einzelhandel (Altbestand) |
| **VDA** | Deutscher Automobilverband | Deutsche Automobilindustrie |
| **ODETTE** | Europäische Automobilindustrie | Europäischer Automobilbau |
| **OpenTrans / UBL** | XML-basiert, modern | E-Procurement, XRechnung |
**EDIFACT** (UN/EDIFACT — United Nations / Electronic Data Interchange for Administration, Commerce and Transport) ist der **weltweit am häufigsten eingesetzte EDI-Standard** außerhalb Nordamerikas. Er wurde von der UNO standardisiert und definiert exakt, wie eine Nachricht aufgebaut ist: Segmente, Trennzeichen, Zeichensätze, Nachrichtentypen (ORDERS, INVOIC, DESADV, …).
**Lyx's ''std.edi'' implementiert UN/EDIFACT.** Wenn im Lyx-Kontext von "EDI" gesprochen wird, ist immer EDIFACT gemeint.
----
===== EDIFACT-Aufbau =====
Eine EDIFACT-Übertragung (Interchange) ist wie ein Brief strukturiert: Umschlag außen, Inhalt innen.
UNA:+.? ' ← Trennzeichen-Definition (optional)
UNB+UNOA:1+SENDER:1+EMPFÄNGER:1+261201:0900+1' ← Interchange-Anfang
UNH+1+ORDERS:D:96A:UN:EAN008' ← Nachrichtenkopf
BGM+220+ORD-2026-001+9' ← Dokumenttyp + Nummer
DTM+137:20261201:102' ← Datum
LIN+1++04012345678901:SRV' ← Position 1 (GTIN)
QTY+21:100' ← Bestellmenge
UNT+5+1' ← Nachrichtenende (5 Segmente, Nr. 1)
UNZ+1+1' ← Interchange-Ende (1 Nachricht, Nr. 1)
**Hierarchie einer EDIFACT-Übertragung:**
Interchange (UNB…UNZ) ← eine Übertragung
└─ Funktionale Gruppe (UNG…UNE) ← optional; mehrere gleiche Nachrichtentypen
└─ Nachricht (UNH…UNT) ← z. B. 1 ORDERS
└─ Segmente ← BGM, DTM, LIN, QTY, MOA, …
└─ Elemente ← einzelne Felder, durch + getrennt
└─ Komponenten ← Teilfelder, durch : getrennt
**Segmentnamen** sind immer dreistellig. Häufige Segmente:
^ Segment ^ Bedeutung ^ Beispiel ^
| ''UNA'' | Service String Advice — definiert Trennzeichen | ''UNA:+.? ''' |
| ''UNB'' | Interchange Header — Absender, Empfänger, Datum | ''UNB+UNOA:1+…'' |
| ''UNZ'' | Interchange Trailer — Prüfsumme | ''UNZ+1+1''' |
| ''UNH'' | Message Header — Nachrichtentyp + Version | ''UNH+1+ORDERS:D:96A:UN''' |
| ''UNT'' | Message Trailer — Segmentzahl | ''UNT+12+1''' |
| ''BGM'' | Beginning of Message — Dokumenttyp + Nummer | ''BGM+220+ORD-001+9''' |
| ''DTM'' | Date/Time/Period | ''DTM+137:20261201:102''' |
| ''NAD'' | Name and Address | ''NAD+BY+4012345000009::9''' |
| ''LIN'' | Line Item | ''LIN+1++04012345678901:SRV''' |
| ''QTY'' | Quantity | ''QTY+21:100''' |
| ''MOA'' | Monetary Amount | ''MOA+203:1999'' |
| ''RFF'' | Reference | ''RFF+ON:ORD-2026-001''' |
| ''FTX'' | Free Text | ''FTX+AAI+++Lieferkommentar''' |
----
===== Zeichensätze =====
EDIFACT unterstützt mehrere Zeichensätze. Der wichtigste ist **UNOA**:
^ Zeichensatz ^ Zeichen ^ Einsatz ^
| **UNOA** | A–Z (Großbuchstaben), 0–9, Leerzeichen, Sonderzeichen | Standard, B2B-Handel, sichere Wahl |
| **UNOB** | Wie UNOA + a–z (Kleinbuchstaben) | Erweiterter ASCII |
| **UNOC** | ISO 8859-1 (Latin-1, Umlaute) | Deutschland/Österreich/Schweiz |
Lyx setzt standardmäßig **UNOA**. Der Zeichensatz wird im UNB-Segment angegeben und durch ''EdiUnaRead'' aus der eingehenden Nachricht gelesen.
**Standard-Trennzeichen (UNOA)**:
| Komponenten-Trennzeichen | '':'' (Doppelpunkt) |
| Datenelement-Trennzeichen | ''+'' (Plus) |
| Dezimalzeichen | ''.'' (Punkt) |
| Escape-Zeichen (Release) | ''?'' (Fragezeichen) |
| Segment-Abschlusszeichen | ''''(Apostroph) |
Das Escape-Zeichen ''?'' maskiert Sonderzeichen innerhalb von Feldern. ''?+'' bedeutet ein wörtliches ''+'', nicht ein Trennzeichen.
----
===== Unit-Auswahl =====
Die 14 EDI-Units decken alle Phasen einer Lieferkette ab:
Stammdaten → std.edi.catalog (PRICAT, PARTIN)
Partner → std.edi.partner (AS2/SFTP-Profile)
Anfrage → std.edi.rfq (REQOTE, QUOTES)
Bestellung → std.edi.orders (ORDERS, ORDRSP, ORDCHG)
Lieferung → std.edi.shipping (DESADV, RECADV)
Inventar → std.edi.inventory (INVRPT, SLSRPT, SLSFCT)
Lieferabruf → std.edi.delfor (DELFOR, DELJIT)
Rechnung → std.edi.invoice (INVOIC, INVCON, COMDIS, COACSU)
Zahlung → std.edi.payment (REMADV, PAYMUL, PAYORD, DIRDEB, …)
Zoll → std.edi.customs (CUSCAR, CUSDEC, CUSEXP, CUSRSP)
Seefracht → std.edi.seafreight(BAPLIE, VERMAS, CODECO, …)
Transport → std.edi.transport (IFTMIN, IFTSTA, IFTDGN, …)
Infrastruktur → std.edi.messages (CONTRL, APERAK, GENRAL)
Kern → std.edi.core (Parser, UNA/UNB/UNZ)
----
===== Workflow 1 — Eingehende Nachricht empfangen und prüfen =====
Bevor eine Nachricht inhaltlich verarbeitet wird, liest man UNA (Trennzeichen) und UNB (Absender/Empfänger). Der Nachrichtentyp bestimmt, welche Unit für das eigentliche Parsing zuständig ist.
import std.edi.core;
import std.edi.messages;
fn HandleIncoming(inBuf: int64, inBufLen: int64): int64 {
var ctx: int64 := alloc(EDI_CTX_SIZE);
var una: int64 := alloc(EDI_UNA_SIZE);
var unb: int64 := alloc(EDI_SEG_SIZE);
EdiCtxInit(ctx);
// 1. Trennzeichen aus UNA lesen (setzt Kontext)
var rc: int64 := EdiUnaRead(una, inBuf, inBufLen, ctx);
// rc=0 bedeutet keine UNA vorhanden → UNOA-Defaults gelten
// 2. UNB auslesen (Absender, Empfänger, Nachrichtenanzahl)
EdiUnbRead(unb, inBuf, inBufLen, ctx);
// 3. Segmente iterieren um Nachrichtentyp zu finden
var seg: int64 := alloc(EDI_SEGCTX_SIZE);
var s: int64 := alloc(EDI_SEG_SIZE);
EdiSegCtxInit(seg, inBuf, inBufLen, ctx);
while (EdiSegmentNext(seg, s) == 1) {
var elem: int64 := alloc(EDI_ELEM_SIZE);
EdiElementSplit(s, 0, elem);
// Segmentname in elem — für UNH: Nachrichtentyp in Element 2, Komponente 0
free(elem, EDI_ELEM_SIZE);
}
free(s, EDI_SEG_SIZE);
free(seg, EDI_SEGCTX_SIZE);
free(unb, EDI_SEG_SIZE);
free(una, EDI_UNA_SIZE);
free(ctx, EDI_CTX_SIZE);
return 0;
}
Nach dem Lesen von UNH und dem Bestimmen des Nachrichtentyps die entsprechende Unit-Read-Funktion aufrufen (z. B. ''EdiOrdersRead'', ''EdiInvoicRead'').
**Quittung senden (CONTRL):**
Nach jeder empfangenen Nachricht erwartet der Sender eine CONTRL-Quittung. Ohne CONTRL weiß er nicht, ob die Nachricht ankam.
import std.edi.messages;
// Empfang bestätigen
var out: int64 := alloc(4096);
var n: int64 := EdiContrlOk(
"CONTRL-001" as int64, // Referenz
"RECV-2026-001" as int64, // Bestätigte Nachrichtenreferenz
out, 4096);
// Bei Syntaxfehler ablehnen
var n2: int64 := EdiContrlErr(
"CONTRL-002" as int64,
"RECV-2026-002" as int64,
EDI_ERR_SYNTAX, // Fehlercode
"UNH+2 fehlt" as int64, // Beschreibung
out, 4096);
free(out, 4096);
----
===== Workflow 2 — Bestellung senden (ORDERS) =====
Ein vollständiger Bestellvorgang: Bestellung → Auftragsbestätigung → (Ablehnung oder Änderung).
import std.edi.orders;
import std.edi.catalog;
fn SendOrder(): int64 {
// GTIN vorab prüfen
var gtin: pchar := "04012345678901";
if (EdiGtinCheck(gtin as int64, 14) == 0) {
PrintLn("Ungültige GTIN — Bestellung abgebrochen");
return -1;
}
// Header
var hdr: int64 := alloc(EDI_ORDER_HDR_SIZE);
poke64(hdr + EDI_ORDER_HDR_NUM, "ORD-2026-4711" as int64);
poke64(hdr + EDI_ORDER_HDR_NUMLEN, 13);
poke64(hdr + EDI_ORDER_HDR_DATE, "20261201" as int64);
poke64(hdr + EDI_ORDER_HDR_DATELEN, 8);
poke64(hdr + EDI_ORDER_HDR_BUYER_GLN, "4012345000009" as int64); // Käufer-GLN
poke64(hdr + EDI_ORDER_HDR_BUYER_LEN, 13);
poke64(hdr + EDI_ORDER_HDR_SUP_GLN, "4056489000004" as int64); // Lieferanten-GLN
poke64(hdr + EDI_ORDER_HDR_SUP_LEN, 13);
// 3 Positionen
var lines: int64 := alloc(3 * EDI_ORDER_LINE_SIZE);
// Position 1: 100 Stück à 9,99 EUR (zzgl. 19% MwSt.)
poke64(lines + 0 * EDI_ORDER_LINE_SIZE + EDI_ORDER_LINE_GTIN, gtin as int64);
poke64(lines + 0 * EDI_ORDER_LINE_SIZE + EDI_ORDER_LINE_GTINLEN, 14);
poke64(lines + 0 * EDI_ORDER_LINE_SIZE + EDI_ORDER_LINE_QTY, 100);
poke64(lines + 0 * EDI_ORDER_LINE_SIZE + EDI_ORDER_LINE_UNIT, "PCE" as int64);
poke64(lines + 0 * EDI_ORDER_LINE_SIZE + EDI_ORDER_LINE_UNITLEN, 3);
poke64(lines + 0 * EDI_ORDER_LINE_SIZE + EDI_ORDER_LINE_PRICE, 999); // 9,99 EUR × 100
poke64(lines + 0 * EDI_ORDER_LINE_SIZE + EDI_ORDER_LINE_TAXRATE, 1900); // 19 % × 100
poke64(lines + 0 * EDI_ORDER_LINE_SIZE + EDI_ORDER_LINE_LINENO, 1);
// Position 2: 50 Stück à 24,99 EUR
poke64(lines + 1 * EDI_ORDER_LINE_SIZE + EDI_ORDER_LINE_GTIN, "04056489123450" as int64);
poke64(lines + 1 * EDI_ORDER_LINE_SIZE + EDI_ORDER_LINE_GTINLEN, 14);
poke64(lines + 1 * EDI_ORDER_LINE_SIZE + EDI_ORDER_LINE_QTY, 50);
poke64(lines + 1 * EDI_ORDER_LINE_SIZE + EDI_ORDER_LINE_PRICE, 2499);
poke64(lines + 1 * EDI_ORDER_LINE_SIZE + EDI_ORDER_LINE_TAXRATE, 1900);
poke64(lines + 1 * EDI_ORDER_LINE_SIZE + EDI_ORDER_LINE_LINENO, 2);
var out: int64 := alloc(16384);
var n: int64 := EdiOrdersWrite(hdr, lines, 2, out, 16384);
// n < 0 wenn Bestellnummer fehlt
// Bestellung validieren (Summe stimmt bis auf 1 Cent Rundung)
var valrc: int64 := EdiOrdersValidate(hdr, lines, 2);
// valrc = 0: ok, 1: Nettodifferenz, 3: Bruttodifferenz
free(lines, 3 * EDI_ORDER_LINE_SIZE);
free(hdr, EDI_ORDER_HDR_SIZE);
free(out, 16384);
return 0;
}
**Auftragsbestätigung auswerten (ORDRSP):**
import std.edi.orders;
fn ReadOrdrsp(inBuf: int64, inBufLen: int64): int64 {
var hdr: int64 := alloc(EDI_ORDER_HDR_SIZE);
var lines: int64 := alloc(50 * EDI_ORDER_LINE_SIZE);
var count: int64 := EdiOrdrspRead(inBuf, inBufLen, hdr, lines, 50);
var i: int64 := 0;
while (i < count) {
var line: int64 := lines + i * EDI_ORDER_LINE_SIZE;
var status: int64 := peek64(line + EDI_ORDER_LINE_STATUS);
var lineno: int64 := peek64(line + EDI_ORDER_LINE_LINENO);
if (status == 7) {
// STATUS 7 = abgelehnt
PrintLn("Position " + IntToStr(lineno) + " abgelehnt");
} else if (status == 5) {
// STATUS 5 = geändert (andere Menge, anderer Preis)
var newQty: int64 := peek64(line + EDI_ORDER_LINE_QTY);
PrintLn("Position " + IntToStr(lineno) + " geändert: " + IntToStr(newQty) + " Stück");
}
i := i + 1;
}
free(lines, 50 * EDI_ORDER_LINE_SIZE);
free(hdr, EDI_ORDER_HDR_SIZE);
return count;
}
----
===== Workflow 3 — Lieferschein mit SSCC-Hierarchie (DESADV) =====
SSCC (Serial Shipping Container Code) ist eine 18-stellige GS1-Nummer, die jede Versandeinheit eindeutig identifiziert — über Unternehmensgrenzen hinweg. Das DESADV-Segment CPS (Consignment Packing Sequence) bildet die Hierarchie ab.
**Was ist ein SSCC?** Format: 1 Erweiterungsziffer + 7–10 stelliger GS1-Unternehmens-Präfix + fortlaufende Seriennummer + 1 Prüfziffer = 18 Stellen. Beispiel: ''004000000000000011''.
import std.edi.shipping;
fn SendDesadv(): int64 {
var packs: int64 := alloc(5 * EDI_PACKAGE_SIZE);
// Schritt 1: SSCC-Prüfziffer generieren
var base: pchar := "00400000000000001"; // 17-stellige Basis
var sscc1: int64 := alloc(19); // 18 Ziffern + NUL
EdiSsccGenerate(base as int64, sscc1);
// Palette (Index 0, kein Elternteil)
poke64(packs + 0 * EDI_PACKAGE_SIZE + EDI_PACKAGE_SSCC, sscc1);
poke64(packs + 0 * EDI_PACKAGE_SIZE + EDI_PACKAGE_SSCCLEN, 18);
poke64(packs + 0 * EDI_PACKAGE_SIZE + EDI_PACKAGE_TYPE, EDI_PKG_PALLET);
poke64(packs + 0 * EDI_PACKAGE_SIZE + EDI_PACKAGE_PARENT, 0 - 1); // -1 = kein Elternteil
// Karton 1 auf Palette (Index 1, Parent=0)
var sscc2: int64 := alloc(19);
EdiSsccGenerate("00400000000000002" as int64, sscc2);
poke64(packs + 1 * EDI_PACKAGE_SIZE + EDI_PACKAGE_SSCC, sscc2);
poke64(packs + 1 * EDI_PACKAGE_SIZE + EDI_PACKAGE_SSCCLEN, 18);
poke64(packs + 1 * EDI_PACKAGE_SIZE + EDI_PACKAGE_TYPE, EDI_PKG_CARTON);
poke64(packs + 1 * EDI_PACKAGE_SIZE + EDI_PACKAGE_PARENT, 0);
// Artikel im Karton 1 (Index 2, Parent=1)
poke64(packs + 2 * EDI_PACKAGE_SIZE + EDI_PACKAGE_GTIN, "04012345678901" as int64);
poke64(packs + 2 * EDI_PACKAGE_SIZE + EDI_PACKAGE_GTINLEN, 14);
poke64(packs + 2 * EDI_PACKAGE_SIZE + EDI_PACKAGE_QTY, 24);
poke64(packs + 2 * EDI_PACKAGE_SIZE + EDI_PACKAGE_TYPE, EDI_PKG_ITEM);
poke64(packs + 2 * EDI_PACKAGE_SIZE + EDI_PACKAGE_PARENT, 1);
// DESADV schreiben (Bezug auf Bestellnummer)
var out: int64 := alloc(16384);
var n: int64 := EdiDesadvWrite(
"ORD-2026-4711" as int64, // Bestellreferenz
"20261202" as int64,
packs, 3, out, 16384);
free(sscc2, 19);
free(sscc1, 19);
free(packs, 5 * EDI_PACKAGE_SIZE);
free(out, 16384);
return 0;
}
**Wichtig:** Pakete müssen in Hierarchie-Reihenfolge übergeben werden — erst alle Paletten, dann alle Kartons, dann alle Artikel. Der ''PARENT''-Index verweist auf den 0-basierten Positionsindex im Array.
----
===== Workflow 4 — Rechnung erstellen und validieren (INVOIC) =====
EDIFACT unterscheidet vier Rechnungstypen über den BGM-Qualifier:
^ BGM ^ Typ ^ Einsatz ^
| 380 | Rechnung (Soll) | Normalfall |
| 381 | Gutschrift | Retourerstattung |
| 383 | Debit Note | Nachbelastung |
| 386 | Prepayment | Vorauszahlung |
Steuertypen (MOA-Qualifier T):
^ Konstante ^ Typ ^ Satz ^
| ''EDI_TAX_S'' (1) | Normalsatz | 19 % |
| ''EDI_TAX_AA'' (2) | Ermäßigter Satz | 7 % |
| ''EDI_TAX_Z'' (3) | Nullsatz | 0 % |
| ''EDI_TAX_E'' (4) | Steuerbefreit | — |
import std.edi.invoice;
fn SendInvoice(): int64 {
var hdr: int64 := alloc(EDI_INVOIC_HDR_SIZE);
var lines: int64 := alloc(10 * EDI_INVOIC_LINE_SIZE);
// Header — Totale werden von EdiInvoicWrite aus den Positionen berechnet!
// Die Felder TOTAL_NET, TOTAL_TAX, TOTAL_GROSS im Header werden ignoriert.
poke64(hdr + EDI_INVOIC_HDR_NUM, "RE-2026-0042" as int64);
poke64(hdr + EDI_INVOIC_HDR_NUMLEN, 12);
poke64(hdr + EDI_INVOIC_HDR_DATE, "20261202" as int64);
poke64(hdr + EDI_INVOIC_HDR_DATELEN, 8);
poke64(hdr + EDI_INVOIC_HDR_ORDERREF, "ORD-2026-4711" as int64);
poke64(hdr + EDI_INVOIC_HDR_ORDERLEN, 13);
poke64(hdr + EDI_INVOIC_HDR_BUYER_GLN, "4012345000009" as int64);
poke64(hdr + EDI_INVOIC_HDR_BUYER_LEN, 13);
poke64(hdr + EDI_INVOIC_HDR_SUP_GLN, "4056489000004" as int64);
poke64(hdr + EDI_INVOIC_HDR_SUP_LEN, 13);
poke64(hdr + EDI_INVOIC_HDR_CURRENCY, "EUR" as int64);
poke64(hdr + EDI_INVOIC_HDR_CURLEN, 3);
// 2 Rechnungspositionen
poke64(lines + 0 * EDI_INVOIC_LINE_SIZE + EDI_INVOIC_LINE_GTIN, "04012345678901" as int64);
poke64(lines + 0 * EDI_INVOIC_LINE_SIZE + EDI_INVOIC_LINE_GTINLEN, 14);
poke64(lines + 0 * EDI_INVOIC_LINE_SIZE + EDI_INVOIC_LINE_QTY, 100);
poke64(lines + 0 * EDI_INVOIC_LINE_SIZE + EDI_INVOIC_LINE_PRICE, 999); // 9,99 EUR × 100
poke64(lines + 0 * EDI_INVOIC_LINE_SIZE + EDI_INVOIC_LINE_TAXTYPE, EDI_TAX_S); // 19 %
poke64(lines + 1 * EDI_INVOIC_LINE_SIZE + EDI_INVOIC_LINE_GTIN, "04056489123450" as int64);
poke64(lines + 1 * EDI_INVOIC_LINE_SIZE + EDI_INVOIC_LINE_GTINLEN, 14);
poke64(lines + 1 * EDI_INVOIC_LINE_SIZE + EDI_INVOIC_LINE_QTY, 50);
poke64(lines + 1 * EDI_INVOIC_LINE_SIZE + EDI_INVOIC_LINE_PRICE, 2499);
poke64(lines + 1 * EDI_INVOIC_LINE_SIZE + EDI_INVOIC_LINE_TAXTYPE, EDI_TAX_S);
var out: int64 := alloc(16384);
var n: int64 := EdiInvoicWrite(hdr, lines, 2, out, 16384);
// Validierung (Netto, Steuer, Brutto innerhalb 1-Cent-Toleranz)
var valrc: int64 := EdiInvoicValidate(hdr, lines, 2);
if (valrc == 1) { PrintLn("Warnung: Nettodifferenz"); }
if (valrc == 3) { PrintLn("Warnung: Bruttodifferenz"); }
free(lines, 10 * EDI_INVOIC_LINE_SIZE);
free(hdr, EDI_INVOIC_HDR_SIZE);
free(out, 16384);
return 0;
}
----
===== Workflow 5 — Partner-Verwaltung =====
''std.edi.partner'' ist kein Netzwerktreiber, sondern ein **Metadaten-Register**: Wer darf welche Nachrichtentypen senden, über welchen Kanal, mit welchen Sicherheitsoptionen?
import std.edi.partner;
fn SetupPartner(): int64 {
var p: int64 := alloc(EDI_PART_SIZE);
poke64(p + EDI_PART_PROTOCOL, EDI_PROTO_AS2); // AS2 (1) oder SFTP (2)
poke64(p + EDI_PART_PARTNERID, "LIEFERANT-GMBH" as int64);
poke64(p + EDI_PART_PARTNERLEN, 14);
poke64(p + EDI_PART_OURID, "MEINE-AG" as int64);
poke64(p + EDI_PART_OURLEN, 8);
poke64(p + EDI_PART_HOST, "as2.lieferant-gmbh.de" as int64);
poke64(p + EDI_PART_HOSTLEN, 22);
poke64(p + EDI_PART_PORT, 4080);
// Welche Nachrichtentypen dieser Partner empfangen darf (Bitmask)
var types: int64 :=
EDI_MSGTYPE_ORDERS + // 1 — Bestellungen
EDI_MSGTYPE_INVOIC + // 2 — Rechnungen
EDI_MSGTYPE_DESADV; // 4 — Lieferscheine
poke64(p + EDI_PART_MSGTYPES, types);
// AS2-Optionen (Sicherheit)
var opts: int64 :=
EDI_AS2_SIGN + // 1 — Nachricht signieren (S/MIME)
EDI_AS2_ENCRYPT + // 2 — Nachricht verschlüsseln
EDI_AS2_MDN_SYNC; // 4 — Synchrone MDN-Quittung
poke64(p + EDI_PART_OPTIONS, opts);
// Prüfen ob Partner INVOIC unterstützt
if (EdiPartnerSupports(p, EDI_MSGTYPE_INVOIC) == 1) {
PrintLn("Rechnungsversand an Partner möglich");
}
// In Datei speichern / aus Datei lesen
var out: int64 := alloc(4096);
EdiPartnerWrite(p, out, 4096);
// … über AS2/SFTP senden …
free(out, 4096);
free(p, EDI_PART_SIZE);
return 0;
}
**Partner-Liste laden:**
import std.edi.partner;
fn FindPartner(partnerId: int64, idLen: int64): int64 {
var listBuf: int64 := alloc(65536);
var maxParts: int64 := 100;
var parts: int64 := alloc(maxParts * EDI_PART_SIZE);
var count: int64 := EdiPartnerListRead(listBuf, 65536, parts, maxParts);
var found: int64 := EdiPartnerFind(parts, count, partnerId, idLen);
// found = Index (0-basiert) oder -1 wenn nicht gefunden
free(parts, maxParts * EDI_PART_SIZE);
free(listBuf, 65536);
return found;
}
----
===== Workflow 6 — Zahlungsavis mit IBAN/BIC-Validierung =====
Vor dem Senden eines Zahlungsavis (REMADV) oder Lastschriftauftrags (DIRDEB) immer IBAN und BIC prüfen.
import std.edi.payment;
fn SendRemadv(ibanStr: pchar, ibanLen: int64, bicStr: pchar, bicLen: int64): int64 {
// IBAN per MOD-97 prüfen
if (EdiIbanCheck(ibanStr as int64, ibanLen) == 0) {
PrintLn("Ungültige IBAN — Zahlung abgebrochen");
return -1;
}
// BIC: muss 8 oder 11 Zeichen haben, erste 4 = Bankcode (A–Z)
if (EdiBicCheck(bicStr as int64, bicLen) == 0) {
PrintLn("Ungültiger BIC");
return -1;
}
var hdr: int64 := alloc(EDI_PAY_HDR_SIZE);
poke64(hdr + EDI_PAY_HDR_REF, "REMADV-2026-099" as int64);
poke64(hdr + EDI_PAY_HDR_REFLEN, 15);
poke64(hdr + EDI_PAY_HDR_DATE, "20261205" as int64);
poke64(hdr + EDI_PAY_HDR_AMOUNT, 119000); // 1190,00 EUR × 100
poke64(hdr + EDI_PAY_HDR_CURRENCY, "EUR" as int64);
poke64(hdr + EDI_PAY_HDR_CURLEN, 3);
poke64(hdr + EDI_PAY_HDR_CREDIBAN, ibanStr as int64);
poke64(hdr + EDI_PAY_HDR_CREDILEN, ibanLen);
poke64(hdr + EDI_PAY_HDR_BIC, bicStr as int64);
poke64(hdr + EDI_PAY_HDR_BICLEN, bicLen);
poke64(hdr + EDI_PAY_HDR_INVOICEREF, "RE-2026-0042" as int64);
poke64(hdr + EDI_PAY_HDR_INVOICELEN, 12);
// Pflichtfeldprüfung vor dem Senden
var rc: int64 := EdiRemadvCheck(hdr);
if (rc == 1) { PrintLn("Warnung: keine Rechnungsreferenz"); }
// Warnung, kein Fehler — REMADV kann trotzdem gesendet werden
var out: int64 := alloc(4096);
var n: int64 := EdiRemadvWrite(hdr, out, 4096);
free(hdr, EDI_PAY_HDR_SIZE);
free(out, 4096);
return 0;
}
**Unterschied DIRDEB vs. REMADV:**
| **REMADV** (481) | Avis: "Ich habe bereits gezahlt" — Bezug auf Rechnung (RFF+IV) |
| **DIRDEB** (431) | Einzug: "Ich werde abbuchen" — Mandatsreferenz Pflicht (RFF+MR) |
----
===== Workflow 7 — Lieferabruf (DELFOR) =====
DELFOR ist der rollierender Lieferplan: Jeden Montag schickt der Käufer dem Lieferanten eine aktualisierte Übersicht über die nächsten 4–52 Wochen. Typ 1 = fester Bedarf (Produktion garantiert), Typ 3 = Planungsbedarf (Prognose, kann sich noch ändern).
import std.edi.delfor;
fn SendDelfor(): int64 {
// 2 Artikel, je 4 Wochen Vorschau
var lines: int64 := alloc(2 * EDI_DELFOR_LINE_SIZE);
// Artikel 1: GTIN + 4 Anforderungen
poke64(lines + 0 * EDI_DELFOR_LINE_SIZE + EDI_DELFOR_LINE_GTIN, "04012345678901" as int64);
poke64(lines + 0 * EDI_DELFOR_LINE_SIZE + EDI_DELFOR_LINE_GTINLEN, 14);
poke64(lines + 0 * EDI_DELFOR_LINE_SIZE + EDI_DELFOR_LINE_CUMULQTY, 1250); // Kumulation bisher
poke64(lines + 0 * EDI_DELFOR_LINE_SIZE + EDI_DELFOR_LINE_REQCOUNT, 4);
var base: int64 := lines + 0 * EDI_DELFOR_LINE_SIZE + EDI_DELFOR_LINE_REQS;
// KW49: 200 Stück fest
poke64(base + 0 * EDI_DELFOR_REQ_SIZE + EDI_DELFOR_REQ_DATE, "20261207" as int64);
poke64(base + 0 * EDI_DELFOR_REQ_SIZE + EDI_DELFOR_REQ_QTY, 200);
poke64(base + 0 * EDI_DELFOR_REQ_SIZE + EDI_DELFOR_REQ_TYPE, 1); // fest
// KW50: 150 Stück fest
poke64(base + 1 * EDI_DELFOR_REQ_SIZE + EDI_DELFOR_REQ_DATE, "20261214" as int64);
poke64(base + 1 * EDI_DELFOR_REQ_SIZE + EDI_DELFOR_REQ_QTY, 150);
poke64(base + 1 * EDI_DELFOR_REQ_SIZE + EDI_DELFOR_REQ_TYPE, 1); // fest
// KW51: 180 Stück Planung
poke64(base + 2 * EDI_DELFOR_REQ_SIZE + EDI_DELFOR_REQ_DATE, "20261221" as int64);
poke64(base + 2 * EDI_DELFOR_REQ_SIZE + EDI_DELFOR_REQ_QTY, 180);
poke64(base + 2 * EDI_DELFOR_REQ_SIZE + EDI_DELFOR_REQ_TYPE, 3); // Planung
// KW52: 200 Stück Planung
poke64(base + 3 * EDI_DELFOR_REQ_SIZE + EDI_DELFOR_REQ_DATE, "20261228" as int64);
poke64(base + 3 * EDI_DELFOR_REQ_SIZE + EDI_DELFOR_REQ_QTY, 200);
poke64(base + 3 * EDI_DELFOR_REQ_SIZE + EDI_DELFOR_REQ_TYPE, 3); // Planung
// Kumulationsprüfung: neue Kumulation muss ≥ alte sein
var newCumu: int64 := 1250 + 200 + 150; // nach KW50
var rc: int64 := EdiDelforCheckCumu(newCumu, 1250);
if (rc == 1) { PrintLn("Warnung: Kumulation gesunken"); }
var out: int64 := alloc(32768); // DELFOR-Nachrichten können groß werden
var n: int64 := EdiDelforWrite(
"DELFOR-2026-049" as int64,
"20261201" as int64,
lines, 1, out, 32768);
free(lines, 2 * EDI_DELFOR_LINE_SIZE);
free(out, 32768);
return 0;
}
**Speicherhinweis:** ''EDI_DELFOR_LINE_SIZE = 1 280 Bytes'' pro Artikel (Kopf 32 B + 52 × 24 B für Anforderungen). Bei 100 Artikeln sind das 128 KB. Bei sehr vielen Artikeln Puffer-Größe sorgfältig wählen.
----
===== GS1-Validierungen im Überblick =====
Die GS1-Identifikationsnummern sind das Rückgrat des EDIFACT-Handels. Lyx validiert alle gängigen Formate:
^ Funktion ^ Typ ^ Länge ^ Algorithmus ^
| ''EdiGtinCheck'' | GTIN (EAN) | 8, 12, 13, 14 | GS1 Mod-10 |
| ''EdiGlnCheck'' | GLN (Firmenstandort) | 13 | GS1 Mod-10 |
| ''EdiSsccCheck'' | SSCC-18 | 18 | GS1 Mod-10 |
| ''EdiContainerCheck'' | ISO 6346 Container | 11 | Zweierpotenzen Mod-11, Rest %10 |
| ''EdiIbanCheck'' | IBAN | variabel | MOD-97 |
| ''EdiBicCheck'' | BIC/SWIFT | 8 oder 11 | Strukturell |
| ''EdiUnNumberCheck'' | UN-Gefahrgutnummer | 4 | Genau 4 Ziffern |
**GS1 Mod-10-Algorithmus** (für GTIN, GLN, SSCC):
- Alle Ziffern außer Prüfziffer: gerade Positionen × 1, ungerade × 3 (von rechts)
- Summe bilden
- Prüfziffer = (10 − (Summe mod 10)) mod 10
import std.edi.catalog;
import std.edi.shipping;
// Alle GS1-Nummern vor Verwendung prüfen
var gtin: pchar := "04012345678901";
var gln: pchar := "4012345000009";
var sscc: pchar := "004000000000000011";
if (EdiGtinCheck(gtin as int64, 14) == 0) { PrintLn("GTIN ungültig"); }
if (EdiGlnCheck(gln as int64, 13) == 0) { PrintLn("GLN ungültig"); }
if (EdiSsccCheck(sscc as int64, 18) == 0) { PrintLn("SSCC ungültig"); }
----
===== Typischer B2B-Nachrichtenfluss =====
**Beschaffungsprozess (Retail/Industrie):**
Käufer (Retailer) Lieferant
REQOTE → Preisanfrage
← QUOTES Angebot
ORDERS → Bestellung
← ORDRSP Bestätigung (oder Ablehnung)
← DESADV Lieferschein (vor Versand)
Wareneingang:
RECADV → Wareneingangsbestätigung
← INVOIC Rechnung
REMADV → Zahlungsavis
← CONTRL Quittung jeder Nachricht
**Automobilzulieferung (Just-in-Time):**
OEM Zulieferer
DELFOR → Wochenlieferplan (jeden Montag)
DELJIT → JIT-Abruf (Stunden vorher, exakte Zeit)
← DESADV Lieferschein (SSCC pro Behälter)
← INVOIC Rechnung (oft monatliche Sammelrechnung)
COMDIS → Rechnungsdisput (bei Differenzen)
← COACSU Saldenabstimmung
**Seefracht (Container-Logistik):**
Verlader/Spediteur Terminalbetreiber / Reederei
CUSCAR → Zollanmeldung Carrier
CUSDEC → Zollanmeldung Anmelder
← CUSRSP Zollantwort (akzeptiert/freigegeben)
COPARN → Voranmeldung Container-Einlauf
← CODECO Gate-In-Bestätigung
VERMAS → VGM-Gewicht (SOLAS-Pflicht!)
← BAPLIE Stauplan
← COARRI Ankunftsmeldung
← COPINO Pick-up-Freigabe
----
===== Fehlerbehandlung =====
**CONTRL** ist die universelle Quittung für alle EDIFACT-Nachrichten. Ohne CONTRL weiß der Sender nicht, ob die Nachricht ankam und korrekt verarbeitet wurde.
^ Status ^ Bedeutung ^
| ACCEPTED (7) | Nachricht wurde akzeptiert und verarbeitet |
| REJECTED4 (4) | Abgelehnt auf Interchange-Ebene (UNB-Fehler) |
| REJECTED5 (5) | Abgelehnt auf Nachrichten-Ebene (Inhaltsfehler) |
^ Fehlercode ^ Bedeutung ^
| ''EDI_ERR_SYNTAX'' (3) | Syntaxfehler (falsche Segmentfolge, fehlende Pflichtfelder) |
| ''EDI_ERR_NOUNA'' (2) | Keine UNA — Trennzeichen unklar |
| ''EDI_ERR_TRUNC'' (1) | Nachricht unvollständig (Übertragungsabbruch) |
| ''EDI_ERR_DUP'' (4) | Doppelt empfangene Nachricht (gleiche UNB-Referenz) |
| ''EDI_ERR_OVERFLOW'' (5) | Puffer zu klein |
| ''EDI_ERR_PARTNER'' (6) | Unbekannter Absender (nicht in Partnerliste) |
**APERAK** (Application Error and Acknowledgement) ist die inhaltliche Fehlermeldung. Während CONTRL Syntaxprobleme meldet, erklärt APERAK fachliche Probleme ("GTIN unbekannt", "GLN nicht registriert").
import std.edi.messages;
// Syntaxfehler per CONTRL melden
var out: int64 := alloc(4096);
EdiContrlErr(
"CONTRL-ERR-001" as int64,
"RECV-XYZ-001" as int64,
EDI_ERR_SYNTAX,
"BGM+220 ohne DTM+137" as int64,
out, 4096);
// … CONTRL zurücksenden …
// Fachlichen Fehler per APERAK melden
EdiAperakWrite(
"APERAK-001" as int64,
"RECV-XYZ-002" as int64,
14, // error code 14 = invalid value
"GTIN 04012345678900 unbekannt" as int64,
out, 4096);
free(out, 4096);
----
===== Duplikatserkennung =====
''std.edi.core'' enthält einen Duplikat-Ringpuffer (256 Einträge). Jede UNB-Referenznummer wird beim Parsen automatisch geprüft. Doppelt empfangene Nachrichten sind im EDI-Alltag nicht selten (Timeouts, Retry-Loops auf AS2-Ebene).
import std.edi.core;
var state: int64 := alloc(EDI_STATE_SIZE);
EdiStateInit(state);
// EdiDuplicateCheck gibt EDI_ERR_DUP zurück wenn Referenz schon gesehen
var ref: pchar := "UNB-REF-0047";
var rc: int64 := EdiDuplicateCheck(state, ref as int64, 12);
if (rc == EDI_ERR_DUP) {
PrintLn("Duplikat — Nachricht bereits verarbeitet");
// CONTRL mit REJECTED4 oder REJECTED5 zurückschicken
}
free(state, EDI_STATE_SIZE);
Der Ringpuffer umläuft nach 256 Einträgen. Für langfristige Duplikatsverhinderung (über Stunden) muss die Applikation zusätzlich eine persistente Datenbank nutzen.
----
===== Hinweise und häufige Fehler =====
* **Festkomma-Beträge**: Alle Geldbeträge als ''int64 × 100''. ''1999'' = 19,99 €. Niemals ''f64'' für Geldrechnung — Rundungsfehler!
* **EdiInvoicWrite berechnet Summen selbst**: Die Felder ''TOTAL_NET'', ''TOTAL_TAX'', ''TOTAL_GROSS'' im INVOIC-Header werden von der Write-Funktion aus den Positionen neu berechnet. Vorab gesetzte Werte werden überschrieben.
* **EdiOrdersWrite schlägt fehl ohne Bestellnummer**: Rückgabewert -1 wenn ''EDI_ORDER_HDR_NUM'' nicht gesetzt. Immer prüfen.
* **ORDRSP-Referenz**: Bei QUOTES (Angebote) ist ''rfqRef'' Pflichtfeld — ohne Bezug auf die ursprüngliche REQOTE gibt EdiQuotesWrite -1 zurück.
* **DESADV-Hierarchiereihenfolge**: Erst Paletten, dann Kartons, dann Artikel. Falsche Reihenfolge erzeugt ungültiges EDIFACT.
* **DIRDEB ohne Mandatsreferenz**: ''EdiDirdebCheck'' gibt Fehlercode 2 zurück. SEPA-Lastschriften ohne Mandatsreferenz sind rechtlich ungültig.
* **GENRAL-Body freigeben**: ''EdiGenralRead'' alloziert den Body-Buffer. Nach der Verarbeitung ''free(result.BODY, result.BODYLEN)'' aufrufen.
* **AS2-Optionen sind Metadaten**: ''std.edi.partner'' implementiert keinen AS2-Transport. Die Optionen (SIGN, ENCRYPT, MDN_SYNC) werden von der übergeordneten Kommunikationsschicht ausgelesen und angewendet.
* **ISO 6346 Check**: Der Algorithmus überspringt die Werte 11, 22, 33 bei der Buchstaben-zu-Zahl-Zuordnung. ''EdiContainerCheck'' implementiert das korrekt.
* **DELFOR-Puffer**: ''EDI_DELFOR_LINE_SIZE = 1 280 Bytes''. Bei 100 Positionen → 128 KB. Vor der Allokation mit ''alloc()'' Größe berechnen.
----
===== Entscheidungsguide =====
**Welchen Nachrichtentyp für welchen Zweck?**
^ Aufgabe ^ Nachricht ^ Unit ^
| Preisanfrage stellen | REQOTE | ''std.edi.rfq'' |
| Angebot zurückmelden | QUOTES | ''std.edi.rfq'' |
| Bestellung aufgeben | ORDERS | ''std.edi.orders'' |
| Bestellung bestätigen/ablehnen | ORDRSP | ''std.edi.orders'' |
| Bestellung ändern | ORDCHG | ''std.edi.orders'' |
| Lieferschein senden | DESADV | ''std.edi.shipping'' |
| Wareneingang bestätigen | RECADV | ''std.edi.shipping'' |
| Rechnung stellen | INVOIC (BGM 380) | ''std.edi.invoice'' |
| Gutschrift stellen | INVOIC (BGM 381) | ''std.edi.invoice'' |
| Rechnungsdisput eröffnen | COMDIS | ''std.edi.invoice'' |
| Saldenabstimmung | COACSU | ''std.edi.invoice'' |
| Zahlung ankündigen | REMADV | ''std.edi.payment'' |
| Einzelzahlung beauftragen | PAYORD | ''std.edi.payment'' |
| Sammelüberweisung | PAYMUL | ''std.edi.payment'' |
| Lastschrift einziehen | DIRDEB | ''std.edi.payment'' |
| Zahlungsstornierung | FINCAN | ''std.edi.payment'' |
| Akkreditiv eröffnen | DOCAPP | ''std.edi.payment'' |
| Lagerbestand melden | INVRPT | ''std.edi.inventory'' |
| Absatzdaten melden | SLSRPT | ''std.edi.inventory'' |
| Absatzprognose melden | SLSFCT | ''std.edi.inventory'' |
| Wochenlieferplan | DELFOR | ''std.edi.delfor'' |
| JIT-Minuten-Abruf | DELJIT | ''std.edi.delfor'' |
| Zollanmeldung | CUSDEC | ''std.edi.customs'' |
| Zollantwort lesen | CUSRSP | ''std.edi.customs'' |
| Containerstauplan | BAPLIE | ''std.edi.seafreight'' |
| VGM-Gewicht melden (SOLAS) | VERMAS | ''std.edi.seafreight'' |
| Transportauftrag | IFTMIN | ''std.edi.transport'' |
| Sendungsstatus abfragen | IFTSTA | ''std.edi.transport'' |
| Gefahrgutmeldung | IFTDGN | ''std.edi.transport'' |
| Empfangsquittung | CONTRL | ''std.edi.messages'' |
| Fachliche Fehlermeldung | APERAK | ''std.edi.messages'' |
----
Letzte Aktualisierung: 2026-06-16