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 · std.edi.core · Welche Unit?

Alle Beispiele dieser Seite sind mit lyxc 1.0.21A übersetzt und ausgeführt; die gezeigten Ausgaben sind echte Programmausgaben.


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.alloc;
import std.string;

fn Feld(p: int64, l: int64): pchar {
    var b: int64 := alloc(l + 1);
    var i: int64 := 0;
    while (i < l) { poke8(b + i, peek8(p + i)); i := i + 1; }
    poke8(b + l, 0);
    return b as pchar;
}

fn main(): int64 {
    var inBuf: pchar := "UNA:+.? 'UNB+UNOA:1+4098765000004+4012345000009+20260813:0930+IC000042'UNH+1+ORDERS:D:01B:UN'BGM+220+BST-2026-0815+9'UNT+3+1'UNZ+1+IC000042'"c;
    var buf: int64 := inBuf as int64;
    var len: int64 := StrLen(inBuf);

    // 1. Trennzeichen: EDI_OK = UNA vorhanden (Nutzdaten ab Byte 9),
    //    EDI_ERR_NOUNA = keine UNA, Standardtrennzeichen gelten (ab Byte 0)
    var una: int64 := alloc(EDI_UNA_SIZE);
    var start: int64 := 0;
    if (EdiUnaRead(buf, len, una) == EDI_OK) { start := 9; }

    // 2. Segmente durchlaufen
    var sctx: int64 := alloc(EDI_SEGCTX_SIZE);
    EdiSegCtxInit(sctx, una, start);
    var seg:   int64 := alloc(EDI_SEG_SIZE);
    var elems: int64 := alloc(16 * EDI_ELEM_SIZE);
    var comps: int64 := alloc(8 * EDI_ELEM_SIZE);
    var ctx:   int64 := alloc(EDI_CTX_SIZE);
    EdiCtxInit(ctx);

    while (EdiSegmentNext(buf, len, sctx, seg) == 1) {
        var p: int64 := peek64(seg + EDI_SEG_PTR);
        var tag: pchar := Feld(p, 3);

        // 3. UNB: EdiUnbRead bekommt genau dieses eine Segment
        if (StrEquals(tag, "UNB") == 1) {
            EdiUnbRead(p, peek64(seg + EDI_SEG_LEN), ctx);
            PrintLn(StrConcat("Absender: ", Feld(peek64(ctx + EDI_CTX_SENDER), peek64(ctx + EDI_CTX_SNDLEN))));
        }

        // 4. UNH: Nachrichtentyp steht in Element 1, Komponente 0
        if (StrEquals(tag, "UNH") == 1) {
            EdiElementSplit(seg, elems, 16);
            var e1p: int64 := peek64(elems + EDI_ELEM_SIZE + EDI_ELEM_PTR);
            var e1l: int64 := peek64(elems + EDI_ELEM_SIZE + EDI_ELEM_LEN);
            EdiComponentSplit(e1p, e1l, comps, 8);
            PrintLn(StrConcat("Nachrichtentyp: ", Feld(peek64(comps + EDI_ELEM_PTR), peek64(comps + EDI_ELEM_LEN))));
        }
    }
    return 0;
}

Absender: 4098765000004
Nachrichtentyp: ORDERS

Der Nachrichtentyp entscheidet, welche Unit den Rumpf liest: ORDERSEdiOrdersRead, INVOICEdiInvoicRead und so weiter.

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;
import std.alloc;

fn main(): int64 {
    var out: int64 := alloc(2048);

    // Empfang bestaetigen: icRef, eigene ID, Partner-ID
    var n: int64 := EdiContrlOk("IC000042"c, "4012345000009"c,
                                "4098765000004"c, out, 2048);

    // Bei Syntaxfehler ablehnen: icRef, Segmentposition, Fehlercode
    var n2: int64 := EdiContrlErr("IC000042"c, 3, EDI_ERR_MISSING_SEG, out, 2048);

    free(out, 2048);
    return 0;
}

EdiContrlOk/EdiContrlErr erzeugen einen vollständigen Interchange von UNB bis UNZ — hier ist also kein eigener Umschlag nötig.


Workflow 2 — Bestellung senden (ORDERS)

Ein vollständiger Bestellvorgang: Bestellung → Auftragsbestätigung → (Ablehnung oder Änderung).

import std.edi.orders;
import std.edi.catalog;
import std.alloc;
import std.string;

fn Feld(p: int64, l: int64): pchar {
    var b: int64 := alloc(l + 1);
    var i: int64 := 0;
    while (i < l) { poke8(b + i, peek8(p + i)); i := i + 1; }
    poke8(b + l, 0);
    return b as pchar;
}

fn main(): int64 {
    var gtin: pchar := "04012345678901"c;

    // GTIN vorab pruefen - keine Write-Funktion tut das
    if (EdiGtinCheck(gtin, 14) == 0) {
        PrintLn("Ungueltige GTIN - Bestellung abgebrochen");
        return 1;
    }

    var hdr: int64 := alloc(EDI_ORDER_HDR_SIZE);
    var i: int64 := 0;
    while (i < EDI_ORDER_HDR_SIZE) { poke8(hdr + i, 0); i := i + 1; }
    poke64(hdr + EDI_ORDER_HDR_NUM,        "ORD-2026-4711"c as int64);
    poke64(hdr + EDI_ORDER_HDR_NUMLEN,     13);
    poke64(hdr + EDI_ORDER_HDR_DATE,       "20260813"c as int64);
    poke64(hdr + EDI_ORDER_HDR_DATELEN,    8);
    poke64(hdr + EDI_ORDER_HDR_BUYER_GLN,  "4012345000009"c as int64);
    poke64(hdr + EDI_ORDER_HDR_BUYER_GLNL, 13);
    poke64(hdr + EDI_ORDER_HDR_SUP_GLN,    "4098765000004"c as int64);
    poke64(hdr + EDI_ORDER_HDR_SUP_GLNL,   13);

    var lines: int64 := alloc(2 * EDI_ORDER_LINE_SIZE);
    i := 0;
    while (i < 2 * EDI_ORDER_LINE_SIZE) { poke8(lines + i, 0); i := i + 1; }
    poke64(lines + EDI_ORDER_LINE_GTIN,    gtin as int64);
    poke64(lines + EDI_ORDER_LINE_GTINLEN, 14);
    poke64(lines + EDI_ORDER_LINE_QTY,     100);
    poke64(lines + EDI_ORDER_LINE_UNIT,    "PCE"c as int64);
    poke64(lines + EDI_ORDER_LINE_UNITLEN, 3);
    poke64(lines + EDI_ORDER_LINE_PRICE,   999);     // 9,99 EUR
    poke64(lines + EDI_ORDER_LINE_TAXRATE, 1900);    // 19,00 %
    poke64(lines + EDI_ORDER_LINE_LINENO,  1);
    var l1: int64 := lines + EDI_ORDER_LINE_SIZE;
    poke64(l1 + EDI_ORDER_LINE_GTIN,    "04012345678918"c as int64);
    poke64(l1 + EDI_ORDER_LINE_GTINLEN, 14);
    poke64(l1 + EDI_ORDER_LINE_QTY,     50);
    poke64(l1 + EDI_ORDER_LINE_PRICE,   2499);
    poke64(l1 + EDI_ORDER_LINE_TAXRATE, 1900);
    poke64(l1 + EDI_ORDER_LINE_LINENO,  2);

    // Erwartete Summe im Kopf eintragen, sonst prueft Validate nichts
    poke64(hdr + EDI_ORDER_HDR_TOTAL, 100 * 999 + 50 * 2499);
    PrintLn(StrConcat("Summenpruefung (0 = ok): ", IntToStr(EdiOrdersValidate(hdr, lines, 2))));

    var out: int64 := alloc(4096);
    var n: int64 := EdiOrdersWrite(hdr, lines, 2, out, 4096);
    if (n < 0) { PrintLn("Bestellnummer fehlt"); return 1; }
    PrintLn(Feld(out, n));
    return 0;
}

Summenpruefung (0 = ok): 0
BGM+220+ORD-2026-4711+9'DTM+137:20260813:102'NAD+BY+4012345000009::9'NAD+SU+4098765000004::9'LIN+1++04012345678901:SRV'QTY+21:100:PCE'PRI+AAA:9.99:CP'TAX+7+VAT+++:::19.00'LIN+2++04012345678918:SRV'QTY+21:50'PRI+AAA:24.99:CP'TAX+7+VAT+++:::19.00'UNS+S'MOA+77:2248.50'CNT+2:2'

Auftragsbestätigung auswerten (ORDRSP):

import std.edi.orders;
import std.alloc;
import std.string;

fn main(): int64 {
    // Antwort des Lieferanten: Position 1 bestaetigt, Position 2 geaendert, Position 3 abgelehnt
    var msg: pchar := "BGM+231+AB-9001+9'RFF+ON:ORD-2026-4711'LIN+1+1+04012345678901:SRV'QTY+21:100'LIN+2+5+04012345678918:SRV'QTY+21:30'LIN+3+7+04012345678925:SRV'QTY+21:0'CNT+2:3'"c;

    var hdr:   int64 := alloc(EDI_ORDER_HDR_SIZE);
    var lines: int64 := alloc(50 * EDI_ORDER_LINE_SIZE);
    var count: int64 := EdiOrdrspRead(msg as int64, StrLen(msg), 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 == 1) {
            PrintLn(StrConcat(StrConcat("Position ", IntToStr(lineno)), " bestaetigt"));
        } else if (status == 5) {
            PrintLn(StrConcat(StrConcat(StrConcat("Position ", IntToStr(lineno)), " geaendert auf Menge "),
                    IntToStr(peek64(line + EDI_ORDER_LINE_QTY))));
        } else if (status == 7) {
            PrintLn(StrConcat(StrConcat("Position ", IntToStr(lineno)), " abgelehnt"));
        }
        i := i + 1;
    }
    free(lines, 50 * EDI_ORDER_LINE_SIZE);
    free(hdr, EDI_ORDER_HDR_SIZE);
    return 0;
}

Position 1 bestaetigt
Position 2 geaendert auf Menge 30
Position 3 abgelehnt

Der Positionsstatus steht im zweiten Datenelement des LIN-Segments: 1 bestätigt, 5 geändert, 7 abgelehnt.


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;
import std.alloc;
import std.string;

fn Feld(p: int64, l: int64): pchar {
    var b: int64 := alloc(l + 1);
    var i: int64 := 0;
    while (i < l) { poke8(b + i, peek8(p + i)); i := i + 1; }
    poke8(b + l, 0);
    return b as pchar;
}

fn main(): int64 {
    // SSCC aus 17 Stellen erzeugen (Prüfziffer haengt die Bibliothek an)
    var ssccPal: int64 := alloc(19);
    var ssccKar: int64 := alloc(19);
    EdiSsccGenerate("34012345000000001"c, ssccPal);
    EdiSsccGenerate("34012345000000002"c, ssccKar);

    // Hierarchie: Palette -> Karton -> Artikel
    var pk: int64 := alloc(3 * EDI_PACKAGE_SIZE);
    var i: int64 := 0;
    while (i < 3 * EDI_PACKAGE_SIZE) { poke8(pk + i, 0); i := i + 1; }

    var p0: int64 := pk;
    poke64(p0 + EDI_PACKAGE_SSCC,    ssccPal);
    poke64(p0 + EDI_PACKAGE_SSCCLEN, 18);
    poke64(p0 + EDI_PACKAGE_TYPE,    EDI_PKG_PALLET);
    poke64(p0 + EDI_PACKAGE_PARENT,  0 - 1);

    var p1: int64 := pk + EDI_PACKAGE_SIZE;
    poke64(p1 + EDI_PACKAGE_SSCC,    ssccKar);
    poke64(p1 + EDI_PACKAGE_SSCCLEN, 18);
    poke64(p1 + EDI_PACKAGE_TYPE,    EDI_PKG_CARTON);
    poke64(p1 + EDI_PACKAGE_PARENT,  0);        // haengt an Paket 0

    var p2: int64 := pk + 2 * EDI_PACKAGE_SIZE;
    poke64(p2 + EDI_PACKAGE_TYPE,    EDI_PKG_ITEM);
    poke64(p2 + EDI_PACKAGE_PARENT,  1);        // liegt im Karton
    poke64(p2 + EDI_PACKAGE_GTIN,    "04012345678901"c as int64);
    poke64(p2 + EDI_PACKAGE_GTINLEN, 14);
    poke64(p2 + EDI_PACKAGE_QTY,     24);

    var out: int64 := alloc(4096);
    var n: int64 := EdiDesadvWrite("LS-2026-0815"c, "20260813"c, pk, 3, out, 4096);
    PrintLn(Feld(out, n));

    var pk2: int64 := alloc(16 * EDI_PACKAGE_SIZE);
    var cnt: int64 := EdiDesadvRead(out, n, pk2, 16);
    PrintLn(StrConcat("Packstuecke: ", IntToStr(cnt)));
    i := 0;
    while (i < cnt) {
        var p: int64 := pk2 + i * EDI_PACKAGE_SIZE;
        PrintLn(StrConcat(StrConcat("  Typ ", IntToStr(peek64(p + EDI_PACKAGE_TYPE))),
                StrConcat(" Parent ", IntToStr(peek64(p + EDI_PACKAGE_PARENT)))));
        i += 1;
    }
    return 0;
}

BGM+351+LS-2026-0815+9'DTM+137:20260813:102'CPS+1'PAC+1++201'PCI+33E'GIN+BJ+340123450000000017'CPS+2+1'PAC+1++CT'PCI+33E'GIN+BJ+340123450000000024'CPS+3+2'LIN+1++04012345678901:SRV'QTY+12:24'CNT+2:3'
Packstuecke: 3
  Typ 1 Parent -1
  Typ 2 Parent 0
  Typ 3 Parent 1

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_INVOIC_TAX_S (1) Normalsatz 19 %
EDI_INVOIC_TAX_AA (2) Ermäßigter Satz 7 %
EDI_INVOIC_TAX_Z (3) Nullsatz 0 %
EDI_INVOIC_TAX_E (4) Steuerbefreit

import std.edi.invoice;
import std.alloc;
import std.string;

fn Feld(p: int64, l: int64): pchar {
    var b: int64 := alloc(l + 1);
    var i: int64 := 0;
    while (i < l) { poke8(b + i, peek8(p + i)); i := i + 1; }
    poke8(b + l, 0);
    return b as pchar;
}

// Betrag in Cent als "123.45" ausgeben
fn Geld(cent: int64): pchar {
    var r: int64 := cent - (cent / 100) * 100;
    var nk: pchar := IntToStr(r);
    if (r < 10) { nk := StrConcat("0", nk); }
    return StrConcat(StrConcat(IntToStr(cent / 100), ","), nk);
}

fn main(): int64 {
    var hdr: int64 := alloc(EDI_INVOIC_HDR_SIZE);
    var i: int64 := 0;
    while (i < EDI_INVOIC_HDR_SIZE) { poke8(hdr + i, 0); i := i + 1; }
    poke64(hdr + EDI_INVOIC_HDR_NUM,      "RE-2026-4711"c as int64);
    poke64(hdr + EDI_INVOIC_HDR_NUMLEN,   12);
    poke64(hdr + EDI_INVOIC_HDR_DATE,     "20260813"c as int64);
    poke64(hdr + EDI_INVOIC_HDR_DATELEN,  8);
    poke64(hdr + EDI_INVOIC_HDR_BGMQUAL,  380);            // 380 = Rechnung
    poke64(hdr + EDI_INVOIC_HDR_SUPPLIER, "4098765000004"c as int64);
    poke64(hdr + EDI_INVOIC_HDR_SUPLEN,   13);
    poke64(hdr + EDI_INVOIC_HDR_BUYER,    "4012345000009"c as int64);
    poke64(hdr + EDI_INVOIC_HDR_BUYLEN,   13);
    poke64(hdr + EDI_INVOIC_HDR_CURRENCY, "EUR"c as int64);
    poke64(hdr + EDI_INVOIC_HDR_CURLEN,   3);

    var lines: int64 := alloc(2 * EDI_INVOIC_LINE_SIZE);
    i := 0;
    while (i < 2 * EDI_INVOIC_LINE_SIZE) { poke8(lines + i, 0); i := i + 1; }
    poke64(lines + EDI_INVOIC_LINE_GTIN,    "04012345678901"c as int64);
    poke64(lines + EDI_INVOIC_LINE_GTINLEN, 14);
    poke64(lines + EDI_INVOIC_LINE_QTY,     24);
    poke64(lines + EDI_INVOIC_LINE_PRICE,   1999);
    poke64(lines + EDI_INVOIC_LINE_NETAMT,  24 * 1999);
    poke64(lines + EDI_INVOIC_LINE_TAXTYPE, EDI_INVOIC_TAX_S);
    poke64(lines + EDI_INVOIC_LINE_LINENO,  1);
    var l1: int64 := lines + EDI_INVOIC_LINE_SIZE;
    poke64(l1 + EDI_INVOIC_LINE_GTIN,    "04012345678918"c as int64);
    poke64(l1 + EDI_INVOIC_LINE_GTINLEN, 14);
    poke64(l1 + EDI_INVOIC_LINE_QTY,     6);
    poke64(l1 + EDI_INVOIC_LINE_PRICE,   4950);
    poke64(l1 + EDI_INVOIC_LINE_NETAMT,  6 * 4950);
    poke64(l1 + EDI_INVOIC_LINE_TAXTYPE, EDI_INVOIC_TAX_AA);
    poke64(l1 + EDI_INVOIC_LINE_LINENO,  2);

    var out: int64 := alloc(4096);
    var n: int64 := EdiInvoicWrite(hdr, lines, 2, out, 4096);
    PrintLn(Feld(out, n));

    var hdr2: int64 := alloc(EDI_INVOIC_HDR_SIZE);
    var lin2: int64 := alloc(8 * EDI_INVOIC_LINE_SIZE);
    var cnt: int64 := EdiInvoicRead(out, n, hdr2, lin2, 8);
    PrintLn(StrConcat("Positionen: ", IntToStr(cnt)));
    PrintLn(StrConcat("Netto:  ", Geld(peek64(hdr2 + EDI_INVOIC_HDR_NETTOTAL))));
    PrintLn(StrConcat("Steuer: ", Geld(peek64(hdr2 + EDI_INVOIC_HDR_TAXTOTAL))));
    PrintLn(StrConcat("Brutto: ", Geld(peek64(hdr2 + EDI_INVOIC_HDR_GROSSTOTAL))));
    PrintLn(StrConcat("Validierung: ", IntToStr(EdiInvoicValidate(hdr2, lin2, cnt))));
    return 0;
}

BGM+380+RE-2026-4711+9'DTM+137:20260813:102'NAD+SU+4098765000004::9'NAD+BY+4012345000009::9'CUX+2:EUR:4'LIN+1++04012345678901:SRV'QTY+47:24'PRI+AAA:19.99:CP'MOA+203:479.76'TAX+7+VAT+++:::S'LIN+2++04012345678918:SRV'QTY+47:6'PRI+AAA:49.50:CP'MOA+203:297.00'TAX+7+VAT+++:::AA'UNS+S'TAX+7+VAT+++:::S'MOA+125:479.76'MOA+124:91.15'TAX+7+VAT+++:::AA'MOA+125:297.00'MOA+124:20.79'MOA+77:776.76'MOA+176:111.94'MOA+9:888.70'CNT+2:2'
Positionen: 2
Netto:  776,76
Steuer: 111,94
Brutto: 888,70
Validierung: 0

EdiInvoicWrite rechnet Netto-, Steuer- und Bruttosumme aus den Positionen und weist die Steuer je Satz getrennt aus. EdiInvoicValidate vergleicht sie mit den Kopfwerten: 0 passt, 1 Nettodifferenz, 3 Bruttodifferenz.


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;
import std.alloc;
import std.string;

fn Feld(p: int64, l: int64): pchar {
    var b: int64 := alloc(l + 1);
    var i: int64 := 0;
    while (i < l) { poke8(b + i, peek8(p + i)); i := i + 1; }
    poke8(b + l, 0);
    return b as pchar;
}

fn main(): int64 {
    var p: int64 := alloc(EDI_PART_SIZE);
    var i: int64 := 0;
    while (i < EDI_PART_SIZE) { poke8(p + i, 0); i := i + 1; }

    poke64(p + EDI_PART_PROTOCOL,   EDI_PART_PROTO_AS2);
    poke64(p + EDI_PART_PARTNERID,  "PARTNER-AS2-ID"c as int64);
    poke64(p + EDI_PART_PARTNERLEN, 14);
    poke64(p + EDI_PART_OURID,      "MEINE-AS2-ID"c as int64);
    poke64(p + EDI_PART_OURIDLEN,   12);
    poke64(p + EDI_PART_HOST,       "edi.partner.example"c as int64);
    poke64(p + EDI_PART_HOSTLEN,    19);
    poke64(p + EDI_PART_PORT,       443);
    poke64(p + EDI_PART_PATH,       "/as2/inbound"c as int64);
    poke64(p + EDI_PART_PATHLEN,    12);
    poke64(p + EDI_PART_MSGTYPES,   EDI_PART_MSG_ORDERS + EDI_PART_MSG_INVOIC + EDI_PART_MSG_DESADV);
    poke64(p + EDI_PART_OPTIONS,    EDI_PART_OPT_SIGN + EDI_PART_OPT_ENCRYPT);

    PrintLn(StrConcat("gueltig: ", IntToStr(EdiPartnerValidate(p))));

    var out: int64 := alloc(1024);
    var n: int64 := EdiPartnerWrite(p, out, 1024);
    Print(Feld(out, n));            // Zeile endet bereits mit \n

    // Zeile wieder einlesen
    var p2: int64 := alloc(EDI_PART_SIZE);
    if (EdiPartnerRead(out, n, p2) == 1) {
        PrintLn(StrConcat("Host: ", Feld(peek64(p2 + EDI_PART_HOST), peek64(p2 + EDI_PART_HOSTLEN))));
        PrintLn(StrConcat("Port: ", IntToStr(peek64(p2 + EDI_PART_PORT))));
        PrintLn(StrConcat("ORDERS erlaubt: ", IntToStr(EdiPartnerSupports(p2, EDI_PART_MSG_ORDERS))));
        PrintLn(StrConcat("CUSTOMS erlaubt: ", IntToStr(EdiPartnerSupports(p2, EDI_PART_MSG_CUSTOMS))));
    }

    // Registry: mehrere Zeilen, Suche nach Partner-ID
    var parts: int64 := alloc(4 * EDI_PART_SIZE);
    var cnt: int64 := EdiPartnerListRead(out, n, parts, 4);
    // EdiPartnerFind liefert einen ZEIGER auf den Datensatz, keinen Index - 0 = nicht gefunden
    var rec: int64 := EdiPartnerFind(parts, cnt, "PARTNER-AS2-ID"c, 14);
    if (rec != 0) {
        PrintLn(StrConcat("Registry-Eintraege: ", IntToStr(cnt)));
        PrintLn(StrConcat("Pfad des Treffers: ", Feld(peek64(rec + EDI_PART_PATH), peek64(rec + EDI_PART_PATHLEN))));
    } else {
        PrintLn("Partner nicht gefunden");
    }
    return 0;
}

gueltig: 1
AS2|PARTNER-AS2-ID|MEINE-AS2-ID|edi.partner.example|443|/as2/inbound|ORDERS,INVOIC,DESADV|3
Host: edi.partner.example
Port: 443
ORDERS erlaubt: 1
CUSTOMS erlaubt: 0
Registry-Eintraege: 1
Pfad des Treffers: /as2/inbound

Eine Partner-Registry ist damit eine einfache Textdatei — eine pipe-getrennte Zeile je Partner, die EdiPartnerListRead wieder einliest. EdiPartnerFind liefert einen Zeiger auf den Treffer (0 = nicht gefunden), keinen Index.


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;
import std.alloc;
import std.string;

fn Feld(p: int64, l: int64): pchar {
    var b: int64 := alloc(l + 1);
    var i: int64 := 0;
    while (i < l) { poke8(b + i, peek8(p + i)); i := i + 1; }
    poke8(b + l, 0);
    return b as pchar;
}

fn main(): int64 {
    var hdr: int64 := alloc(EDI_PAY_HDR_SIZE);
    var i: int64 := 0;
    while (i < EDI_PAY_HDR_SIZE) { poke8(hdr + i, 0); i := i + 1; }
    poke64(hdr + EDI_PAY_HDR_REF,        "AVIS-2026-0091"c as int64);
    poke64(hdr + EDI_PAY_HDR_REFLEN,     14);
    poke64(hdr + EDI_PAY_HDR_DATE,       "20260813"c as int64);
    poke64(hdr + EDI_PAY_HDR_DATELEN,    8);
    poke64(hdr + EDI_PAY_HDR_AMOUNT,     88870);        // 888,70 EUR
    poke64(hdr + EDI_PAY_HDR_CURRENCY,   "EUR"c as int64);
    poke64(hdr + EDI_PAY_HDR_CURLEN,     3);
    poke64(hdr + EDI_PAY_HDR_CREDIBAN,   "DE89370400440532013000"c as int64);
    poke64(hdr + EDI_PAY_HDR_CREDILEN,   22);
    poke64(hdr + EDI_PAY_HDR_BIC,        "COBADEFFXXX"c as int64);
    poke64(hdr + EDI_PAY_HDR_BICLEN,     11);
    poke64(hdr + EDI_PAY_HDR_INVOICEREF, "RE-2026-4711"c as int64);
    poke64(hdr + EDI_PAY_HDR_INVOICELEN, 12);

    // Bankdaten selbst pruefen - die Write-Funktionen tun es nicht
    PrintLn(StrConcat("IBAN gueltig: ", IntToStr(EdiIbanCheck(peek64(hdr + EDI_PAY_HDR_CREDIBAN), 22))));
    PrintLn(StrConcat("BIC gueltig:  ", IntToStr(EdiBicCheck(peek64(hdr + EDI_PAY_HDR_BIC), 11))));
    PrintLn(StrConcat("REMADV-Pflichtfelder (0 = ok): ", IntToStr(EdiRemadvCheck(hdr))));

    var out: int64 := alloc(4096);
    var n: int64 := EdiRemadvWrite(hdr, out, 4096);
    PrintLn(Feld(out, n));

    var hdr2: int64 := alloc(EDI_PAY_HDR_SIZE);
    EdiRemadvRead(out, n, hdr2);
    PrintLn(StrConcat("Betrag/100: ", IntToStr(peek64(hdr2 + EDI_PAY_HDR_AMOUNT))));
    return 0;
}

IBAN gueltig: 1
BIC gueltig:  1
REMADV-Pflichtfelder (0 = ok): 0
BGM+481+AVIS-2026-0091+9'DTM+137:20260813:102'FII+CR+DE89370400440532013000:COBADEFFXXX:25'CUX+2:EUR:4'MOA+9:888.70'RFF+IV:RE-2026-4711'CNT+2:1'
Betrag/100: 88870

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;
import std.alloc;
import std.string;

fn Feld(p: int64, l: int64): pchar {
    var b: int64 := alloc(l + 1);
    var i: int64 := 0;
    while (i < l) { poke8(b + i, peek8(p + i)); i := i + 1; }
    poke8(b + l, 0);
    return b as pchar;
}

fn main(): int64 {
    // DELFOR: eine Materialnummer, drei Abrufe (1x fest, 2x Vorschau)
    var lines: int64 := alloc(EDI_DELFOR_LINE_SIZE);
    var i: int64 := 0;
    while (i < EDI_DELFOR_LINE_SIZE) { poke8(lines + i, 0); i := i + 1; }
    poke64(lines + EDI_DELFOR_LINE_GTIN,     "04012345678901"c as int64);
    poke64(lines + EDI_DELFOR_LINE_GTINLEN,  14);
    poke64(lines + EDI_DELFOR_LINE_CUMULQTY, 12000);   // Fortschrittszahl
    poke64(lines + EDI_DELFOR_LINE_REQCOUNT, 3);

    var req: int64 := lines + EDI_DELFOR_LINE_REQS;
    poke64(req + EDI_DELFOR_REQ_DATE, "20260817"c as int64);
    poke64(req + EDI_DELFOR_REQ_QTY,  600);
    poke64(req + EDI_DELFOR_REQ_TYPE, 1);              // 1 = fester Abruf
    var r1: int64 := req + EDI_DELFOR_REQ_SIZE;
    poke64(r1 + EDI_DELFOR_REQ_DATE, "20260824"c as int64);
    poke64(r1 + EDI_DELFOR_REQ_QTY,  600);
    poke64(r1 + EDI_DELFOR_REQ_TYPE, 3);               // 3 = Planzahl
    var r2: int64 := req + 2 * EDI_DELFOR_REQ_SIZE;
    poke64(r2 + EDI_DELFOR_REQ_DATE, "20260831"c as int64);
    poke64(r2 + EDI_DELFOR_REQ_QTY,  450);
    poke64(r2 + EDI_DELFOR_REQ_TYPE, 3);

    var out: int64 := alloc(8192);
    var n: int64 := EdiDelforWrite("ABR-2026-33"c, "20260813"c, lines, 1, out, 8192);
    PrintLn(Feld(out, n));

    var l2: int64 := alloc(2 * EDI_DELFOR_LINE_SIZE);
    var cnt: int64 := EdiDelforRead(out, n, l2, 2);
    PrintLn(StrConcat(StrConcat("Zeilen: ", IntToStr(cnt)),
            StrConcat("  Abrufe: ", IntToStr(peek64(l2 + EDI_DELFOR_LINE_REQCOUNT)))));

    // Fortschrittszahl darf nie sinken
    PrintLn(StrConcat("Kumulativ 12600 nach 12000 ok: ", IntToStr(EdiDelforCheckCumu(12600, 12000))));
    PrintLn(StrConcat("Kumulativ 11000 nach 12000 ok: ", IntToStr(EdiDelforCheckCumu(11000, 12000))));
    return 0;
}

BGM+241+ABR-2026-33+9'DTM+137:20260813:102'LIN+1++04012345678901:SRV'SCC+1+12000'QTY+1:600'DTM+64:20260817:102'QTY+3:600'DTM+64:20260824:102'QTY+3:450'DTM+64:20260831:102'CNT+2:1'
Zeilen: 1  Abrufe: 3
Kumulativ 12600 nach 12000 ok: 0
Kumulativ 11000 nach 12000 ok: 1

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;
import std.alloc;
import std.string;

fn main(): int64 {
    // Alle GS1-Nummern vor Verwendung pruefen (1 = gueltig)
    PrintLn(StrConcat("GTIN: ", IntToStr(EdiGtinCheck("04012345678901"c, 14))));
    PrintLn(StrConcat("GLN:  ", IntToStr(EdiGlnCheck("4012345000009"c, 13))));

    // Eine SSCC baut man am besten aus 17 Stellen - die Pruefziffer rechnet die Unit
    var sscc: int64 := alloc(19);
    EdiSsccGenerate("34012345000000001"c, sscc);
    PrintLn(StrConcat(StrConcat("SSCC: ", sscc as pchar), StrConcat(" -> ", IntToStr(EdiSsccCheck(sscc as pchar, 18)))));

    // Von Hand erfundene Pruefziffern fallen durch
    PrintLn(StrConcat("SSCC 004000000000000011: ", IntToStr(EdiSsccCheck("004000000000000011"c, 18))));
    return 0;
}

GTIN: 1
GLN:  1
SSCC: 340123450000000017 -> 1
SSCC 004000000000000011: 0

Die zuletzt geprüfte Nummer stammt aus einer älteren Fassung dieser Seite — sie sieht plausibel aus, trägt aber die falsche Prüfziffer. Genau dafür gibt es EdiSsccGenerate.


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;
import std.alloc;

fn main(): int64 {
    var out: int64 := alloc(2048);

    // Syntaxfehler per CONTRL melden: icRef, Segmentposition, Fehlercode
    EdiContrlErr("IC000042"c, 3, EDI_ERR_SYNTAX, out, 2048);

    // Fachlichen Fehler per APERAK melden: Bezugsnachricht, Fehlercode, Freitext
    EdiAperakWrite("BST-2026-0815"c, 14,
                   "GTIN 04012345678900 unbekannt"c, out, 2048);

    free(out, 2048);
    return 0;
}


Duplikatserkennung

std.edi.core führt je Partner einen Ringpuffer mit 128 Interchange-Referenzen (maximal 16 Partner je EdiState). Geprüft wird nicht automatisch: EdiDuplicateCheck muss nach dem Lesen des UNB selbst aufgerufen werden. Der Rückgabewert ist 1 für ein Duplikat und 0 für eine neue Referenz — die dabei gleich registriert wird. Doppelt empfangene Nachrichten sind im EDI-Alltag nicht selten (Timeouts, Retry-Loops auf AS2-Ebene).

import std.edi.core;
import std.alloc;
import std.string;

fn main(): int64 {
    var state: int64 := alloc(EDI_STATE_SIZE);
    EdiStateInit(state);

    var partner: pchar := "4098765000004"c;      // Partnerschluessel (z. B. Absender-GLN)
    var icRef:   pchar := "IC000042"c;           // Interchange-Referenz aus dem UNB

    // Erster Empfang: 0 = neu, wird registriert
    PrintLn(StrConcat("1. Empfang: ", IntToStr(EdiDuplicateCheck(state, partner as int64, icRef as int64))));
    // Zweiter Empfang derselben Referenz: 1 = Duplikat
    PrintLn(StrConcat("2. Empfang: ", IntToStr(EdiDuplicateCheck(state, partner as int64, icRef as int64))));

    // Laufende Nummer fuer ausgehende Interchanges je Partner
    PrintLn(StrConcat("naechste Sequenznummer: ", IntToStr(EdiSeqNext(state, partner))));

    free(state, EDI_STATE_SIZE);
    return 0;
}

1. Empfang: 0
2. Empfang: 1
naechste Sequenznummer: 1

Der Ring überschreibt nach 128 Referenzen die ältesten Einträge, und der Zustand lebt nur im Speicher. Für eine Duplikatssperre über Stunden oder Neustarts hinweg gehört zusätzlich eine persistente Tabelle in die Anwendung.


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: EDI_INVOIC_HDR_NETTOTAL, …_TAXTOTAL und …_GROSSTOTAL werden beim Schreiben aus den Positionen neu berechnet. Den Positionsbetrag EDI_INVOIC_LINE_NETAMT rechnet die Bibliothek dagegen nicht aus — er muss gesetzt sein, sonst steht die Rechnung auf 0,00 €.
  • EdiOrdersWrite schlägt fehl ohne Bestellnummer: Rückgabewert -1 wenn EDI_ORDER_HDR_NUM nicht gesetzt. Immer prüfen.
  • QUOTES-Referenz: rfqRef ist Pflichtfeld — ohne Bezug auf die ursprüngliche REQOTE gibt EdiQuotesWrite -1 zurück.
  • Prüf-Funktionen melden Fehler, nicht Erfolg: EdiOrdersValidate, EdiInvoicValidate, EdiRemadvCheck, EdiDirdebCheck, EdiPaymulValidate, EdiDelforCheckCumu, EdiIftstaStatusCheck und EdiIftfccCheck liefern 0, wenn alles stimmt. Die GS1-/Bank-Validatoren (EdiGtinCheck, EdiIbanCheck, …) machen es umgekehrt: 1 = gültig.
  • Umschlag kommt aus std.edi.core: Die Fachunits schreiben nur den Nachrichtenrumpf (BGM, DTM, LIN …). UNA/UNB/UNH davor und UNT/UNZ dahinter erzeugt der Aufrufer — Ausnahme ist CONTRL, das einen vollständigen Interchange liefert.
  • Zeiger statt Zahlen: msgRef, msgType und grpRef in EdiUnhWrite/EdiUntWrite/EdiUngWrite sowie orderRef/confirmNum bei ORDRSP sind Zeiger auf nullterminierten Text, obwohl sie als int64 deklariert sind. Eine echte Zahl wird als Adresse gelesen und stürzt ab.
  • Structs selbst nullen: Die Write-Funktionen lesen jedes Feld. Nicht initialisierter Speicher liefert Zufallszeiger — EdiOrdersRead und Verwandte nullen dagegen ihre Kopf-Structs selbst.
  • 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-Puffer — die einzige Stelle in std.edi, an der die Bibliothek selbst Speicher anfordert. Danach free(peek64(result + EDI_GENRAL_BODY), peek64(result + EDI_GENRAL_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

Weiterführend: std.edi — Übersicht aller Units · std.edi.core · std.net

Letzte Aktualisierung: 2026-08-13 — sämtliche zwölf Codebeispiele durch lauffähige Programme ersetzt, übersetzt und ausgeführt (lyxc 1.0.21A), echte Ausgaben ergänzt. Dabei berichtigt: falsche Aufrufsignaturen (EdiUnaRead, EdiUnbRead, EdiSegCtxInit, EdiSegmentNext, EdiContrlOk/Err, EdiAperakWrite, EdiDuplicateCheck), erfundene Konstantennamen (EDI_TAX_*, EDI_MSGTYPE_*, EDI_PROTO_AS2, EDI_AS2_*, EDI_PART_OURLEN, EDI_INVOIC_HDR_ORDERREF, …_BUYER_LEN), die Ringpuffergröße der Duplikatserkennung (128 je Partner, nicht 256 gesamt) und EdiPartnerFind (liefert einen Zeiger, keinen Index).

Codebeispiele geprüft: gegen lyxc 1.2.5C übersetzt (Prüflauf 2026-09-08 über die gesamte Doku: 574 Vollprogramme, 0 echte Fehler; zusätzlich 5159 Aufrufe gegen die pub fn-Signaturen in aurum/std gehalten, 0 Abweichungen).