340 lines
14 KiB
JavaScript
340 lines
14 KiB
JavaScript
/**
|
|
* VDA 4905 Generator
|
|
* Strictly follows BSH VDA 4905 Spec (Feb 2017)
|
|
*/
|
|
window.EDIBridge = window.EDIBridge || {};
|
|
|
|
class VDA4905Generator {
|
|
static generate(parsed) {
|
|
const segments = parsed.segments;
|
|
const records = [];
|
|
|
|
let count511 = 0, count512 = 0, count513 = 0, count514 = 0;
|
|
|
|
// --- Helpers ---
|
|
const getVal = (seg, elIdx, compIdx) => {
|
|
if (!seg || !seg.elements) return '';
|
|
const el = seg.elements[elIdx];
|
|
if (!el) return '';
|
|
if (Array.isArray(el)) return el[compIdx] || '';
|
|
if (compIdx === 0) return el;
|
|
// Fallback for flat elements: if asking for comp 1 of el 0, but el 0 is flat, maybe it's in el 1
|
|
if (elIdx === 0 && compIdx === 1) return seg.elements[1] || '';
|
|
return '';
|
|
};
|
|
|
|
const findSeg = (tag, qual) => segments.find(s => s.tag === tag && getVal(s, 0, 0) === qual);
|
|
|
|
// --- 1. Header (Record 511) ---
|
|
const nadBY = findSeg('NAD', 'BY');
|
|
const nadSE = findSeg('NAD', 'SE');
|
|
|
|
const custId = getVal(nadBY, 1, 0) || getVal(nadBY, 2, 0); // Try el 1 or 2
|
|
const suppId = getVal(nadSE, 1, 0) || getVal(nadSE, 2, 0);
|
|
|
|
const bgm = segments.find(s => s.tag === 'BGM');
|
|
const docNum = bgm ? (getVal(bgm, 1, 0) || getVal(bgm, 0, 1)) : '';
|
|
|
|
const dtm137 = findSeg('DTM', '137');
|
|
const docVerify = dtm137 ? getVal(dtm137, 0, 1) : '';
|
|
const docDateYYMMDD = docVerify.length === 8 ? docVerify.slice(2, 8) : (docVerify || '000000');
|
|
|
|
// Old Release Number: Prioritize AIF (Previous)
|
|
let rffOld = segments.find(s => s.tag === 'RFF' && ['AIF', 'AAN', 'AAL', 'PL'].includes(getVal(s, 0, 0)));
|
|
let oldDocNum = rffOld ? getVal(rffOld, 0, 1) : '00000';
|
|
|
|
// Old Release Date (DTM+192)
|
|
const dtm192 = findSeg('DTM', '192');
|
|
const oldDocVerify = dtm192 ? getVal(dtm192, 0, 1) : '';
|
|
const oldDocDateYYMMDD = oldDocVerify.length === 8 ? oldDocVerify.slice(2, 8) : (oldDocVerify || '000000');
|
|
|
|
records.push(this.build511(custId, suppId, docNum, docDateYYMMDD, oldDocNum));
|
|
count511++;
|
|
|
|
// --- 2. Loop through Articles (LIN) ---
|
|
let currentLinData = null;
|
|
for (let i = 0; i < segments.length; i++) {
|
|
const s = segments[i];
|
|
if (s.tag === 'LIN') {
|
|
if (currentLinData) {
|
|
const added514 = this.processLin(currentLinData, records, custId, docNum, docDateYYMMDD, oldDocNum, oldDocDateYYMMDD);
|
|
count512++;
|
|
count513++;
|
|
count514 += added514;
|
|
} else {
|
|
for (let k = i + 1; k < segments.length && segments[k].tag !== 'LIN'; k++) {
|
|
if (segments[k].tag === 'RFF' && getVal(segments[k], 0, 0) === 'AIF') {
|
|
oldDocNum = getVal(segments[k], 0, 1);
|
|
records[0] = this.build511(custId, suppId, docNum, docDateYYMMDD, oldDocNum);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
currentLinData = { lin: s, qtyDtms: [], rffs: [], pia: null, imd: null, locs: [], dtms: [] };
|
|
} else if (currentLinData) {
|
|
if (s.tag === 'QTY') {
|
|
let date = '';
|
|
let rffs = [];
|
|
for (let j = i + 1; j < segments.length; j++) {
|
|
const next = segments[j];
|
|
if (next.tag === 'QTY' || next.tag === 'LIN' || next.tag === 'UNS' || next.tag === 'CNT') break;
|
|
if (next.tag === 'DTM') {
|
|
const q = getVal(next, 0, 0);
|
|
if (['10', '2', '11', '64', '63', '69', '171', '50', '137', '131', '242'].includes(q)) {
|
|
date = getVal(next, 0, 1);
|
|
}
|
|
}
|
|
if (next.tag === 'RFF' || next.tag === 'DOC') {
|
|
rffs.push(next);
|
|
}
|
|
}
|
|
currentLinData.qtyDtms.push({ qtySeg: s, date, rffs });
|
|
}
|
|
else if (s.tag === 'RFF') currentLinData.rffs.push(s);
|
|
else if (s.tag === 'PIA') currentLinData.pia = s;
|
|
else if (s.tag === 'IMD') currentLinData.imd = s;
|
|
else if (s.tag === 'LOC') currentLinData.locs.push(s);
|
|
else if (s.tag === 'DTM') currentLinData.dtms.push(s);
|
|
}
|
|
}
|
|
if (currentLinData) {
|
|
const added514 = this.processLin(currentLinData, records, custId, docNum, docDateYYMMDD, oldDocNum, oldDocDateYYMMDD);
|
|
count512++;
|
|
count513++;
|
|
count514 += added514;
|
|
}
|
|
|
|
// --- 3. Trailer (Record 519) ---
|
|
records.push(this.build519(count511, count512, count513, count514));
|
|
|
|
return records.map(r => r + '\r\n').join('');
|
|
}
|
|
|
|
static processLin(data, records, custId, docNum, docDate, headerOldDoc, headerOldDate) {
|
|
const getVal = (seg, elIdx, compIdx) => {
|
|
if (!seg || !seg.elements) return '';
|
|
const el = seg.elements[elIdx];
|
|
if (!el) return '';
|
|
if (Array.isArray(el)) return el[compIdx] || '';
|
|
if (compIdx === 0) return el;
|
|
if (elIdx === 0 && compIdx === 1) return seg.elements[1] || '';
|
|
return '';
|
|
};
|
|
|
|
const loc11 = data.locs.find(l => getVal(l, 0, 0) === '11');
|
|
const plant = loc11 ? getVal(loc11, 1, 0) : ' ';
|
|
|
|
const custMat = getVal(data.lin, 2, 0);
|
|
|
|
const rffON = data.rffs.find(r => getVal(r, 0, 0) === 'ON');
|
|
const orderNo = rffON ? getVal(rffON, 0, 1) : '';
|
|
|
|
const rffAif = data.rffs.find(r => getVal(r, 0, 0) === 'AIF');
|
|
const oldDocNum = rffAif ? getVal(rffAif, 0, 1) : headerOldDoc;
|
|
|
|
const dtm192 = data.dtms.find(d => getVal(d, 0, 0) === '192');
|
|
let oldDocDate = dtm192 ? getVal(dtm192, 0, 1) : headerOldDate;
|
|
if (oldDocDate.length === 8) oldDocDate = oldDocDate.slice(2);
|
|
|
|
const loc159 = data.locs.find(l => getVal(l, 0, 0) === '159');
|
|
let unloading = loc159 ? getVal(loc159, 1, 0) : '';
|
|
if (!unloading && loc11) unloading = getVal(loc11, 1, 0);
|
|
|
|
let unit = 'ST';
|
|
if (data.qtyDtms.length > 0) {
|
|
const u = getVal(data.qtyDtms[0].qtySeg, 0, 2);
|
|
if (u === 'PCE' || u === 'C62') unit = 'ST';
|
|
else if (u === 'KGM') unit = 'KG';
|
|
else if (u === 'MTR') unit = 'M ';
|
|
}
|
|
|
|
records.push(this.build512(plant, docNum, docDate, custMat, orderNo, unloading, unit, oldDocNum, oldDocDate));
|
|
|
|
const schedules = [];
|
|
for (const item of data.qtyDtms) {
|
|
const qual = getVal(item.qtySeg, 0, 0);
|
|
const qty = getVal(item.qtySeg, 0, 1);
|
|
|
|
if (['1', '12', '11', '21', '113'].includes(qual)) {
|
|
let d = item.date;
|
|
if (!d) d = docDate;
|
|
if (d.length === 8) d = d.slice(2);
|
|
// VDA 4905: Quantities are usually whole numbers or scaled as needed.
|
|
// Removed implicit x1000 scaling as per user feedback (15000 should be 15000).
|
|
const mappedQty = String(Math.round(parseFloat(qty) || 0));
|
|
schedules.push({ date: d || '000000', qty: mappedQty, qual });
|
|
}
|
|
}
|
|
|
|
schedules.sort((a, b) => {
|
|
if (a.qual === '12' && b.qual !== '12') return -1;
|
|
if (a.qual !== '12' && b.qual === '12') return 1;
|
|
return 0;
|
|
});
|
|
|
|
const chunk513 = schedules.slice(0, 5);
|
|
const chunkRest = schedules.slice(5);
|
|
|
|
// EFZ (Eingangs-Fortschrittszahl) = QTY+70
|
|
// QTY+194 sind einzelne Lieferschein-Mengen, NICHT die Fortschrittszahl!
|
|
// VDA 4905: Quantities are usually whole numbers as per user requirements
|
|
const qty70 = data.qtyDtms.find(q => getVal(q.qtySeg, 0, 0) === '70');
|
|
const efzRaw = qty70 ? (parseFloat(getVal(qty70.qtySeg, 0, 1)) || 0) : 0;
|
|
const efz = String(Math.round(efzRaw));
|
|
|
|
// Pick latest candidate for head of 513
|
|
// 48=Received, 194=Received (alt), 12=In-Transit
|
|
let candidates = data.qtyDtms.filter(q => ['48', '194', '12'].includes(getVal(q.qtySeg, 0, 0)));
|
|
if (candidates.length === 0) candidates = data.qtyDtms.filter(q => getVal(q.qtySeg, 0, 0) === '1');
|
|
const lastRec = candidates.length > 0 ? candidates[candidates.length - 1] : null;
|
|
|
|
let weDate = lastRec && lastRec.date ? lastRec.date : '';
|
|
if (!weDate && data.dtms) {
|
|
const dtmRec = data.dtms.find(d => ['131', '50', '171', '137', '11'].includes(getVal(d, 0, 0)));
|
|
weDate = dtmRec ? getVal(dtmRec, 0, 1) : '';
|
|
}
|
|
if (weDate.length === 8) weDate = weDate.slice(2);
|
|
|
|
// lastQty likewise without x1000 scaling
|
|
const lastQtyRaw = lastRec ? (parseFloat(getVal(lastRec.qtySeg, 0, 1)) || 0) : 0;
|
|
const lastQty = String(Math.round(lastQtyRaw));
|
|
|
|
let lastDn = '';
|
|
const dnQuals = ['AAU', 'AAK', 'VS', 'VN', 'IF', 'SI', 'RE'];
|
|
if (lastRec && lastRec.rffs && lastRec.rffs.length > 0) {
|
|
const rr = lastRec.rffs.find(seg => dnQuals.includes(getVal(seg, 0, 0)));
|
|
if (rr) lastDn = getVal(rr, 0, 1);
|
|
}
|
|
if (!lastDn) {
|
|
const rff = data.rffs.find(r => dnQuals.includes(getVal(r, 0, 0)));
|
|
lastDn = rff ? getVal(rff, 0, 1) : '';
|
|
}
|
|
|
|
let lastDate = lastRec && lastRec.date ? lastRec.date : '';
|
|
if (!lastDate && data.dtms) {
|
|
const dtmL = data.dtms.find(d => ['11', '171', '137', '131', '50'].includes(getVal(d, 0, 0)));
|
|
lastDate = dtmL ? getVal(dtmL, 0, 1) : '';
|
|
}
|
|
if (lastDate.length === 8) lastDate = lastDate.slice(2);
|
|
|
|
// Null-Stellung: DTM+51 Datum
|
|
let nullStellung = '';
|
|
if (qty70) {
|
|
// DTM+51 folgt direkt nach QTY+70 in der DELFOR
|
|
const qty70idx = data.qtyDtms.indexOf(qty70);
|
|
// Suche DTM+51 in den Artikel-DTMs
|
|
const dtm51 = data.dtms ? data.dtms.find(d => getVal(d, 0, 0) === '51') : null;
|
|
if (dtm51) {
|
|
nullStellung = getVal(dtm51, 0, 1);
|
|
if (nullStellung.length === 8) nullStellung = nullStellung.slice(2);
|
|
}
|
|
}
|
|
|
|
records.push(this.build513(chunk513, weDate, lastDn, lastDate, lastQty, efz, nullStellung));
|
|
|
|
let count514 = 0;
|
|
for (let i = 0; i < chunkRest.length; i += 8) {
|
|
const chunk = chunkRest.slice(i, i + 8);
|
|
records.push(this.build514(chunk));
|
|
count514++;
|
|
}
|
|
return count514;
|
|
}
|
|
|
|
// --- Format Helpers ---
|
|
static padN(val, len) {
|
|
let v = String(val || '').replace(/[^0-9]/g, '');
|
|
if (v.length > len) v = v.replace(/^0+/, '');
|
|
return v.padStart(len, '0').slice(-len);
|
|
}
|
|
static padA(val, len) { return String(val || '').padEnd(len, ' ').slice(0, len); }
|
|
|
|
// --- Builders ---
|
|
|
|
static build511(cust, supp, docNum, date, oldDocNum) {
|
|
let r = '51101';
|
|
r += this.padA(cust, 9);
|
|
r += this.padA(supp, 9);
|
|
r += this.padN(oldDocNum || '00000', 5); // Old Trans No
|
|
r += this.padN(docNum, 5); // New Trans No
|
|
r += this.padN(date, 6);
|
|
r += ''.padEnd(89, ' ');
|
|
return r;
|
|
}
|
|
|
|
static build512(plant, docNum, docDate, custMat, orderNo, unloading, unit, oldDocNum, oldDocDate) {
|
|
let r = '51201';
|
|
r += this.padA(plant, 3);
|
|
r += this.padN(docNum, 9);
|
|
r += this.padN(docDate, 6);
|
|
r += this.padN(oldDocNum || '000000000', 9);
|
|
r += this.padN(oldDocDate || '000000', 6);
|
|
r += this.padA(custMat, 22);
|
|
r += this.padA('', 22);
|
|
r += this.padA(orderNo, 12);
|
|
r += this.padA(unloading, 5);
|
|
r += ' ';
|
|
r += this.padA(unit, 2);
|
|
r += 'L';
|
|
r += ' ';
|
|
r += 'S';
|
|
r += 'D ';
|
|
r += '01 ';
|
|
r += ' ';
|
|
return r;
|
|
}
|
|
|
|
static build513(schedules5, weDate, lastDn, lastDate, lastQty, efz, nullStellung) {
|
|
let r = '51301';
|
|
r += this.padN(weDate || '000000', 6);
|
|
r += this.padN(lastDn || '00000000', 8);
|
|
r += this.padN(lastDate || '000000', 6);
|
|
r += this.padN(lastQty || '0', 12);
|
|
r += this.padN(efz || '0', 10);
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
if (i < schedules5.length) {
|
|
const s = schedules5[i];
|
|
r += this.padN(s.date, 6);
|
|
r += this.padN(s.qty, 9);
|
|
} else {
|
|
r += ' ';
|
|
r += ' ';
|
|
}
|
|
}
|
|
// Null-Stellung (6 Zeichen JJMMTT) am Ende von 513
|
|
r += this.padN(nullStellung || '000000', 6);
|
|
return r;
|
|
}
|
|
|
|
static build514(schedules8) {
|
|
let r = '51401';
|
|
for (let i = 0; i < 8; i++) {
|
|
if (i < schedules8.length) {
|
|
const s = schedules8[i];
|
|
r += this.padN(s.date, 6);
|
|
r += this.padN(s.qty, 9);
|
|
} else {
|
|
r += ' ';
|
|
r += ' ';
|
|
}
|
|
}
|
|
r += ' ';
|
|
return r;
|
|
}
|
|
|
|
static build519(c511, c512, c513, c514) {
|
|
let r = '51901';
|
|
r += this.padN(c511, 7);
|
|
r += this.padN(c512, 7);
|
|
r += this.padN(c513, 7);
|
|
r += this.padN(c514, 7);
|
|
r += '0000000';
|
|
r += '0000000';
|
|
r += '0000001';
|
|
r += ''.padEnd(74, ' ');
|
|
return r;
|
|
}
|
|
}
|
|
window.EDIBridge.VDA4905Generator = VDA4905Generator;
|