399 lines
18 KiB
JavaScript
399 lines
18 KiB
JavaScript
/**
|
|
* INVRPT D13A → VDA 4913 EDL36/35 Converter
|
|
*
|
|
* Converts EDIFACT INVRPT (Inventory Report) messages into
|
|
* VDA 4913 format with EDL36 (delivery note) and EDL35 (stock) records.
|
|
*
|
|
* Record structure produced:
|
|
* 711 Header
|
|
* 712 Transport (one per INVRPT message)
|
|
* 713 Delivery note header (EDL36 or EDL35)
|
|
* 714 Position (material / qty)
|
|
* 719 Trailer
|
|
*/
|
|
window.EDIBridge = window.EDIBridge || {};
|
|
|
|
class InvrptToVDA4913 {
|
|
|
|
// ─── Public API ──────────────────────────────────────────────────
|
|
static async convertAsync(edifactContent, config) {
|
|
let parsed;
|
|
// Try rigorous parsing via ts-edifact if available
|
|
if (window.electronAPI && window.electronAPI.parseEdifactLib) {
|
|
try {
|
|
const libResult = await window.electronAPI.parseEdifactLib(edifactContent);
|
|
if (libResult.success && libResult.data) {
|
|
parsed = { segments: libResult.data };
|
|
}
|
|
} catch (e) {
|
|
console.warn('ts-edifact parsing fall-back to basic parser:', e);
|
|
}
|
|
}
|
|
|
|
// Fallback to basic parser
|
|
if (!parsed) {
|
|
const parser = window.EDIBridge.DelforParser;
|
|
if (!parser) throw new Error('DelforParser nicht geladen.');
|
|
parsed = parser.parse(edifactContent);
|
|
}
|
|
|
|
return this.processParsedData(parsed.segments, config);
|
|
}
|
|
|
|
// For backwards compatibility if called synchronously without await
|
|
static convert(edifactContent, config) {
|
|
const parser = window.EDIBridge.DelforParser;
|
|
if (!parser) throw new Error('DelforParser nicht geladen.');
|
|
const parsed = parser.parse(edifactContent);
|
|
return this.processParsedData(parsed.segments, config);
|
|
}
|
|
|
|
static processParsedData(segments, config) {
|
|
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 groups = this.splitMessages(segments, getVal);
|
|
const firstGroup = groups[0] || [];
|
|
const nadWH = firstGroup.find(s => s.tag === 'NAD' && getVal(s, 0, 0) === 'WH');
|
|
const nadGM = firstGroup.find(s => s.tag === 'NAD' && getVal(s, 0, 0) === 'GM');
|
|
const dtm137 = firstGroup.find(s => s.tag === 'DTM' && getVal(s, 0, 0) === '137');
|
|
|
|
const targetId = nadWH ? (getVal(nadWH, 1, 0) || getVal(nadWH, 0, 1)) : '';
|
|
const sourceId = nadGM ? (getVal(nadGM, 1, 0) || getVal(nadGM, 0, 1)) : '';
|
|
const msgDateRaw = dtm137 ? getVal(dtm137, 0, 1) : '';
|
|
const msgDate = msgDateRaw.length === 8 ? msgDateRaw.slice(2) : (msgDateRaw || '000000');
|
|
|
|
const unz = segments.find(s => s.tag === 'UNZ');
|
|
const unzRef = unz ? getVal(unz, 1, 0) : '00001';
|
|
const transNo1 = unzRef;
|
|
const transNo2 = String((parseInt(unzRef) || 0) + 1);
|
|
|
|
const records = [];
|
|
records.push(this.build711(targetId, sourceId, transNo1, transNo2, msgDate, config));
|
|
|
|
let totalRecords712 = 0;
|
|
let totalRecords713 = 0;
|
|
let totalRecords714 = 0;
|
|
|
|
for (const group of groups) {
|
|
const bgm = group.find(s => s.tag === 'BGM');
|
|
const bgmType = bgm ? getVal(bgm, 0, 0) : '';
|
|
|
|
// We now process BGM+35 (Inventory Report) and BGM+78 (Inventory Movement)
|
|
|
|
// Re-group into LIN blocks
|
|
let currentLin = null;
|
|
let currentInv = null;
|
|
let linBlocks = [];
|
|
|
|
for (const seg of group) {
|
|
if (seg.tag === 'LIN') {
|
|
currentLin = { seg, invs: [] };
|
|
linBlocks.push(currentLin);
|
|
currentInv = null;
|
|
} else if (seg.tag === 'INV' && currentLin) {
|
|
currentInv = { seg, details: [] };
|
|
currentLin.invs.push(currentInv);
|
|
} else if (currentInv) {
|
|
currentInv.details.push(seg);
|
|
} else if (currentLin) {
|
|
currentLin.invs.push({ seg: null, details: [seg] }); // segments under LIN but before INV
|
|
}
|
|
}
|
|
|
|
for (const linBlock of linBlocks) {
|
|
const lin = linBlock.seg;
|
|
const material = getVal(lin, 2, 0);
|
|
|
|
for (const invBlock of linBlock.invs) {
|
|
const inv = invBlock.seg;
|
|
const details = invBlock.details;
|
|
|
|
const qty156 = details.find(s => s.tag === 'QTY' && getVal(s, 0, 0) === '156');
|
|
const qty145 = details.find(s => s.tag === 'QTY' && getVal(s, 0, 0) === '145');
|
|
const qtyOnHand = qty156 ? (parseFloat(getVal(qty156, 0, 1)) || 0) : 0;
|
|
const qtyCumulative = qty145 ? (parseFloat(getVal(qty145, 0, 1)) || 0) : 0;
|
|
|
|
let unitObj = qty156 || qty145;
|
|
const unit = unitObj ? (getVal(unitObj, 0, 2) || 'PCE') : 'PCE';
|
|
const vdaUnit = (unit === 'PCE' || unit === 'C62' || unit === 'EA') ? 'ST' :
|
|
(unit === 'KGM' ? 'KG' : (unit === 'MTR' ? 'M ' : 'ST'));
|
|
|
|
const dtm179 = details.find(s => s.tag === 'DTM' && getVal(s, 0, 0) === '179');
|
|
const invDateRaw = dtm179 ? getVal(dtm179, 0, 1) : msgDateRaw;
|
|
const invDate = invDateRaw.length === 8 ? invDateRaw.slice(2) : (invDateRaw || '000000');
|
|
|
|
// Global or detail refs
|
|
const rffDQ = details.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'DQ') || group.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'DQ');
|
|
const rffTN = details.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'TN') || group.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'TN');
|
|
const rffAAU = details.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'AAU') || group.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'AAU');
|
|
const rffON = details.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'ON') || group.find(s => s.tag === 'RFF' && getVal(s, 0, 0) === 'ON');
|
|
|
|
const dqNo = rffDQ ? getVal(rffDQ, 0, 1) : '';
|
|
const tnNo = rffTN ? getVal(rffTN, 0, 1) : '';
|
|
const aauNo = rffAAU ? getVal(rffAAU, 0, 1) : '';
|
|
const onNo = rffON ? getVal(rffON, 0, 1) : '';
|
|
|
|
// Lieferschein-Nr (current reference)
|
|
const currentDn = aauNo || tnNo || dqNo;
|
|
|
|
const groupLoc11 = group.find(s => s.tag === 'LOC' && getVal(s, 0, 0) === '11');
|
|
const unloadingPoint = groupLoc11 ? (getVal(groupLoc11, 1, 0) || '').replace(/^W/, '') : '';
|
|
|
|
// Get specific storage location for this stock
|
|
const loc18 = details.find(s => s.tag === 'LOC' && getVal(s, 0, 0) === '18');
|
|
const storageLocation = loc18 ? getVal(loc18, 1, 0) : '';
|
|
|
|
// EDL36 (Movements) logic
|
|
if (qty156 || currentDn) {
|
|
// Determine transaction key (Vorgangsschlüssel) based on INV and QTY
|
|
// Default to 36 (Abgangsmeldung)
|
|
let vdaKey = '36';
|
|
|
|
if (inv) {
|
|
const direction = getVal(inv, 0, 0); // DE 4501
|
|
const reason = getVal(inv, 2, 0); // DE 4499
|
|
|
|
if (direction === '2' || reason === '1') {
|
|
vdaKey = '30'; // Eingangsmeldung
|
|
} else if (reason === '4' || reason === '3') {
|
|
vdaKey = '32'; // Differenz-/Verlustmeldung
|
|
} else if (reason === '2' || reason === '11') {
|
|
vdaKey = '36'; // Abgangsmeldung
|
|
}
|
|
}
|
|
|
|
records.push(this.build712(msgDate));
|
|
totalRecords712++;
|
|
|
|
records.push(this.build713(currentDn || '00000000', invDate, unloadingPoint, vdaKey, targetId, onNo));
|
|
totalRecords713++;
|
|
|
|
records.push(this.build714(material, qtyOnHand, vdaUnit, dqNo || currentDn));
|
|
totalRecords714++;
|
|
}
|
|
|
|
// EDL35 (Stock balances) generated for QTY+145
|
|
if (qty145) {
|
|
records.push(this.build712(msgDate));
|
|
totalRecords712++;
|
|
|
|
// Stock reports use Vorgangsschlüssel 35
|
|
records.push(this.build713('00000000', invDate, storageLocation || unloadingPoint, '35', targetId, onNo));
|
|
totalRecords713++;
|
|
|
|
records.push(this.build714(material, qtyCumulative, vdaUnit, dqNo));
|
|
totalRecords714++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
records.push(this.build719(
|
|
totalRecords712, totalRecords713, totalRecords714,
|
|
totalRecords712, totalRecords713, totalRecords714
|
|
));
|
|
|
|
return records.join('\r\n') + '\r\n';
|
|
}
|
|
|
|
// ─── Split EDIFACT segments into UNH message groups ──────────────
|
|
static splitMessages(segments, getVal) {
|
|
const groups = [];
|
|
let current = null;
|
|
|
|
for (const seg of segments) {
|
|
if (seg.tag === 'UNH') {
|
|
if (current) groups.push(current);
|
|
current = [seg];
|
|
} else if (seg.tag === 'UNT') {
|
|
if (current) {
|
|
current.push(seg);
|
|
groups.push(current);
|
|
current = null;
|
|
}
|
|
} else if (current) {
|
|
current.push(seg);
|
|
}
|
|
}
|
|
if (current) groups.push(current);
|
|
return groups;
|
|
}
|
|
|
|
// ─── Format Helpers ──────────────────────────────────────────────
|
|
static padN(val, len) {
|
|
let v = String(val || '').replace(/[^0-9]/g, '');
|
|
return v.padStart(len, '0').slice(-len);
|
|
}
|
|
|
|
static padA(val, len) {
|
|
return String(val || '').padEnd(len, ' ').slice(0, len);
|
|
}
|
|
|
|
static padID(val, len) {
|
|
let v = String(val || '').replace(/[^0-9]/g, '');
|
|
// Prefer the front of the ID if it exceeds VDA limits
|
|
return v.padEnd(len, ' ').slice(0, len).trim().padStart(len, '0');
|
|
}
|
|
|
|
// ─── Record Builders ─────────────────────────────────────────────
|
|
|
|
/**
|
|
* 711 - Vorsatz Lieferschein- und Transportdaten
|
|
* Version 03
|
|
* Pos 01: 1-3 (3) Konstant "711"
|
|
* Pos 02: 4-5 (2) Version "03"
|
|
* Pos 03: 6-14 (9) Daten-Empfänger-Nummer
|
|
* Pos 04: 15-23 (9) Daten-Sender-Nummer
|
|
* Pos 05: 24-28 (5) Übertragungs-Nummer-Alt
|
|
* Pos 06: 29-33 (5) Übertragungs-Nummer-Neu
|
|
* Pos 07: 34-39 (6) Übertragungsdatum (JJMMTT)
|
|
*/
|
|
static build711(targetId, sourceId, transNo1, transNo2, date, config) {
|
|
let r = '71102';
|
|
|
|
// Custom override for targetId (e.g. "10305") via Mapping or Setting
|
|
let target = targetId;
|
|
if (config && window.EDIBridge.ConfigParser) {
|
|
const parser = window.EDIBridge.ConfigParser;
|
|
// 1. Try specific mapping (e.g. 8810 -> 10305)
|
|
const mapped = parser.findInSection(config, 'VDA711_ID_MAPPING', targetId);
|
|
if (mapped) {
|
|
target = mapped;
|
|
} else {
|
|
// 2. Fallback to global setting if no specific mapping exists
|
|
target = parser.lookupGeneral(config, 'OWN_CUSTOMER_NUMBER', targetId);
|
|
}
|
|
}
|
|
|
|
r += this.padA(target, 9); // Daten-Empfänger
|
|
r += this.padA(sourceId, 9); // Daten-Sender
|
|
r += this.padN(transNo1, 5);
|
|
r += this.padN(transNo2, 5);
|
|
r += this.padN(date, 6);
|
|
r += ''.padEnd(128 - r.length, ' ');
|
|
return r.slice(0, 128);
|
|
}
|
|
|
|
/**
|
|
* 712 - Einmalige Daten des Transports
|
|
* Version 03
|
|
* Pos 01: 1-3 (3) Konstant "712"
|
|
* Pos 02: 4-5 (2) Version "03"
|
|
* Pos 03: 6-13 (8) Sendungs-Ladungs-Bezugs-Nummer
|
|
* Pos 04: 14-16 (3) Werk Lieferant
|
|
* Pos 05: 17-30 (14) Frachtführer Name/Nummer
|
|
* Pos 06: 31-36 (6) Übergabedatum (JJMMTT)
|
|
* Pos 07: 37-40 (4) Übergabezeit (HHMM)
|
|
* Pos 08: 41-47 (7) Sendungs-Gewicht Brutto (kg)
|
|
* Pos 09: 48-54 (7) Sendungs-Gewicht Netto (kg)
|
|
*/
|
|
static build712(date) {
|
|
let r = '71202';
|
|
r += this.padN('0', 8); // Sendungs-Ladungs-Bezug-Nr
|
|
r += this.padA('', 3); // Werk Lieferant
|
|
r += this.padA('', 14); // Frachtführer
|
|
r += this.padN(date, 6); // Datum
|
|
r += this.padN('0', 4); // Zeit
|
|
r += this.padN('0', 7); // Brutto
|
|
r += this.padN('0', 7); // Netto
|
|
r += ''.padEnd(128 - r.length, ' ');
|
|
return r.slice(0, 128);
|
|
}
|
|
|
|
/**
|
|
* 713 - Einmalige Daten des Lieferscheins (Kopf)
|
|
* Version 03
|
|
* Pos 01: 1-3 (3) Konstant "713"
|
|
* Pos 02: 4-5 (2) Version "03"
|
|
* Pos 03: 6-13 (8) Lieferschein-Nummer
|
|
* Pos 04: 14-19 (6) Versanddatum (JJMMTT)
|
|
* Pos 05: 20-24 (5) Abladestelle
|
|
* Pos 06: 25-26 (2) Versandart
|
|
* Pos 08: 31-42 (12) Bestellnummer
|
|
* Pos 09: 43-44 (2) Vorgangs-Schlüssel (EDL)
|
|
* Pos 11: 49-51 (3) Werk Kunde
|
|
* Pos 16: 77-85 (9) Lieferanten-Nummer
|
|
*/
|
|
static build713(dnNo, date, unloadingPoint, edlType, targetId, orderNo) {
|
|
let r = '713';
|
|
// User requested shifting DN left by 2 chars (occupying the version field area to allow 10 digits)
|
|
r += this.padID(dnNo, 10); // Lieferschein-Nr (4-13)
|
|
r += this.padN(date, 6); // Datum (14-19)
|
|
r += this.padA(unloadingPoint, 5); // Abladestelle (20-24)
|
|
r += '00'; // Versandart (25-26)
|
|
r += ' '; // Filler (27-30)
|
|
r += this.padA(orderNo, 12); // Bestellnummer (31-42)
|
|
const validKeys = ['30', '32', '33', '35', '36', '40'];
|
|
r += this.padN(validKeys.includes(edlType) ? edlType : '00', 2); // Vorgangsschlüssel (43-44)
|
|
r += ' '; // Filler (45-48)
|
|
r += this.padA(targetId.slice(0, 3), 3); // Werk Kunde (49-51)
|
|
r += ''.padEnd(25, ' '); // Filler to index 76 (52-76)
|
|
r += this.padA(targetId, 9); // Lieferanten-Nummer (Pos 16, index 76-84)
|
|
r += ''.padEnd(25, ' '); // Filler to index 110 (85-109)
|
|
r += this.padA(dnNo, 14); // Dokument-Nr. Kunde (Pos 20, index 110-123)
|
|
r += ''.padEnd(4, ' '); // Filler to 128
|
|
return r.slice(0, 128);
|
|
}
|
|
|
|
/**
|
|
* 714 - Lieferscheinpositionsdaten
|
|
* Version 03
|
|
* Pos 01: 1-3 (3) Konstant "714"
|
|
* Pos 02: 4-5 (2) Version "03"
|
|
* Pos 03: 6-27 (22) Sachnummer-Kunde
|
|
* Pos 04: 28-49 (22) Sachnummer-Lieferant
|
|
* Pos 05: 50-52 (3) Ursprungsland
|
|
* Pos 06: 53-65 (13) Liefermenge (mit 3 impliziten Dezimalstellen, VDA Standard)
|
|
* Pos 07: 66-67 (2) Mengeneinheit
|
|
* Pos 12: 87-89 (3) Positions-Nummer Lieferschein
|
|
* Pos 14: 91-105 (15) Chargen-Nummer
|
|
* Pos 22: 121-128 (8) Ursprung-Lieferschein-Nr
|
|
*/
|
|
static build714(custMat, qty, unit, refNo) {
|
|
const qtyEncoded = Math.round(qty * 1000);
|
|
let r = '71403';
|
|
r += this.padA(custMat, 22); // Sachnummer-Kunde
|
|
r += this.padA('', 22); // Sachnummer-Lieferant
|
|
r += '000'; // Ursprungsland
|
|
r += this.padN(String(qtyEncoded), 13); // Liefermenge (13 Stellen)
|
|
r += this.padA(unit, 2); // ME
|
|
r += ''.padEnd(19, ' '); // Filler to 86
|
|
r += this.padN('0', 3); // Pos-Nr (87-89)
|
|
r += ' '; // Filler
|
|
r += this.padA('', 15); // Charge (91-105)
|
|
r += ' '; // Filler to 120
|
|
r += this.padID(refNo, 8); // Ursprung-LS (121-128)
|
|
return r.slice(0, 128);
|
|
}
|
|
|
|
/**
|
|
* 719 - Nachsatz Lieferschein- und Transportdaten
|
|
* Version 02
|
|
*/
|
|
static build719(cnt712, cnt713, cnt714, tot712, tot713, tot714) {
|
|
let r = '71902';
|
|
r += this.padN('0', 8);
|
|
r += this.padN(String(1), 7); // Zähler 711
|
|
r += this.padN(String(cnt712), 7);
|
|
r += this.padN(String(cnt713), 7);
|
|
r += this.padN(String(cnt714), 7);
|
|
r += this.padN('0', 7); // Zähler 715
|
|
r += this.padN('0', 7); // Zähler 716
|
|
r += this.padN('0', 7); // Zähler 718
|
|
r += this.padN(String(1), 7); // Zähler 719
|
|
r += this.padN('0', 7); // Zähler 717
|
|
r += ''.padEnd(128 - r.length, ' ');
|
|
return r.slice(0, 128);
|
|
}
|
|
}
|
|
|
|
window.EDIBridge.InvrptToVDA4913 = InvrptToVDA4913;
|