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