/** * VDA 4913 to EDIFACT DESADV D07A (ZF GAVF13) Converter * * Implements a top-down packaging hierarchy (Pallet -> Carton -> Item) * as specified in "ZF Global DESADV 1.3.9 EN Examples.pdf". */ window.EDIBridge = window.EDIBridge || {}; class VDA4913ToDESADVZF { static UNIT_MAP = { 'ST': 'PCE', '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', 'ZF FRIEDRICHSHAFEN AG') : 'ZF FRIEDRICHSHAFEN AG'; const unb = edi.segment('UNB', [ ['UNOC', '3'], sendId, [unbReceiver, '', recId], [hdr.date, timeStr], interchangeRef ]); // Calculate dynamic expiry date (1 year after delivery fallback) const deliveryDate = hdr.date; // YYMMDD let expiryDate = '20271231'; let formattedProdDate = '20230101'; if (deliveryDate && deliveryDate.length === 6) { const year = parseInt('20' + deliveryDate.substring(0, 2)) + 1; expiryDate = year.toString() + deliveryDate.substring(2); formattedProdDate = '20' + deliveryDate; } for (const t of interchange.transports) { for (const dn of t.deliveryNotes) { const segs = []; // UNH + BGM + DTMs (Header) segs.push(edi.segment('UNH', ['1', ['DESADV', 'D', '07A', 'UN', 'GAVF13']])); segs.push(edi.segment('BGM', ['351', dn.header.dnNo || '', '9'])); segs.push(edi.segment('DTM', [['137', '20' + hdr.date, '102']])); // Date of message segs.push(edi.segment('DTM', [['11', '20' + hdr.date, '102']])); // Despatch date segs.push(edi.segment('DTM', [['132', '20' + hdr.date, '102']])); // Estimated arrival 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]])); // ZF uses CRN for Shipment number segs.push(edi.segment('RFF', [['CRN', t.data.refNo || dn.header.dnNo]])); const segBY = this._buildNAD(edi, 'BY', nadBY); const segSE = this._buildNAD(edi, 'SE', nadSE); const segSF = this._buildNAD(edi, 'SF', nadSE); // Ship from const segST = this._buildNAD(edi, 'ST', nadST); if (segBY) segs.push(segBY); if (segSE) segs.push(segSE); if (segSF) segs.push(segSF); if (segST) segs.push(segST); segs.push(edi.segment('LOC', ['11', dn.header.unloadingPoint || 'GR'])); segs.push(edi.segment('TOD', ['6', '', 'FCA'])); segs.push(edi.segment('TDT', ['12', '', '30'])); // Road transport // ------------------------------------------------------- // Packaging Hierarchy (Top-Down: Pallet -> Cartons -> Item) // ------------------------------------------------------- let cpsCounter = 1; for (const pos of dn.positions) { const unitEdi = this.UNIT_MAP[pos.data.unit] || pos.data.unit || 'PCE'; 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]); } // Generate Carton Serials 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); for (let i = 0; i < palCount; i++) { const curPal = outers[i]; const curCartonGrp = inners[0] || pos.packaging[0] || {}; 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 palMatCust = (curPal && curPal.packMatCust) || 'PALLET'; const cartonMatCust = (curCartonGrp && curCartonGrp.packMatCust) || 'CARTON'; // Top level: Pallet (CPS 3) segs.push(edi.segment('CPS', [String(cpsCounter++), '', '3'])); segs.push(edi.segment('PAC', ['1', '', '35', ['AAD', palMatCust, '', '', '92']])); segs.push(edi.segment('PCI', ['17', '', '', ['6J', '', '5']])); segs.push(edi.segment('GIN', ['ML', palSSCC])); if (mySerials.length > 0) { const awBatch = ['AW', ...mySerials.slice(0, 4)]; // AW supports up to 35 refs, we do chunks if needed segs.push(edi.segment('GIN', awBatch)); } // Optional: Packaging Aid for Pallet if (curPal && curPal.packMatSupp) { segs.push(edi.segment('PAC', ['1', '', '37', ['AAD', curPal.packMatCust || '', '', '', '92']])); } // Second level: Cartons (CPS 1) if (mySerials.length > 0) { const parentCps = cpsCounter - 1; segs.push(edi.segment('CPS', [String(cpsCounter++), String(parentCps), '1'])); segs.push(edi.segment('PAC', [String(mySerials.length), '', '35', ['AAD', cartonMatCust, '', '', '92']])); segs.push(edi.segment('QTY', [['52', String(pcsPerCarton), unitEdi]])); // QTY per pack segs.push(edi.segment('PCI', ['17', '', '', ['1J', '', '5']])); if (pos.data.batchNo) { segs.push(edi.segment('GIR', ['1', [pos.data.batchNo, 'BX']])); // Batch number on single label } // GIN+ML for all single label SSCCs const mlBatch = ['ML', ...mySerials.slice(0, 4)]; segs.push(edi.segment('GIN', mlBatch)); // Optional: Packaging Aid for Carton if (curCartonGrp && curCartonGrp.packMatSupp) { segs.push(edi.segment('PAC', ['1', '', '37', ['AAD', curCartonGrp.packMatCust || '', '', '', '92']])); } } // Item level (LIN) segs.push(edi.segment('LIN', ['', '', [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)); segs.push(edi.segment('IMD', ['', '', ['', '', pos.data.custMatDesc || 'ITEM']])); segs.push(edi.segment('QTY', [['12', String(qtyPerPal), unitEdi]])); segs.push(edi.segment('ALI', [pos.data.originCountry || 'DE'])); // Article RFF references const lineItemNo = pos.data.posNo || String(cpsCounter); segs.push(edi.segment('RFF', [['AAU', dn.header.dnNo + ':' + lineItemNo]])); segs.push(edi.segment('DTM', [['171', formattedProdDate, '102']])); // Delivery note date segs.push(edi.segment('RFF', [['ON', dn.header.orderNo + ':' + lineItemNo]])); } } // 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; // NAD segment format for ZF const c082 = [nad.id || '', '', '92']; // Typically 92 or 91 return edi.segment('NAD', [ qualifier, c082, '', nad.name || '', nad.street || '', nad.city || '', '', nad.zip || '', nad.country || '' ]); } } window.EDIBridge.VDA4913ToDESADVZF = VDA4913ToDESADVZF;