====== EDI/EDIFACT-Guide — Elektronischer Datenaustausch mit Lyx ====== Dieser Guide erklärt den elektronischen B2B-Datenaustausch mit der Lyx-EDI-Bibliothek: von den Grundbegriffen über den Nachrichtenaufbau bis zu vollständigen Workflows für Bestellungen, Rechnungen, Lieferscheine und Zahlungsabwicklung. → [[lyx_-_programmiersprache:units:edi|std.edi — Übersicht]] · [[lyx_-_programmiersprache:units:edi:core|std.edi.core]] · [[lyx_-_programmiersprache:guides:welche-unit|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: ''ORDERS'' → ''EdiOrdersRead'', ''INVOIC'' → ''EdiInvoicRead'' 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): - Alle Ziffern außer Prüfziffer: gerade Positionen × 1, ungerade × 3 (von rechts) - Summe bilden - Prüfziffer = (10 − (Summe mod 10)) mod 10 import std.edi.catalog; import std.edi.shipping; 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:** [[lyx_-_programmiersprache:units:edi|std.edi — Übersicht aller Units]] · [[lyx_-_programmiersprache:units:edi:core|std.edi.core]] · [[lyx_-_programmiersprache:units:net|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).