377 lines
19 KiB
JavaScript
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;
|