Files
vda-to-edifact-converter/js/vda4913-to-desadv.js
2026-03-13 09:53:40 +01:00

377 lines
19 KiB
JavaScript

/**
* 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+<anzahl>+++F:<typ>:IN:<suppMat>:SA
* MEA(5x: G, AAL, LN, WD, HT)
* QTY+52:<stueckProKarton>:PCE
* PCI+17+++S::5
* RFF+ACI:<paletteSSCC> (Referenz auf Aussenverpackung)
* GIR+7+1:AM
* GIN+ML+<kartonSSCC1>
* GIN+ML+<kartonSSCC2>
* ...
* LIN+1++<custMat>:IN
* PIA+1+<suppMat>:SA
* QTY+12:<gesamtMenge>:<einheit>
* DTM+36:<ablaufdatum>:102
* DTM+94:<lieferdatum>:102
* NAD+MF+<senderId>
* RFF+ON:<bestellnr>:001
* LOC+11+<entladestelle>
* LOC+18+<empfaenger>
*
* -- 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+<letzteN+1>++3
* PAC+<anzahlPaletten>+++F:Palette:IN::SA
* MEA(5x)
* QTY+189:<anzahlKartons>:PCE
* -- Pro Palette: PCI+17+++M::5 + GIN+ML+<palSSCC> + GIN+AW+<kartonSSCC>... --
* PCI+17+++M::5
* GIN+ML+<palSSCC1>
* GIN+AW+<kartonSSCC1>
* GIN+AW+<kartonSSCC2>
* PCI+17+++M::5
* GIN+ML+<palSSCC2>
* GIN+AW+<kartonSSCC3>
* ...
*
* UNT+<segCount>+1
* UNZ+1+<interchangeRef>
*/
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:<palSSCC> (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;