/** * VDA 4913 to DESADV Mapping Logic * Bosch-Compliant DESADV D.07A GMI022 * * Struktur gemaess Bosch PDF (global-desadv-d-07a_v19_iso9735): * * UNB * UNH+1+DESADV:D:07A:UN:GMI022 * BGM / DTM(3x) / MEA(2x) / RFF / NAD(3x) / LOC * * -- Pro Artikel-/Batch-Gruppe: CPS+N++1 (Karton-Ebene) -- * CPS+1++1 * PAC++++F::IN::SA * MEA(5x: G, AAL, LN, WD, HT) * QTY+52::PCE * PCI+17+++S::5 * RFF+ACI: (Referenz auf Aussenverpackung) * GIR+7+1:AM * GIN+ML+ * GIN+ML+ * ... * LIN+1++:IN * PIA+1+:SA * QTY+12:: * DTM+36::102 * DTM+94::102 * NAD+MF+ * RFF+ON::001 * LOC+11+ * LOC+18+ * * -- Fuer jede weitere Palette: CPS+N++1 (neue Karton-Gruppe) -- * CPS+2++1 * PAC / MEA / QTY / PCI / RFF / GIR / GIN+ML / LIN / ... * * -- Einmalig am Ende: CPS+N++3 (Palette-Ebene) -- * CPS+++3 * PAC++++F:Palette:IN::SA * MEA(5x) * QTY+189::PCE * -- Pro Palette: PCI+17+++M::5 + GIN+ML+ + GIN+AW+... -- * PCI+17+++M::5 * GIN+ML+ * GIN+AW+ * GIN+AW+ * PCI+17+++M::5 * GIN+ML+ * GIN+AW+ * ... * * UNT++1 * UNZ+1+ */ window.EDIBridge = window.EDIBridge || {}; class VDA4913ToDESADV { static UNIT_MAP = { 'ST': 'EA', 'KG': 'KGM', 'M': 'MTR', 'L': 'LTR', 'PCE': 'PCE' }; static convert(vdaContent, nadData, weights, config) { const VDAParser = window.EDIBridge.VDAParser; const EDIFACTLogic = window.EDIBridge.EDIFACTLogic; const ConfigParser = window.EDIBridge.ConfigParser; const vda = VDAParser.parse(vdaContent); const edi = new EDIFACTLogic(); const h = vda.interchanges[0] && vda.interchanges[0].header; const rffAnk = (config && h && ConfigParser) ? (ConfigParser.lookupRffAnk(config, h.sourceId) || '') : ''; let globalSerial = 10000; const interchangeRef = (h && (h.transNo1 || h.transNo2 || h.sourceId) || '0') .replace(/\D/g, '') .padStart(14, '0') .slice(-14); const allMessages = []; for (const interchange of vda.interchanges) { const hdr = interchange.header; const now = new Date(); const timeStr = String(now.getHours()).padStart(2, '0') + String(now.getMinutes()).padStart(2, '0'); const cfgBY = (config && ConfigParser) ? ConfigParser.lookupNAD(config, 'BY', hdr.targetId) : null; const cfgSE = (config && ConfigParser) ? ConfigParser.lookupNAD(config, 'SE', hdr.sourceId) : null; const cfgST = (config && ConfigParser) ? ConfigParser.lookupNAD(config, 'ST', hdr.targetId) : null; const nadBY = (cfgBY && cfgBY.name) ? cfgBY : ((nadData && nadData.BY) || {}); const nadSE = (cfgSE && cfgSE.name) ? cfgSE : ((nadData && nadData.SE) || {}); const nadST = (cfgST && cfgST.name) ? cfgST : ((nadData && nadData.ST) || {}); const sendId = (nadSE && nadSE.id) || hdr.sourceId; const recId = (nadBY && nadBY.id) || hdr.targetId; const unbReceiver = (config && ConfigParser) ? ConfigParser.lookupGeneral(config, 'UNB_RECEIVER', 'BOSCH EDI-TEAM') : 'BOSCH EDI-TEAM'; const unb = edi.segment('UNB', [ ['UNOC', '3'], sendId, [unbReceiver, '', recId], [hdr.date, timeStr], interchangeRef ]); // Calculate dynamic expiry date (1 year after delivery) const deliveryDate = hdr.date; // YYMMDD let expiryDate = '20271231'; // Safe fallback if (deliveryDate && deliveryDate.length === 6) { const year = parseInt('20' + deliveryDate.substring(0, 2)) + 1; expiryDate = year.toString() + deliveryDate.substring(2); } for (const t of interchange.transports) { for (const dn of t.deliveryNotes) { // ------------------------------------------------------- // Alle Positionen sammeln: Karton-SSCCs und Paletten-SSCCs // ------------------------------------------------------- // palGroups: Array von { palSSCC, cartonSerials[], pos, linNo, cPack, pPack, qtyPerPal, pcsPerCarton, cMatSupp, pMatSupp } const palGroups = []; let linCounter = 1; for (const pos of dn.positions) { const unitEdi = this.UNIT_MAP[pos.data.unit] || pos.data.unit || 'EA'; const inners = pos.packaging.filter(p => p.qty > 1); const outers = pos.packaging.filter(p => p.qty === 1); if (inners.length === 0 && pos.packaging.length > 0) { inners.push(pos.packaging[0]); } // Alle Karton-SSCCs generieren const allCartonSerials = []; for (const pack of inners) { if (pack.labelFrom) { const f = parseInt(pack.labelFrom); const to = pack.labelTo ? parseInt(pack.labelTo) : f; for (let s = f; s <= to; s++) { const sn = String(s).padStart(pack.labelFrom.length, '0'); allCartonSerials.push(('UN' + rffAnk + sn).slice(0, 20)); } } else { allCartonSerials.push( ('UN' + rffAnk + '8' + String(globalSerial++).padStart(9, '0')).slice(0, 20) ); } } const palCount = Math.max(1, outers.length); const qtyPerPal = pos.data.qty / palCount; const cartonsPerPal = Math.ceil(allCartonSerials.length / palCount); const cPack = inners[0] || pos.packaging[0]; const cMatSupp = (cPack && cPack.packMatSupp) || ''; const pw_c = (config && ConfigParser) ? (ConfigParser.lookupPacWeight(config, cMatSupp) || 1) : ((weights && weights.pacWeights && weights.pacWeights[cMatSupp]) || 1); const pd_c_ln = (config && ConfigParser) ? (ConfigParser.lookupPacDimension(config, cMatSupp, 'LENGTH') || 40) : ((weights && weights.pacDims && weights.pacDims[cMatSupp] && weights.pacDims[cMatSupp].ln) || 40); const pd_c_wd = (config && ConfigParser) ? (ConfigParser.lookupPacDimension(config, cMatSupp, 'WIDTH') || 30) : ((weights && weights.pacDims && weights.pacDims[cMatSupp] && weights.pacDims[cMatSupp].wd) || 30); const pd_c_ht = (config && ConfigParser) ? (ConfigParser.lookupPacDimension(config, cMatSupp, 'HEIGHT') || 15) : ((weights && weights.pacDims && weights.pacDims[cMatSupp] && weights.pacDims[cMatSupp].ht) || 15); for (let i = 0; i < palCount; i++) { const curPal = outers[i]; const mySerials = allCartonSerials.slice( i * cartonsPerPal, (i + 1) * cartonsPerPal ); const pcsPerCarton = Math.max(1, Math.floor(qtyPerPal / (mySerials.length || 1))); const palSSCC = (curPal && curPal.labelFrom) ? ('UN' + rffAnk + curPal.labelFrom).slice(0, 20) : ('UN' + rffAnk + '8' + String(globalSerial++).padStart(9, '0')).slice(0, 20); const pMatSupp = (curPal && curPal.packMatSupp) || ''; const pw_p = (config && ConfigParser) ? (ConfigParser.lookupPacWeight(config, pMatSupp) || 1) : ((weights && weights.pacWeights && weights.pacWeights[pMatSupp]) || 1); const pd_p_ln = (config && ConfigParser) ? (ConfigParser.lookupPacDimension(config, pMatSupp, 'LENGTH') || 120) : ((weights && weights.pacDims && weights.pacDims[pMatSupp] && weights.pacDims[pMatSupp].ln) || 120); const pd_p_wd = (config && ConfigParser) ? (ConfigParser.lookupPacDimension(config, pMatSupp, 'WIDTH') || 80) : ((weights && weights.pacDims && weights.pacDims[pMatSupp] && weights.pacDims[pMatSupp].wd) || 80); const pd_p_ht = (config && ConfigParser) ? (ConfigParser.lookupPacDimension(config, pMatSupp, 'HEIGHT') || 100) : ((weights && weights.pacDims && weights.pacDims[pMatSupp] && weights.pacDims[pMatSupp].ht) || 100); palGroups.push({ palSSCC, cartonSerials: mySerials, pos, linNo: (linCounter++).toString(), unitEdi, qtyPerPal, pcsPerCarton, cPack, cMatSupp, pw_c, pd_c_ln, pd_c_wd, pd_c_ht, pMatSupp, pw_p, pd_p_ln, pd_p_wd, pd_p_ht }); } } // ------------------------------------------------------- // EINE UNH-Nachricht fuer alle Paletten dieser Lieferung // ------------------------------------------------------- const segs = []; segs.push(edi.segment('UNH', ['1', ['DESADV', 'D', '07A', 'UN', 'GMI022']])); segs.push(edi.segment('BGM', ['351', dn.header.dnNo || '', '9'])); segs.push(edi.segment('DTM', [['137', '20' + hdr.date, '102']])); segs.push(edi.segment('DTM', [['132', '20' + hdr.date, '102']])); segs.push(edi.segment('DTM', [['10', '20' + hdr.date, '102']])); segs.push(edi.segment('MEA', ['AAX', 'AAD', ['KGM', t.data.grossWeight]])); segs.push(edi.segment('MEA', ['AAX', 'AAL', ['KGM', t.data.netWeight || t.data.grossWeight]])); segs.push(edi.segment('RFF', [['AAS', t.data.refNo]])); const segBY = this._buildNAD(edi, 'BY', nadBY); const segSE = this._buildNAD(edi, 'SE', nadSE); const segST = this._buildNAD(edi, 'ST', nadST); if (segBY) segs.push(segBY); if (segSE) segs.push(segSE); if (segST) segs.push(segST); segs.push(edi.segment('LOC', ['11', dn.header.unloadingPoint])); // ------------------------------------------------------- // CPS+N++1 pro Artikel (Karton-Ebene) // ------------------------------------------------------- let cpsCounter = 1; // Group palGroups by pos and batch const posGroups = new Map(); for (const pg of palGroups) { const batch = pg.pos.data.batchNo || ''; const groupKey = pg.pos.data.custMat + '_' + batch; if (!posGroups.has(groupKey)) { posGroups.set(groupKey, []); } posGroups.get(groupKey).push(pg); } for (const [groupKey, pgs] of posGroups.entries()) { segs.push(edi.segment('CPS', [String(cpsCounter), '', '1'])); const firstPg = pgs[0]; const pos = firstPg.pos; const totalCartons = pgs.reduce((sum, pg) => sum + pg.cartonSerials.length, 0); // PAC: Anzahl Kartons fuer diesen Artikel gesamt segs.push(edi.segment('PAC', [ String(totalCartons), '', '', ['F', (firstPg.cPack && firstPg.cPack.packMatCust) || '', 'IN', firstPg.cMatSupp, 'SA'] ])); segs.push(edi.segment('MEA', ['AAY', 'G', ['KGM', String(firstPg.pw_c)]])); segs.push(edi.segment('MEA', ['AAY', 'AAL', ['KGM', String(firstPg.pw_c)]])); segs.push(edi.segment('MEA', ['AAY', 'LN', ['CMT', String(firstPg.pd_c_ln)]])); segs.push(edi.segment('MEA', ['AAY', 'WD', ['CMT', String(firstPg.pd_c_wd)]])); segs.push(edi.segment('MEA', ['AAY', 'HT', ['CMT', String(firstPg.pd_c_ht)]])); segs.push(edi.segment('QTY', [['52', String(firstPg.pcsPerCarton), 'PCE']])); // PCI-Loop pro Palette (SG13) for (const pg of pgs) { // PCI+17+++S::5 + RFF+ACI: (Referenz auf Aussenverpackung) segs.push(edi.segment('PCI', ['17', '', '', ['S', '', '5']])); segs.push(edi.segment('RFF', [['ACI', pg.palSSCC]])); segs.push(edi.segment('GIR', ['7', ['1', 'AM']])); // GIN+ML pro Karton-SSCC for (const s of pg.cartonSerials) { segs.push(edi.segment('GIN', ['ML', s])); } } // Artikel-Zeile (SG17) - EINE pro Artikel segs.push(edi.segment('LIN', [firstPg.linNo, '', [pos.data.custMat, 'IN']])); const piaElements = ['1', [pos.data.suppMat, 'SA']]; if (pos.data.revisionLevel) { piaElements.push([pos.data.revisionLevel, 'EC']); } segs.push(edi.segment('PIA', piaElements)); if (pos.data.batchNo) { segs.push(edi.segment('PIA', ['1', [pos.data.batchNo, 'NB']])); } // Total quantity for all pos objects in this group const uniquePoses = new Set(pgs.map(pg => pg.pos)); let groupQty = 0; for (const p of uniquePoses) { groupQty += p.data.qty; } segs.push(edi.segment('QTY', [['12', String(groupQty), firstPg.unitEdi]])); segs.push(edi.segment('DTM', [['36', expiryDate, '102']])); segs.push(edi.segment('DTM', [['94', '20' + hdr.date, '102']])); segs.push(edi.segment('NAD', ['MF', sendId])); segs.push(edi.segment('RFF', [['ON', dn.header.orderNo, '001']])); segs.push(edi.segment('LOC', ['11', dn.header.unloadingPoint])); segs.push(edi.segment('LOC', ['18', hdr.targetId])); cpsCounter++; } // ------------------------------------------------------- // CPS+N++3 (Palette-Ebene) — EINMALIG am Ende // Gemaess Bosch PDF: Pro Palette eine PCI+GIN+ML+GIN+AW-Gruppe // ------------------------------------------------------- const firstPg = palGroups[0] || {}; const totalCartons = palGroups.reduce((s, pg) => s + pg.cartonSerials.length, 0); segs.push(edi.segment('CPS', [String(cpsCounter), '', '3'])); segs.push(edi.segment('PAC', [ String(palGroups.length), '', '', ['F', (firstPg.cPack && firstPg.cPack.packMatCust) || 'Palette', 'IN', firstPg.pMatSupp || '', 'SA'] ])); segs.push(edi.segment('MEA', ['AAY', 'G', ['KGM', String(firstPg.pw_p || 1)]])); segs.push(edi.segment('MEA', ['AAY', 'AAL', ['KGM', String(firstPg.pw_p || 1)]])); segs.push(edi.segment('MEA', ['AAY', 'LN', ['CMT', String(firstPg.pd_p_ln || 120)]])); segs.push(edi.segment('MEA', ['AAY', 'WD', ['CMT', String(firstPg.pd_p_wd || 80)]])); segs.push(edi.segment('MEA', ['AAY', 'HT', ['CMT', String(firstPg.pd_p_ht || 100)]])); segs.push(edi.segment('QTY', [['189', String(totalCartons), 'PCE']])); // Pro Palette: PCI + GIN+ML(palSSCC) + GIN+AW(kartonSSCCs) for (const pg of palGroups) { segs.push(edi.segment('PCI', ['17', '', '', ['M', '', '5']])); segs.push(edi.segment('GIN', ['ML', pg.palSSCC])); for (const s of pg.cartonSerials) { segs.push(edi.segment('GIN', ['AW', s])); } } // UNT: Segmente von UNH bis UNT inklusiv const segCount = segs.length + 1; // +1 fuer UNT selbst segs.push(edi.segment('UNT', [String(segCount), '1'])); allMessages.push(segs.join('')); } } const msgCount = allMessages.length; const unz = edi.segment('UNZ', [String(msgCount), interchangeRef]); return unb + allMessages.join('') + unz; } return ''; } static _buildNAD(edi, qualifier, nad) { if (!nad || !nad.name) return null; // 3055 agency code: GMI022 validator requires '92' for all NAD qualifiers const c082 = [nad.id || '', '', '92']; return edi.segment('NAD', [ qualifier, c082, '', nad.name || '', nad.street || '', nad.city || '', '', nad.zip || '', nad.country || '' ]); } } window.EDIBridge.VDA4913ToDESADV = VDA4913ToDESADV;