====== 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:guides:welche-unit|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): - 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; // 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 ===== * **Festkomma-Beträge**: Alle Geldbeträge als ''int64 × 100''. ''1999'' = 19,99 €. Niemals ''f64'' für Geldrechnung — Rundungsfehler! * **EdiInvoicWrite berechnet Summen selbst**: Die Felder ''TOTAL_NET'', ''TOTAL_TAX'', ''TOTAL_GROSS'' im INVOIC-Header werden von der Write-Funktion aus den Positionen neu berechnet. Vorab gesetzte Werte werden überschrieben. * **EdiOrdersWrite schlägt fehl ohne Bestellnummer**: Rückgabewert -1 wenn ''EDI_ORDER_HDR_NUM'' nicht gesetzt. Immer prüfen. * **ORDRSP-Referenz**: Bei QUOTES (Angebote) ist ''rfqRef'' Pflichtfeld — ohne Bezug auf die ursprüngliche REQOTE gibt EdiQuotesWrite -1 zurück. * **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-Buffer. Nach der Verarbeitung ''free(result.BODY, result.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'' | ---- Letzte Aktualisierung: 2026-06-16