/** * INVRPT D13A → VDA 4913 EDL36/35 Converter * * Converts EDIFACT INVRPT (Inventory Report) messages into * VDA 4913 format with EDL36 (delivery note) and EDL35 (stock) records. * * Record structure produced: * 711 Header * 712 Transport (one per INVRPT message) * 713 Delivery note header (EDL36 or EDL35) * 714 Position (material / qty) * 719 Trailer */ window.EDIBridge = window.EDIBridge || {}; class InvrptToVDA4913 { // ─── Public API ────────────────────────────────────────────────── static async convertAsync(edifactContent, config) { let parsed; // Try rigorous parsing via ts-edifact if available if (window.electronAPI && window.electronAPI.parseEdifactLib) { try { const libResult = await window.electronAPI.parseEdifactLib(edifactContent); if (libResult.success && libResult.data) { parsed = { segments: libResult.data }; } } catch (e) { console.warn('ts-edifact parsing fall-back to basic parser:', e); } } // Fallback to basic parser if (!parsed) { const parser = window.EDIBridge.DelforParser; if (!parser) throw new Error('DelforParser nicht geladen.'); parsed = parser.parse(edifactContent); } return this.processParsedData(parsed.segments, config); } // For backwards compatibility if called synchronously without await static convert(edifactContent, config) { const parser = window.EDIBridge.DelforParser; if (!parser) throw new Error('DelforParser nicht geladen.'); const parsed = parser.parse(edifactContent); return this.processParsedData(parsed.segments, config); } static processParsedData(segments, config) { const getVal = (seg, elIdx, compIdx) => { if (!seg || !seg.elements) return ''; const el = seg.elements[elIdx]; if (!el) return ''; if (Array.isArray(el)) return el[compIdx] || ''; if (compIdx === 0) return el; if (elIdx === 0 && compIdx === 1) return seg.elements[1] || ''; return ''; }; const groups = this.splitMessages(segments, getVal); const firstGroup = groups[0] || []; const nadWH = firstGroup.find(s => s.tag === 'NAD' && getVal(s, 0, 0) === 'WH'); const nadGM = firstGroup.find(s => s.tag === 'NAD' && getVal(s, 0, 0) === 'GM'); const dtm137 = firstGroup.find(s => s.tag === 'DTM' && getVal(s, 0, 0) === '137'); const targetId = nadWH ? (getVal(nadWH, 1, 0) || getVal(nadWH, 0, 1)) : ''; const sourceId = nadGM ? (getVal(nadGM, 1, 0) || getVal(nadGM, 0, 1)) : ''; const msgDateRaw = dtm137 ? getVal(dtm137, 0, 1) : ''; const msgDate = msgDateRaw.length === 8 ? msgDateRaw.slice(2) : (msgDateRaw || '000000'); const unz = segments.find(s => s.tag === 'UNZ'); const unzRef = unz ? getVal(unz, 1, 0) : '00001'; const transNo1 = unzRef; const transNo2 = String((parseInt(unzRef) || 0) + 1); const records = []; records.push(this.build711(targetId, sourceId, transNo1, transNo2, msgDate, config)); let totalRecords712 = 0; let totalRecords713 = 0; let totalRecords714 = 0; for (const group of groups) { const bgm = group.find(s => s.tag === 'BGM'); const bgmType = bgm ? getVal(bgm, 0, 0) : ''; // We now process BGM+35 (Inventory Report) and BGM+78 (Inventory Movement) // Re-group into LIN blocks let currentLin = null; let currentInv = null; let linBlocks = []; for (const seg of group) { if (seg.tag === 'LIN') { currentLin = { seg, invs: [] }; linBlocks.push(currentLin); currentInv = null; } else if (seg.tag === 'INV' && currentLin) { currentInv = { seg, details: [] }; currentLin.invs.push(currentInv); } else if (currentInv) { currentInv.details.push(seg); } else if (currentLin) { currentLin.invs.push({ seg: null, details: [seg] }); // segments under LIN but before INV } } for (const linBlock of linBlocks) { const lin = linBlock.seg; const material = getVal(lin, 2, 0); for (const invBlock of linBlock.invs) { const inv = invBlock.seg; const details = invBlock.details; const qty156 = details.find(s => s.tag === 'QTY' && getVal(s, 0, 0) === '156'); const qty145 = details.find(s => s.tag === 'QTY' && getVal(s, 0, 0) === '145'); const qtyOnHand = qty156 ? (parseFloat(getVal(qty156, 0, 1)) || 0) : 0; const qtyCumulative = qty145 ? (parseFloat(getVal(qty145, 0, 1)) || 0) : 0; let unitObj = qty156 || qty145; const unit = unitObj ? (getVal(unitObj, 0, 2) || 'PCE') : 'PCE'; const vdaUnit = (unit === 'PCE' || unit === 'C62' || unit === 'EA') ? 'ST' : (unit === 'KGM' ? 'KG' : (unit === 'MTR' ? 'M ' : 'ST')); const dtm179 = details.find(s => s.tag === 'DTM' && getVal(s, 0, 0) === '179'); const invDateRaw = dtm179 ? getVal(dtm179, 0, 1) : msgDateRaw; const invDate = invDateRaw.length === 8 ? invDateRaw.slice(2) : (invDateRaw || '000000'); // Global or detail refs const rffDQ = details.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'DQ') || group.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'DQ'); const rffTN = details.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'TN') || group.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'TN'); const rffAAU = details.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'AAU') || group.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'AAU'); const rffON = details.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'ON') || group.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'ON'); const dqNo = rffDQ ? getVal(rffDQ, 0, 1) : ''; const tnNo = rffTN ? getVal(rffTN, 0, 1) : ''; const aauNo = rffAAU ? getVal(rffAAU, 0, 1) : ''; const onNo = rffON ? getVal(rffON, 0, 1) : ''; // Lieferschein-Nr (current reference) const currentDn = aauNo || tnNo || dqNo; const groupLoc11 = group.find(s => s.tag === 'LOC' && getVal(s, 0, 0) === '11'); const unloadingPoint = groupLoc11 ? (getVal(groupLoc11, 1, 0) || '').replace(/^W/, '') : ''; // Get specific storage location for this stock const loc18 = details.find(s => s.tag === 'LOC' && getVal(s, 0, 0) === '18'); const storageLocation = loc18 ? getVal(loc18, 1, 0) : ''; // EDL36 (Movements) logic if (qty156 || currentDn) { // Determine transaction key (Vorgangsschlüssel) based on INV and QTY // Default to 36 (Abgangsmeldung) let vdaKey = '36'; if (inv) { const direction = getVal(inv, 0, 0); // DE 4501 const reason = getVal(inv, 2, 0); // DE 4499 if (direction === '2' || reason === '1') { vdaKey = '30'; // Eingangsmeldung } else if (reason === '4' || reason === '3') { vdaKey = '32'; // Differenz-/Verlustmeldung } else if (reason === '2' || reason === '11') { vdaKey = '36'; // Abgangsmeldung } } records.push(this.build712(msgDate)); totalRecords712++; records.push(this.build713(currentDn || '00000000', invDate, unloadingPoint, vdaKey, targetId, onNo)); totalRecords713++; records.push(this.build714(material, qtyOnHand, vdaUnit, dqNo || currentDn)); totalRecords714++; } // EDL35 (Stock balances) generated for QTY+145 if (qty145) { records.push(this.build712(msgDate)); totalRecords712++; // Stock reports use Vorgangsschlüssel 35 records.push(this.build713('00000000', invDate, storageLocation || unloadingPoint, '35', targetId, onNo)); totalRecords713++; records.push(this.build714(material, qtyCumulative, vdaUnit, dqNo)); totalRecords714++; } } } } records.push(this.build719( totalRecords712, totalRecords713, totalRecords714, totalRecords712, totalRecords713, totalRecords714 )); return records.join('\r\n') + '\r\n'; } // ─── Split EDIFACT segments into UNH message groups ────────────── static splitMessages(segments, getVal) { const groups = []; let current = null; for (const seg of segments) { if (seg.tag === 'UNH') { if (current) groups.push(current); current = [seg]; } else if (seg.tag === 'UNT') { if (current) { current.push(seg); groups.push(current); current = null; } } else if (current) { current.push(seg); } } if (current) groups.push(current); return groups; } // ─── Format Helpers ────────────────────────────────────────────── static padN(val, len) { let v = String(val || '').replace(/[^0-9]/g, ''); return v.padStart(len, '0').slice(-len); } static padA(val, len) { return String(val || '').padEnd(len, ' ').slice(0, len); } static padID(val, len) { let v = String(val || '').replace(/[^0-9]/g, ''); // Prefer the front of the ID if it exceeds VDA limits return v.padEnd(len, ' ').slice(0, len).trim().padStart(len, '0'); } // ─── Record Builders ───────────────────────────────────────────── /** * 711 - Vorsatz Lieferschein- und Transportdaten * Version 03 * Pos 01: 1-3 (3) Konstant "711" * Pos 02: 4-5 (2) Version "03" * Pos 03: 6-14 (9) Daten-Empfänger-Nummer * Pos 04: 15-23 (9) Daten-Sender-Nummer * Pos 05: 24-28 (5) Übertragungs-Nummer-Alt * Pos 06: 29-33 (5) Übertragungs-Nummer-Neu * Pos 07: 34-39 (6) Übertragungsdatum (JJMMTT) */ static build711(targetId, sourceId, transNo1, transNo2, date, config) { let r = '71102'; // Custom override for targetId (e.g. "10305") via Mapping or Setting let target = targetId; if (config && window.EDIBridge.ConfigParser) { const parser = window.EDIBridge.ConfigParser; // 1. Try specific mapping (e.g. 8810 -> 10305) const mapped = parser.findInSection(config, 'VDA711_ID_MAPPING', targetId); if (mapped) { target = mapped; } else { // 2. Fallback to global setting if no specific mapping exists target = parser.lookupGeneral(config, 'OWN_CUSTOMER_NUMBER', targetId); } } r += this.padA(target, 9); // Daten-Empfänger r += this.padA(sourceId, 9); // Daten-Sender r += this.padN(transNo1, 5); r += this.padN(transNo2, 5); r += this.padN(date, 6); r += ''.padEnd(128 - r.length, ' '); return r.slice(0, 128); } /** * 712 - Einmalige Daten des Transports * Version 03 * Pos 01: 1-3 (3) Konstant "712" * Pos 02: 4-5 (2) Version "03" * Pos 03: 6-13 (8) Sendungs-Ladungs-Bezugs-Nummer * Pos 04: 14-16 (3) Werk Lieferant * Pos 05: 17-30 (14) Frachtführer Name/Nummer * Pos 06: 31-36 (6) Übergabedatum (JJMMTT) * Pos 07: 37-40 (4) Übergabezeit (HHMM) * Pos 08: 41-47 (7) Sendungs-Gewicht Brutto (kg) * Pos 09: 48-54 (7) Sendungs-Gewicht Netto (kg) */ static build712(date) { let r = '71202'; r += this.padN('0', 8); // Sendungs-Ladungs-Bezug-Nr r += this.padA('', 3); // Werk Lieferant r += this.padA('', 14); // Frachtführer r += this.padN(date, 6); // Datum r += this.padN('0', 4); // Zeit r += this.padN('0', 7); // Brutto r += this.padN('0', 7); // Netto r += ''.padEnd(128 - r.length, ' '); return r.slice(0, 128); } /** * 713 - Einmalige Daten des Lieferscheins (Kopf) * Version 03 * Pos 01: 1-3 (3) Konstant "713" * Pos 02: 4-5 (2) Version "03" * Pos 03: 6-13 (8) Lieferschein-Nummer * Pos 04: 14-19 (6) Versanddatum (JJMMTT) * Pos 05: 20-24 (5) Abladestelle * Pos 06: 25-26 (2) Versandart * Pos 08: 31-42 (12) Bestellnummer * Pos 09: 43-44 (2) Vorgangs-Schlüssel (EDL) * Pos 11: 49-51 (3) Werk Kunde * Pos 16: 77-85 (9) Lieferanten-Nummer */ static build713(dnNo, date, unloadingPoint, edlType, targetId, orderNo) { let r = '713'; // User requested shifting DN left by 2 chars (occupying the version field area to allow 10 digits) r += this.padID(dnNo, 10); // Lieferschein-Nr (4-13) r += this.padN(date, 6); // Datum (14-19) r += this.padA(unloadingPoint, 5); // Abladestelle (20-24) r += '00'; // Versandart (25-26) r += ' '; // Filler (27-30) r += this.padA(orderNo, 12); // Bestellnummer (31-42) const validKeys = ['30', '32', '33', '35', '36', '40']; r += this.padN(validKeys.includes(edlType) ? edlType : '00', 2); // Vorgangsschlüssel (43-44) r += ' '; // Filler (45-48) r += this.padA(targetId.slice(0, 3), 3); // Werk Kunde (49-51) r += ''.padEnd(25, ' '); // Filler to index 76 (52-76) r += this.padA(targetId, 9); // Lieferanten-Nummer (Pos 16, index 76-84) r += ''.padEnd(25, ' '); // Filler to index 110 (85-109) r += this.padA(dnNo, 14); // Dokument-Nr. Kunde (Pos 20, index 110-123) r += ''.padEnd(4, ' '); // Filler to 128 return r.slice(0, 128); } /** * 714 - Lieferscheinpositionsdaten * Version 03 * Pos 01: 1-3 (3) Konstant "714" * Pos 02: 4-5 (2) Version "03" * Pos 03: 6-27 (22) Sachnummer-Kunde * Pos 04: 28-49 (22) Sachnummer-Lieferant * Pos 05: 50-52 (3) Ursprungsland * Pos 06: 53-65 (13) Liefermenge (mit 3 impliziten Dezimalstellen, VDA Standard) * Pos 07: 66-67 (2) Mengeneinheit * Pos 12: 87-89 (3) Positions-Nummer Lieferschein * Pos 14: 91-105 (15) Chargen-Nummer * Pos 22: 121-128 (8) Ursprung-Lieferschein-Nr */ static build714(custMat, qty, unit, refNo) { const qtyEncoded = Math.round(qty * 1000); let r = '71403'; r += this.padA(custMat, 22); // Sachnummer-Kunde r += this.padA('', 22); // Sachnummer-Lieferant r += '000'; // Ursprungsland r += this.padN(String(qtyEncoded), 13); // Liefermenge (13 Stellen) r += this.padA(unit, 2); // ME r += ''.padEnd(19, ' '); // Filler to 86 r += this.padN('0', 3); // Pos-Nr (87-89) r += ' '; // Filler r += this.padA('', 15); // Charge (91-105) r += ' '; // Filler to 120 r += this.padID(refNo, 8); // Ursprung-LS (121-128) return r.slice(0, 128); } /** * 719 - Nachsatz Lieferschein- und Transportdaten * Version 02 */ static build719(cnt712, cnt713, cnt714, tot712, tot713, tot714) { let r = '71902'; r += this.padN('0', 8); r += this.padN(String(1), 7); // Zähler 711 r += this.padN(String(cnt712), 7); r += this.padN(String(cnt713), 7); r += this.padN(String(cnt714), 7); r += this.padN('0', 7); // Zähler 715 r += this.padN('0', 7); // Zähler 716 r += this.padN('0', 7); // Zähler 718 r += this.padN(String(1), 7); // Zähler 719 r += this.padN('0', 7); // Zähler 717 r += ''.padEnd(128 - r.length, ' '); return r.slice(0, 128); } } window.EDIBridge.InvrptToVDA4913 = InvrptToVDA4913;