Erster Commit
This commit is contained in:
245
js/vda4913-to-desadv-zf.js
Normal file
245
js/vda4913-to-desadv-zf.js
Normal file
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user