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.
→ std.edi — Übersicht · Welche Unit?
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.
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' |
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.
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)
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);
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;
}
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.
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;
}
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;
}
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) |
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.
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):
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"); }
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
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);
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.
int64 × 100. 1999 = 19,99 €. Niemals f64 für Geldrechnung — Rundungsfehler!TOTAL_NET, TOTAL_TAX, TOTAL_GROSS im INVOIC-Header werden von der Write-Funktion aus den Positionen neu berechnet. Vorab gesetzte Werte werden überschrieben.EDI_ORDER_HDR_NUM nicht gesetzt. Immer prüfen.rfqRef Pflichtfeld — ohne Bezug auf die ursprüngliche REQOTE gibt EdiQuotesWrite -1 zurück.EdiDirdebCheck gibt Fehlercode 2 zurück. SEPA-Lastschriften ohne Mandatsreferenz sind rechtlich ungültig.EdiGenralRead alloziert den Body-Buffer. Nach der Verarbeitung free(result.BODY, result.BODYLEN) aufrufen.std.edi.partner implementiert keinen AS2-Transport. Die Optionen (SIGN, ENCRYPT, MDN_SYNC) werden von der übergeordneten Kommunikationsschicht ausgelesen und angewendet.EdiContainerCheck implementiert das korrekt.EDI_DELFOR_LINE_SIZE = 1 280 Bytes. Bei 100 Positionen → 128 KB. Vor der Allokation mit alloc() Größe berechnen.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