Inhaltsverzeichnis

std.edi — EDIFACT / B2B-Datenaustausch

14 Units für den elektronischen Datenaustausch in Handel, Logistik, Seefracht und Finanzwesen — vom Preiskatalog über Bestellung, Lieferschein und Rechnung bis zur Zoll- und Containermeldung. Alles baut auf std.edi.core auf; kein externes EDIFACT-Framework, kein fremder Parser.

Übersicht · Standard Library · Guide: EDI in der Praxis · Welche Unit?

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


Was ist EDIFACT?

UN/EDIFACT ist der internationale Standard für den Datenaustausch zwischen Unternehmen. Eine Nachricht ist reiner Text aus Segmenten — ein Drei-Buchstaben-Tag, dann Datenelemente, abgeschlossen mit dem Segmentzeichen:

UNA:+.? 'UNB+UNOA:1+4012345000009+4098765000004+20260813:0930+IC000042'UNH+1+ORDERS:D:01B:UN'...'UNT+2+1'UNZ+1+IC000042'

Die Hierarchie ist dreistufig — Umschlag, optionale Gruppe, Nachricht:

Ebene Öffnet Schließt Inhalt
Interchange (Umschlag) UNB UNZ Absender, Empfänger, Datum, Interchange-Referenz
Gruppe (optional) UNG UNE fasst gleichartige Nachrichten zusammen
Nachricht UNH UNT eine Geschäftsnachricht, z. B. ORDERS

Vorangestellt werden darf ein UNA-Segment: neun Bytes, die die Trennzeichen bekanntgeben. Fehlt es, gelten die UNOA-Standardwerte.

Rolle Zeichen Code Konstante
Komponententrenner : 58 EDI_DEF_COMP
Datenelementtrenner + 43 EDI_DEF_DATA
Dezimalzeichen . 46 EDI_DEF_DEC
Escape (Release) ? 63 EDI_DEF_REL
Segmentende ' 39 EDI_DEF_SEG

Zeichensätze: EDI_CHARSET_UNOA (A–Z, 0–9, wenige Sonderzeichen — Vorgabe), EDI_CHARSET_UNOB (ISO-8859-1), EDI_CHARSET_UNOC (UTF-8).


Units

Unit Nachrichten Beschreibung
std.edi.core UNA/UNB/UNZ/UNG/UNE/UNH/UNT Umschlag, Segment-Tokenizer, Escape-Behandlung, Sequenznummern, Duplikatserkennung
std.edi.messages CONTRL, APERAK, GENRAL Quittung, Fehlermeldung, freie Nachricht
std.edi.catalog PRICAT, PARTIN Preiskatalog, Partnerstammdaten; GTIN-/GLN-Prüfziffern
std.edi.partner AS2/SFTP-Profile Partner-Registry mit Nachrichtentyp-Bitmaske
std.edi.rfq REQOTE, QUOTES Preisanfrage und Angebot
std.edi.orders ORDERS, ORDRSP, ORDCHG Bestellung, Auftragsbestätigung, Bestelländerung
std.edi.invoice INVOIC, INVCON, COMDIS, COACSU Rechnung, Rechnungsbestätigung, Disput, Saldenabstimmung
std.edi.shipping DESADV, RECADV Lieferschein mit SSCC-Hierarchie, Wareneingang
std.edi.payment REMADV, PAYMUL, DIRDEB u. a. (16 Typen) Zahlungsverkehr; IBAN-/BIC-Prüfung
std.edi.inventory INVRPT, SLSRPT, SLSFCT Lagerbestand, Absatzbericht, Absatzprognose
std.edi.delfor DELFOR, DELJIT Lieferabruf und JIT-Feinabruf
std.edi.customs CUSCAR, CUSDEC, CUSEXP, CUSRSP Zoll: Frachtanmeldung, Zollanmeldung, Behördenantwort
std.edi.seafreight BAPLIE, MOVINS, COPARN, CODECO, COARRI, COPINO, COPRAR, VERMAS, HANMOV Seefracht: Stauplan, Containermeldungen, VGM (SOLAS)
std.edi.transport IFTMIN, IFTMBC, IFTMCS, IFTMAN, IFCSUM, IFTSTA, IFTFCC, IFTDGN Multimodaler Transport: Auftrag, Status, Gefahrgut

Jede Fachunit zieht std.edi.core mit — import std.edi.orders; genügt.


Designprinzipien


Schnellstart — Interchange schreiben und wieder einlesen

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

// Zero-Copy-Felder sind nicht nullterminiert: terminierte Kopie fuer die Ausgabe.
fn Feld(ptr: int64, len: int64): pchar {
    var b: int64 := alloc(len + 1);
    var i: int64 := 0;
    while (i < len) { poke8(b + i, peek8(ptr + i)); i := i + 1; }
    poke8(b + len, 0);
    return b as pchar;
}

fn main(): int64 {
    var ctx: int64 := alloc(EDI_CTX_SIZE);
    EdiCtxInit(ctx);
    poke64(ctx + EDI_CTX_SENDER, "4012345000009"c as int64);
    poke64(ctx + EDI_CTX_SNDLEN, 13);
    poke64(ctx + EDI_CTX_RCVR,   "4098765000004"c as int64);
    poke64(ctx + EDI_CTX_RCVLEN, 13);
    poke64(ctx + EDI_CTX_ICREF,  "IC000042"c as int64);
    poke64(ctx + EDI_CTX_ICREFLEN, 8);
    poke64(ctx + EDI_CTX_DATE,   "20260813"c as int64);
    poke64(ctx + EDI_CTX_TIME,   "0930"c as int64);

    var out: int64 := alloc(4096);
    var pos: int64 := 0;
    pos += EdiUnaWrite(ctx, out + pos, 4096 - pos);
    pos += EdiUnbWrite(ctx, out + pos, 4096 - pos);
    pos += EdiUnhWrite(ctx, "1"c, "ORDERS"c, out + pos, 4096 - pos);
    // ... hier stehen die Fachsegmente, z. B. aus EdiOrdersWrite ...
    pos += EdiUntWrite(ctx, 2, "1"c, out + pos, 4096 - pos);
    pos += EdiUnzWrite(ctx, 1, out + pos, 4096 - pos);
    PrintLn(Feld(out, pos));

    // Wieder einlesen: UNA gibt die Trennzeichen vor
    var una: int64 := alloc(EDI_UNA_SIZE);
    var start: int64 := 0;
    if (EdiUnaRead(out, pos, una) == EDI_OK) { start := 9; }

    var sctx: int64 := alloc(EDI_SEGCTX_SIZE);
    EdiSegCtxInit(sctx, una, start);
    var seg: int64 := alloc(EDI_SEG_SIZE);

    // EdiUnbRead erwartet GENAU ein Segment - erst mit dem Iterator holen.
    var ctx2: int64 := alloc(EDI_CTX_SIZE);
    EdiCtxInit(ctx2);
    if (EdiSegmentNext(out, pos, sctx, seg) == 1) {
        EdiUnbRead(peek64(seg + EDI_SEG_PTR), peek64(seg + EDI_SEG_LEN), ctx2);
    }
    PrintLn(StrConcat("Absender: ", Feld(peek64(ctx2 + EDI_CTX_SENDER), peek64(ctx2 + EDI_CTX_SNDLEN))));
    PrintLn(StrConcat("ICR:      ", Feld(peek64(ctx2 + EDI_CTX_ICREF), peek64(ctx2 + EDI_CTX_ICREFLEN))));

    var n: int64 := 1;
    while (EdiSegmentNext(out, pos, sctx, seg) == 1) {
        n += 1;
        PrintLn(StrConcat("  Segment: ", Feld(peek64(seg + EDI_SEG_PTR), 3)));
    }
    PrintLn(StrConcat("Segmente: ", IntToStr(n)));
    return 0;
}

UNA:+.? 'UNB+UNOA:1+4012345000009+4098765000004+20260813:0930+IC000042'UNH+1+ORDERS:D:01B:UN'UNT+2+1'UNZ+1+IC000042'
Absender: 4012345000009
ICR:      IC000042
  Segment: UNH
  Segment: UNT
  Segment: UNZ
Segmente: 4

Die Fachunits erzeugen nur den Nachrichtenrumpf (BGM, DTM, NAD, LIN …). Der Umschlag kommt immer aus std.edi.core: UNA + UNB + UNH davor, UNT + UNZ dahinter.


Prüfziffern über alle Units

import std.edi.catalog;
import std.edi.shipping;
import std.edi.payment;
import std.edi.seafreight;
import std.edi.transport;
import std.string;
import std.alloc;

fn J(v: int64): pchar { if (v == 1) { return "gueltig"; } return "ungueltig"; }

fn main(): int64 {
    PrintLn(StrConcat("GTIN-13  4012345678901: ", J(EdiGtinCheck("4012345678901"c, 13))));
    PrintLn(StrConcat("GLN      4012345000009: ", J(EdiGlnCheck("4012345000009"c, 13))));
    var sscc: int64 := alloc(19);
    EdiSsccGenerate("34012345000000001"c, sscc);
    PrintLn(StrConcat(StrConcat("SSCC-18  ", sscc as pchar), StrConcat(": ", J(EdiSsccCheck(sscc as pchar, 18)))));
    PrintLn(StrConcat("IBAN     DE89370400440532013000: ", J(EdiIbanCheck("DE89370400440532013000"c, 22))));
    PrintLn(StrConcat("BIC      COBADEFFXXX: ", J(EdiBicCheck("COBADEFFXXX"c, 11))));
    PrintLn(StrConcat("Container MSKU1234565: ", J(EdiContainerCheck("MSKU1234565"c, 11))));
    PrintLn(StrConcat("UN-Nummer 1203: ", J(EdiUnNumberCheck("1203"c, 4))));
    return 0;
}

GTIN-13  4012345678901: gueltig
GLN      4012345000009: gueltig
SSCC-18  340123450000000017: gueltig
IBAN     DE89370400440532013000: gueltig
BIC      COBADEFFXXX: gueltig
Container MSKU1234565: gueltig
UN-Nummer 1203: gueltig

Prüfung Unit Länge Verfahren
EdiGtinCheck catalog 8, 12, 13 oder 14 GS1 Mod-10
EdiGlnCheck catalog genau 13 GS1 Mod-10
EdiSsccCheck / EdiSsccGenerate shipping 18 (Generator: 17 → 18) GS1 Mod-10
EdiIbanCheck payment ≥ 4 ISO 13616, MOD-97
EdiBicCheck payment 8 oder 11 Formatprüfung ISO 9362
EdiContainerCheck seafreight 11 ISO 6346
EdiUnNumberCheck transport genau 4 UN-Gefahrgutnummer

Alle liefern 1 = gültig, 0 = ungültig. Keine Write-Funktion prüft von sich aus — Prüfziffern sind Sache des Aufrufers.


Fehlercodes

Konstante Wert Bedeutung
EDI_OK 0 erfolgreich
EDI_ERR_TRUNC 1 Nachricht/Puffer abgeschnitten
EDI_ERR_NOUNA 2 kein UNA-Segment — Standardtrennzeichen gelten
EDI_ERR_SYNTAX 3 Segment nicht lesbar
EDI_ERR_DUP 4 Interchange-Referenz schon gesehen
EDI_ERR_OVERFLOW 5 Zielpuffer zu klein / Partner-Slots voll
EDI_ERR_PARTNER 6 Partner nicht gefunden

EDI_ERR_NOUNA ist kein Fehler im engeren Sinn: fehlt UNA, füllt EdiUnaRead die Standardtrennzeichen ein und die Verarbeitung läuft weiter — nur der Startversatz ist dann 0 statt 9.

Die …Write-Funktionen melden Überlauf mit -1; ihr Rückgabewert ist sonst die Zahl geschriebener Bytes.


Typische Nachrichtenflüsse

Beschaffung

Käufer                              Lieferant
  REQOTE (Anfrage)      →
                        ←   QUOTES (Angebot)
  ORDERS (Bestellung)   →
                        ←   ORDRSP (Bestaetigung)
  ORDCHG (Aenderung)    →
                        ←   DESADV (Lieferavis, SSCC)
  RECADV (Wareneingang) →
                        ←   INVOIC (Rechnung)
  REMADV (Zahlungsavis) →

Seefracht-Container

Verlader                            Terminal / Carrier
  COPARN (Anmeldung)    →
                        ←   CODECO (Gate-In / Gate-Out)
  VERMAS (VGM, SOLAS)   →
                        ←   BAPLIE (Stauplan)
                        ←   COARRI (Ankunft)
                        ←   COPINO (Abholfreigabe)

Quittungen — auf jede eingehende Nachricht antwortet CONTRL (Syntaxquittung) oder APERAK (fachliche Fehlermeldung), siehe std.edi.messages.


Fallstricke


Weiterführend: Guide: EDI in der Praxis · std.edi.core · std.net · Rohspeicher

Quelldateien

Alle Units liegen unter std/edi/: core.lyx, messages.lyx, catalog.lyx, partner.lyx, rfq.lyx, orders.lyx, invoice.lyx, shipping.lyx, payment.lyx, inventory.lyx, delfor.lyx, customs.lyx, seafreight.lyx, transport.lyx.

Letzte Aktualisierung: 2026-08-13 — Trennzeichen-Tabelle repariert (war eine einspaltige Liste ohne Kopf), Schnellstart durch ein lauffähiges Programm ersetzt (die alten Aufrufe hatten falsche Signaturen), Umschlag-Hierarchie, Prüfziffer-Übersicht, Fehlercodes und Fallstricke ergänzt. Geprüft gegen std/edi/*.lyx und lyxc 1.0.17K.

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).