Erster Commit

This commit is contained in:
zed
2026-03-13 09:53:40 +01:00
commit 5ebcad02ed
3945 changed files with 974582 additions and 0 deletions

245
js/vda4913-to-desadv-zf.js Normal file
View 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;