/** * VDA 4913 → DELVRY03 (XML-IDoc) Converter * Based on: ifm electronic EDI Guide XML-DELVRY03 v1.0 (30.06.2020) * Input: Parsed VDA 4913 object from VDAParser (vda-parser.js) * Output: EDIFACT-style IDoc field map object ready for XML serialisation * OR a flat key=value string for direct IDoc inbound processing. * * Segment mapping: * VDA 711 → EDI_DC40 + E1EDL20 (delivery header) * VDA 712 → E1EDL20 transport fields (carrier, weight) * VDA 713 → E1EDL20 delivery note fields + E1ADRM1 (ship-to) * VDA 714 → E1EDL24 (delivery item) * VDA 715 → E1EDL37 (handling unit) */ window.EDIBridge = window.EDIBridge || {}; class VDA4913ToDELVRY03 { // ── Unit mapping VDA → SAP/IDoc ────────────────────────────────────────── static UNIT_MAP = { 'ST': 'PCE', 'PCE': 'PCE', 'KG': 'KG', 'KGM': 'KG', 'L': 'L', 'MTR': 'M', 'M': 'M', 'PAL': 'PAL', 'CTN': 'CTN', }; // ── Transport type mapping VDA → SAP TRAGY ──────────────────────────────── static TRANSPORT_MAP = { '1': '01', // Sea '2': '02', // Rail '3': '03', // Road '4': '04', // Air '5': '05', // Post }; // ───────────────────────────────────────────────────────────────────────── // Main entry point // @param {object} parsed – result of VDAParser.parse() // @param {object} config – { senderId, receiverId, plant, salesOrg, ... } // @returns {object} – IDoc structure { control: {}, segments: [] } // ───────────────────────────────────────────────────────────────────────── static convert(parsed, config = {}) { const idocs = []; for (const interchange of (parsed.interchanges || [])) { const hdr711 = interchange.header || {}; for (const transport of (interchange.transports || [])) { const hdr712 = transport.data || {}; for (const dn of (transport.deliveryNotes || [])) { const hdr713 = dn.header || {}; idocs.push(this._buildIDoc(hdr711, hdr712, hdr713, dn, config)); } } } return idocs; } // ───────────────────────────────────────────────────────────────────────── // Build one IDoc per delivery note // ───────────────────────────────────────────────────────────────────────── static _buildIDoc(hdr711, hdr712, hdr713, dn, config) { const now = new Date(); const dateNow = this._fmtDate(now); const timeNow = this._fmtTime(now); const senderId = config.senderId || hdr711.sourceId || 'SUPPLIER'; const receiverId = config.receiverId || hdr711.targetId || 'IFM'; const docNum = hdr713.dnNo || hdr712.refNo || ''; const delivDate = this._vdaDate(hdr713.date || hdr712.date || dateNow); // ── EDI_DC40 – Control Record ───────────────────────────────────────── const control = { TABNAM: 'EDI_DC40', MANDT: config.client || '600', DOCNUM: docNum.padStart(16, '0'), DOCREL: config.docrel || '750', STATUS: '30', DIRECT: '1', OUTMOD: '2', IDOCTYP: 'DELVRY03', MESTYP: 'STPOD', STD: 'E', STDVRS: '003', STDMES: 'DELVRY', SNDPOR: config.sndPort || 'EDIPORT', SNDPRT: 'LS', SNDPRN: senderId.substring(0, 10), SNDLAD: senderId.substring(0, 21), RCVPOR: config.rcvPort || 'SAPPORT', RCVPRT: 'LI', RCVPRN: receiverId.substring(0, 10), RCVLAD: '1000280', CREDAT: dateNow, CRETIM: timeNow, }; const segments = []; // ── E1EDL20 – Delivery Header ───────────────────────────────────────── // VDA 712: carrier (16-30), date (30-36), time (36-40) // VDA 713: dnNo (5-13), date (13-19), unloadingPoint (19-24) const e1edl20 = { _SEGMENT: 'E1EDL20', VBELN: docNum.padStart(10, '0'), // Delivery note number VSTEL: hdr713.unloadingPoint || config.shippingPoint || '', // Shipping point VKORG: config.salesOrg || '', LGNUM: config.warehouseNo || '', ABLAD: hdr713.unloadingPoint || '', // Unloading point BTGEW: String(hdr712.grossWeight || ''), // Total weight NTGEW: String(hdr712.netWeight || ''), // Net weight GEWEI: 'KG', BOLNR: hdr712.refNo || '', // Bill of lading TRATY: this.TRANSPORT_MAP[hdr712.transportType] || '', TRAID: hdr712.carrierName || '', // Carrier name / transport ID LIFEX: docNum, // External delivery note no. PARID: senderId, // External partner number PODAT: this._vdaDate(hdr713.date || hdr712.date || ''), // Proof of delivery date POTIM: hdr712.time || '', }; segments.push(e1edl20); // ── E1ADRM1 – Ship-To Address (partner function WE) ─────────────────── // From VDA 713: qualifier (24-30), orderNo (30-38) const e1adrm1 = { _SEGMENT: 'E1ADRM1', PARTNER_Q: 'WE', ADDRESS_T: '1', PARTNER_ID: config.shipToId || hdr713.qualifier || '', NAME1: config.shipToName || 'ifm electronic gmbh', STREET1: config.shipToStreet || '', CITY1: config.shipToCity || '', POSTL_COD1: config.shipToZip || '', COUNTRY1: config.shipToCountry || 'DE', }; segments.push(e1adrm1); // ── E1ADRM1 – Supplier Address (partner function LF) ───────────────── const e1adrm1_lf = { _SEGMENT: 'E1ADRM1', PARTNER_Q: 'LF', ADDRESS_T: '1', PARTNER_ID: senderId, NAME1: config.supplierName || '', STREET1: config.supplierStreet || '', CITY1: config.supplierCity || '', POSTL_COD1: config.supplierZip || '', COUNTRY1: config.supplierCountry || 'DE', }; segments.push(e1adrm1_lf); // ── E1EDL24 – Delivery Items ────────────────────────────────────────── // VDA 714: custMat (5-27), suppMat (27-49), qty (53-65)/1000, unit (65-67), refNo (77-90) let posNr = 10; for (const pos of (dn.positions || [])) { const d = pos.data || {}; const unit = this.UNIT_MAP[d.unit] || d.unit || 'PCE'; const e1edl24 = { _SEGMENT: 'E1EDL24', POSNR: String(posNr).padStart(6, '0'), MATNR: d.suppMat || '', // Supplier material number MATWA: d.suppMat || '', // Material entered ARKTX: '', // Short text KDMAT: d.custMat || '', // Customer material number LGMNG: String(d.qty || 0), // Actual delivery qty VRKME: unit, // Sales unit MEINS: unit, // Base unit VGBEL: hdr713.orderNo || '', // Reference document (order) VGPOS: String(posNr).padStart(6, '0'), WERKS: config.plant || '1110', // Plant }; segments.push(e1edl24); // ── E1EDL37 – Handling Units (from VDA 715 packaging lines) ─────── // VDA 715: packMatCust (5-27), packMatSupp (27-49), qty (59-62), // labelFrom (78-87), labelTo (87-96) let huCounter = 1; for (const pack of (pos.packaging || [])) { const huId = String(huCounter).padStart(10, '0'); const e1edl37 = { _SEGMENT: 'E1EDL37', VPOSNR: String(posNr).padStart(6, '0'), VEGR1: pack.packMatSupp || pack.packMatCust || '', // HU type ANZPK: String(pack.qty || 1), // Number of packages EXIDV: pack.labelFrom || huId, // External HU ID (label from) EXIDV2: pack.labelTo || '', // Label to }; segments.push(e1edl37); huCounter++; } // ── E1EDL14 – DG Control Data (delivery item) – only if needed ─── // Skipped unless config.includeDG = true posNr += 10; } // ── Free texts from VDA 716 ─────────────────────────────────────────── if (dn.texts && dn.texts.length > 0) { const e1txth8 = { _SEGMENT: 'E1TXTH8', FUNCTION: 'H', TDOBJECT: 'VBBK', TDOBNAME: docNum, TDID: '0001', TDSPRAS: '1', TDTEXTTYPE: 'GRKO', LANGUA_ISO: 'DE', }; segments.push(e1txth8); for (const txt of dn.texts) { segments.push({ _SEGMENT: 'E1TXTP8', TDFORMAT: '*', TDLINE: txt.substring(0, 132), }); } } return { control, segments }; } // ───────────────────────────────────────────────────────────────────────── // Serialise IDoc array to flat IDoc text format (for file-based inbound) // ───────────────────────────────────────────────────────────────────────── static toIdocText(idocs) { const lines = []; let docNum = 1; for (const idoc of idocs) { const dn = String(docNum++).padStart(16, '0'); // Control record const ctrl = { ...idoc.control, DOCNUM: dn }; lines.push(this._flatRecord('EDI_DC40', ctrl)); // Data segments let segNum = 1; for (const seg of idoc.segments) { const segName = seg._SEGMENT; const fields = { ...seg }; delete fields._SEGMENT; lines.push(this._flatRecord(segName, fields, dn, segNum++)); } } return lines.join('\n'); } // ───────────────────────────────────────────────────────────────────────── // Serialise to structured JSON (for XML generation or further processing) // ───────────────────────────────────────────────────────────────────────── static toJSON(idocs) { return JSON.stringify(idocs, null, 2); } // ───────────────────────────────────────────────────────────────────────── // Serialise to XML-IDoc format (as expected by ifm DELVRY03 guide) // ───────────────────────────────────────────────────────────────────────── static toXML(idocs) { const xmlParts = ['', '']; for (const idoc of idocs) { xmlParts.push(' '); // Control record xmlParts.push(' '); for (const [k, v] of Object.entries(idoc.control)) { if (v !== undefined && v !== '') { xmlParts.push(` <${k}>${this._xmlEsc(v)}`); } } xmlParts.push(' '); // Data segments for (const seg of idoc.segments) { const name = seg._SEGMENT; xmlParts.push(` <${name} SEGMENT="1">`); for (const [k, v] of Object.entries(seg)) { if (k === '_SEGMENT') continue; if (v !== undefined && v !== '') { xmlParts.push(` <${k}>${this._xmlEsc(v)}`); } } xmlParts.push(` `); } xmlParts.push(' '); } xmlParts.push(''); return xmlParts.join('\n'); } // ───────────────────────────────────────────────────────────────────────── // Helpers // ───────────────────────────────────────────────────────────────────────── /** VDA date YYMMDD → YYYYMMDD */ static _vdaDate(d) { if (!d) return ''; d = String(d).replace(/\D/g, ''); if (d.length === 6) return '20' + d; // YYMMDD → YYYYMMDD if (d.length === 8) return d; // already YYYYMMDD return d; } /** Date object → YYYYMMDD */ static _fmtDate(dt) { return dt.toISOString().replace(/-/g, '').substring(0, 8); } /** Date object → HHMMSS */ static _fmtTime(dt) { return dt.toISOString().replace(/:/g, '').substring(11, 17); } /** Flat IDoc record line */ static _flatRecord(segName, fields, docNum = '', segNum = 0) { const parts = [segName.padEnd(30)]; if (docNum) parts.push(docNum.padStart(16, '0')); if (segNum) parts.push(String(segNum).padStart(6, '0')); for (const [k, v] of Object.entries(fields)) { parts.push(`${k}=${v}`); } return parts.join(' '); } /** XML escape */ static _xmlEsc(v) { return String(v) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } } window.EDIBridge.VDA4913ToDELVRY03 = VDA4913ToDELVRY03;