Inhaltsverzeichnis

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.

std.edi — Übersicht · 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):

  1. Alle Ziffern außer Prüfziffer: gerade Positionen × 1, ungerade × 3 (von rechts)
  2. Summe bilden
  3. 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


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