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

339
js/vda4905-generator.js Normal file
View 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;