/**
* EDI Document Viewer
* Handles parsing and rendering of DELFOR and VDA documents in a human-readable format.
*/
window.EDIBridge = window.EDIBridge || {};
class Viewer {
constructor(app) {
this.app = app;
this.initElements();
this.bindEvents();
}
initElements() {
this.dropZone = document.getElementById('viewerDropZone');
this.fileInput = document.getElementById('viewerFileInput');
this.renderArea = document.getElementById('viewerDocumentRender');
this.btnPrint = document.getElementById('btnViewerPrint');
this.btnBack = document.getElementById('btnViewerBack');
this.dropText = document.getElementById('viewerDropText');
}
bindEvents() {
if (this.dropZone) {
this.dropZone.onclick = (e) => { e.stopPropagation(); if (this.fileInput) this.fileInput.click(); };
this.dropZone.ondragover = (e) => { e.preventDefault(); this.dropZone.classList.add('dragover'); };
this.dropZone.ondragleave = () => this.dropZone.classList.remove('dragover');
this.dropZone.ondrop = (e) => {
e.preventDefault();
this.dropZone.classList.remove('dragover');
this.handleFile(e.dataTransfer.files[0]);
};
}
if (this.fileInput) {
this.fileInput.onchange = (e) => this.handleFile(e.target.files[0]);
}
}
showViewer(html) {
this.renderArea.innerHTML = html;
this.dropZone.style.display = 'none';
this.renderArea.style.display = 'block';
if (this.btnPrint) this.btnPrint.style.display = 'inline-flex';
if (this.btnBack) this.btnBack.style.display = 'inline-flex';
if (window.lucide) {
try { lucide.createIcons(); } catch (e) { }
}
}
handleFile(file) {
if (!file) return;
this.fileName = file.name;
const reader = new FileReader();
reader.onload = (e) => {
this.processContent(e.target.result);
};
reader.readAsText(file);
}
processContent(content) {
const trimmed = content.trim();
const upper = trimmed.toUpperCase();
try {
if (upper.startsWith('UNA') || upper.startsWith('UNB')) {
// Determine EDIFACT type
if (upper.includes('DELFOR')) {
this.renderDelfor(trimmed);
} else if (upper.includes('DESADV')) {
this.renderDesadv(trimmed);
} else if (upper.includes('INVRPT')) {
this.renderInvrpt(trimmed);
} else {
alert('Dieser Nachrichtentyp wird aktuell vom Viewer nicht vollständig unterstützt. (Nur DELFOR, DESADV, INVRPT)');
// Fallback to basic view if possible?
}
} else if (upper.startsWith('711') || upper.startsWith('511')) {
// VDA Parsing
this.renderVDA(trimmed);
} else {
alert('Unbekanntes Dateiformat.');
}
} catch (error) {
console.error(error);
alert('Fehler beim Parsen der Datei: ' + error.message);
}
}
renderDelfor(content) {
const parser = window.EDIBridge.DelforParser;
if (!parser) throw new Error('DelforParser nicht geladen.');
const parsed = parser.parse(content);
// Extract basic data for the template
const data = this.extractDelforData(parsed);
this.renderTemplate(data);
}
extractDelforData(parsed) {
const segments = parsed.segments;
let header = {
docNo: '',
date: '',
buyer: { id: '', name: '', street: '', city: '' },
seller: { id: '', name: '', street: '', city: '' },
shipTo: { id: '', name: '', street: '', city: '', abladestelle: '' }
};
let articles = [];
let currentArticle = null;
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;
return '';
};
for (let i = 0; i < segments.length; i++) {
const seg = segments[i];
// Header extraction
if (seg.tag === 'BGM') {
header.docNo = getVal(seg, 1, 0); // e.g. 177754999
} else if (seg.tag === 'DTM') {
const qual = getVal(seg, 0, 0);
if (qual === '137') {
const dateStr = getVal(seg, 0, 1);
if (dateStr.length >= 8) {
header.date = dateStr.substring(6, 8) + '.' + dateStr.substring(4, 6) + '.' + dateStr.substring(0, 4);
}
}
} else if (seg.tag === 'NAD') {
const qual = getVal(seg, 0, 0);
const nadObj = {
id: getVal(seg, 1, 0), // party id
name: getVal(seg, 3, 0),
street: getVal(seg, 4, 0),
city: getVal(seg, 5, 0) + ' ' + getVal(seg, 7, 0) // city + zip (approx)
};
if (qual === 'BY') header.buyer = nadObj;
else if (qual === 'SE') header.seller = nadObj;
else if (qual === 'ST') header.shipTo = nadObj;
} else if (seg.tag === 'LOC') {
const qual = getVal(seg, 0, 0);
if (qual === '11') {
header.shipTo.abladestelle = getVal(seg, 1, 0);
}
}
// Articles & Schedules Extraction
else if (seg.tag === 'LIN') {
if (currentArticle) articles.push(currentArticle);
currentArticle = {
itemNumber: getVal(seg, 2, 0),
buyerItem: getVal(seg, 2, 0),
desc: '',
orderNo: '',
eingangsfortschrittszahl: 0,
nullStellung: '',
deliveries: [],
schedules: []
};
} else if (seg.tag === 'PIA' && currentArticle) {
// Secondary IDs if available
} else if (seg.tag === 'IMD' && currentArticle) {
if (getVal(seg, 2, 3)) currentArticle.desc = getVal(seg, 2, 3);
} else if (seg.tag === 'RFF' && currentArticle && currentArticle.schedules.length === 0) {
const qual = getVal(seg, 0, 0);
if (['ON', 'CR'].includes(qual)) currentArticle.orderNo = getVal(seg, 0, 1);
} else if (seg.tag === 'QTY' && currentArticle) {
const qual = getVal(seg, 0, 0);
// Schedule quantities (113, 1) or cumulative (194)
if (['113', '1'].includes(qual)) {
// Find DTMs and QTY+3 (Target EFZ)
let deliveryDate = '';
let pickupDate = '';
let targetEFZ = null;
for (let j = i + 1; j < segments.length && j < i + 10; j++) {
const nextSeg = segments[j];
if (nextSeg.tag === 'DTM') {
const dtmQual = getVal(nextSeg, 0, 0);
const dtmVal = getVal(nextSeg, 0, 1);
if (['2', '160', '132'].includes(dtmQual)) deliveryDate = dtmVal;
else if (dtmQual === '10') pickupDate = dtmVal;
} else if (nextSeg.tag === 'QTY' && getVal(nextSeg, 0, 0) === '3') {
targetEFZ = parseFloat(getVal(nextSeg, 0, 1)) || 0;
} else if (['LIN', 'QTY', 'SCC'].includes(nextSeg.tag)) {
// Stop if we hit a new loop element
if (nextSeg.tag === 'QTY' && ['113', '1'].includes(getVal(nextSeg, 0, 0))) break;
if (nextSeg.tag === 'LIN' || nextSeg.tag === 'SCC') break;
}
}
const formatEdiDate = (d) => {
if (!d || d.length < 8) return d;
return d.substring(6, 8) + '.' + d.substring(4, 6) + '.' + d.substring(0, 4);
};
const primaryDate = pickupDate || deliveryDate;
let monthKey = '';
if (primaryDate.length >= 8) {
monthKey = primaryDate.substring(4, 6) + '.' + primaryDate.substring(0, 4);
}
currentArticle.schedules.push({
date: formatEdiDate(primaryDate),
deliveryDate: formatEdiDate(deliveryDate),
pickupDate: formatEdiDate(pickupDate),
monthKey: monthKey,
qty: parseFloat(getVal(seg, 0, 1)) || 0,
unit: getVal(seg, 0, 2),
targetEFZ: targetEFZ
});
}
// Deliveries / Lieferscheine (12 = Despatch Qty, 48 = Received Qty, 194 = Used by IFM)
else if (['12', '48', '194'].includes(qual)) {
let dtmStr = '';
for (let j = i + 1; j < segments.length && j < i + 4; j++) {
if (segments[j].tag === 'DTM') {
dtmStr = getVal(segments[j], 0, 1);
break;
}
}
let refStr = '';
// Look around for RFF
for (let j = i - 3; j < i + 4; j++) {
if (j >= 0 && j < segments.length && segments[j].tag === 'RFF') {
const rffQual = getVal(segments[j], 0, 0);
if (['DQ', 'AAK', 'SI', 'AAU'].includes(rffQual)) {
refStr = getVal(segments[j], 0, 1);
break;
}
}
}
let fmtDate = dtmStr;
if (dtmStr.length >= 8) {
fmtDate = dtmStr.substring(6, 8) + '.' + dtmStr.substring(4, 6) + '.' + dtmStr.substring(0, 4);
}
currentArticle.deliveries.push({
docNo: refStr || '-',
date: fmtDate,
qty: parseFloat(getVal(seg, 0, 1)) || 0,
unit: getVal(seg, 0, 2)
});
}
// Cumulative received (70 or 71 -> Eingangsfortschrittszahl)
else if (['70', '71'].includes(qual)) {
currentArticle.eingangsfortschrittszahl = parseFloat(getVal(seg, 0, 1)) || 0;
// Look for DTM+51 for Null-Stellung
for (let j = i + 1; j < segments.length && j < i + 4; j++) {
if (segments[j].tag === 'DTM') {
const dtmQual = getVal(segments[j], 0, 0);
if (dtmQual === '51') {
const dStr = getVal(segments[j], 0, 1);
if (dStr.length >= 8) {
currentArticle.nullStellung = dStr.substring(6, 8) + '.' + dStr.substring(4, 6) + '.' + dStr.substring(0, 4);
}
break;
}
}
}
}
}
}
if (currentArticle) articles.push(currentArticle);
return { header, articles };
}
renderDesadv(content) {
const parser = window.EDIBridge.DelforParser;
if (!parser) throw new Error('Parser nicht geladen.');
const parsed = parser.parse(content);
const data = this.extractDesadvData(parsed);
this.renderDesadvTemplate(data);
}
extractDesadvData(parsed) {
const segments = parsed.segments;
let header = {
docNo: '', date: '',
deliveryDate: '',
buyer: { id: '', name: '', street: '', city: '' },
seller: { id: '', name: '', street: '', city: '' },
shipTo: { id: '', name: '', street: '', city: '', abladestelle: '' }
};
let articles = [];
let currentArticle = null;
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;
return '';
};
const formatDate = (dateStr) => {
if (!dateStr || dateStr.length < 8) return dateStr;
return dateStr.substring(6, 8) + '.' + dateStr.substring(4, 6) + '.' + dateStr.substring(0, 4);
};
for (let i = 0; i < segments.length; i++) {
const seg = segments[i];
if (seg.tag === 'BGM') {
header.docNo = getVal(seg, 1, 0);
} else if (seg.tag === 'DTM') {
const qual = getVal(seg, 0, 0);
const dateStr = getVal(seg, 0, 1);
if (qual === '137') {
header.date = formatDate(dateStr);
} else if (qual === '132' || qual === '11') {
header.deliveryDate = formatDate(dateStr);
}
} else if (seg.tag === 'NAD') {
const qual = getVal(seg, 0, 0);
const nadObj = {
id: getVal(seg, 1, 0),
name: getVal(seg, 3, 0),
street: getVal(seg, 4, 0),
city: getVal(seg, 5, 0) + ' ' + getVal(seg, 7, 0),
abladestelle: ''
};
if (qual === 'BY') header.buyer = nadObj;
else if (qual === 'SE') header.seller = nadObj;
else if (qual === 'DP' || qual === 'ST' || qual === 'CN') header.shipTo = nadObj;
} else if (seg.tag === 'LOC' && getVal(seg, 0, 0) === '11') {
header.shipTo.abladestelle = getVal(seg, 1, 0);
} else if (seg.tag === 'LIN') {
if (currentArticle) articles.push(currentArticle);
currentArticle = {
itemNumber: getVal(seg, 2, 0),
supplierMat: '',
revisionLevel: '',
desc: '', orderNo: '', qty: 0, unit: 'PCE', batchNo: '', packages: []
};
} else if (seg.tag === 'PIA' && currentArticle) {
const qual = getVal(seg, 0, 0);
if (qual === '1' || qual === '5') {
const subQual = getVal(seg, 1, 1);
if (subQual === 'SA') currentArticle.supplierMat = getVal(seg, 1, 0);
else if (subQual === 'EC') currentArticle.revisionLevel = getVal(seg, 1, 0);
else if (subQual === 'NB' || subQual === 'BT') currentArticle.batchNo = getVal(seg, 1, 0);
}
} else if (seg.tag === 'GIR' && currentArticle) {
if (getVal(seg, 0, 0) === 'V1' || getVal(seg, 1, 1) === 'BT' || getVal(seg, 1, 1) === 'NB') {
currentArticle.batchNo = getVal(seg, 1, 0);
}
} else if (seg.tag === 'IMD' && currentArticle) {
if (getVal(seg, 2, 3)) currentArticle.desc = getVal(seg, 2, 3);
else if (getVal(seg, 2, 0)) currentArticle.desc = getVal(seg, 2, 0);
} else if (seg.tag === 'RFF' && currentArticle) {
const qual = getVal(seg, 0, 0);
if (['ON', 'CR'].includes(qual)) currentArticle.orderNo = getVal(seg, 0, 1);
} else if (seg.tag === 'QTY' && currentArticle) {
const qual = getVal(seg, 0, 0);
if (qual === '12' || qual === '1') {
currentArticle.qty = parseFloat(getVal(seg, 0, 1)) || 0;
const unit = getVal(seg, 0, 2);
if (unit) currentArticle.unit = unit;
}
} else if (seg.tag === 'PAC' && currentArticle) {
currentArticle.packages.push({
qty: parseInt(getVal(seg, 0, 0)) || 1,
type: getVal(seg, 2, 0),
custType: getVal(seg, 2, 1),
name: getVal(seg, 2, 3) || getVal(seg, 2, 0)
});
}
}
if (currentArticle) articles.push(currentArticle);
return { header, articles };
}
renderDesadvTemplate(data) {
let html = `
KÄUFER: ${data.header.buyer.id}
${data.header.buyer.name}
${data.header.buyer.street}
${data.header.buyer.city}
VERKÄUFER: ${data.header.seller.id}
${data.header.seller.name}
${data.header.seller.street}
${data.header.seller.city}
WARENEMPFÄNGER: ${data.header.shipTo.id}
${data.header.shipTo.name}
${data.header.shipTo.street}
${data.header.shipTo.city}
Abladestelle: ${data.header.shipTo.abladestelle || '-'}
`;
data.articles.forEach((art, index) => {
html += `
#${index + 1}
Kunde:
${art.itemNumber}
${art.supplierMat ? `
Lieferant:
${art.supplierMat}
` : ''}
${art.desc || 'Keine Beschreibung'}
${art.revisionLevel ? `Rev: ${art.revisionLevel}` : ''}
${art.batchNo ? `Charge: ${art.batchNo}` : ''}
${this.formatNumber(art.qty)}
${art.unit}
Bestellung:
${art.orderNo || '-'}
${art.packages.length > 0 ? `
${art.packages.map(p => `
${p.qty}x ${p.name || p.type}
(${p.type}${p.custType ? ` / ${p.custType}` : ''})
`).join('')}
` : ''}
`;
});
html += `
`;
this.showViewer(html);
}
async renderInvrpt(content) {
let parsed;
if (window.electronAPI && window.electronAPI.parseEdifactLib) {
try {
const libResult = await window.electronAPI.parseEdifactLib(content);
if (libResult.success && libResult.data) {
parsed = { segments: libResult.data };
}
} catch (e) {
console.warn('ts-edifact parsing fall-back to basic parser in viewer:', e);
}
}
if (!parsed) {
const parser = window.EDIBridge.DelforParser;
if (!parser) throw new Error('Parser nicht geladen.');
parsed = parser.parse(content);
}
const data = this.extractInvrptData(parsed);
this.renderInvrptTemplate(data);
}
extractInvrptData(parsed) {
const segments = parsed.segments;
let header = {
docNo: '', date: '',
buyer: { id: '', name: '', street: '', city: '' },
seller: { id: '', name: '', street: '', city: '' }
};
let items = [];
let currentItem = null;
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;
return '';
};
for (let i = 0; i < segments.length; i++) {
const seg = segments[i];
if (seg.tag === 'BGM') {
header.docNo = getVal(seg, 1, 0);
} else if (seg.tag === 'DTM') {
const qual = getVal(seg, 0, 0);
if (qual === '137') {
const dateStr = getVal(seg, 0, 1);
if (dateStr.length >= 8) {
header.date = dateStr.substring(6, 8) + '.' + dateStr.substring(4, 6) + '.' + dateStr.substring(0, 4);
}
} else if (qual === '179' && currentItem && currentItem.inventories.length > 0) {
const currentInv = currentItem.inventories[currentItem.inventories.length - 1];
const dateStr = getVal(seg, 0, 1);
if (dateStr.length >= 8) {
currentInv.date = dateStr.substring(6, 8) + '.' + dateStr.substring(4, 6) + '.' + dateStr.substring(0, 4);
}
}
} else if (seg.tag === 'NAD') {
const qual = getVal(seg, 0, 0);
const nadObj = {
id: getVal(seg, 1, 0),
name: getVal(seg, 3, 0),
street: getVal(seg, 4, 0),
city: getVal(seg, 5, 0) + ' ' + getVal(seg, 7, 0)
};
if (qual === 'BY' || qual === 'OY' || qual === 'GM') header.buyer = nadObj;
else if (qual === 'SE' || qual === 'SU' || qual === 'WH') header.seller = nadObj;
} else if (seg.tag === 'LIN') {
if (currentItem) items.push(currentItem);
currentItem = {
itemNumber: getVal(seg, 2, 0),
supplierMat: '',
revisionLevel: '',
desc: '', batchNo: '',
inventories: []
};
} else if (seg.tag === 'PIA' && currentItem) {
const qual = getVal(seg, 0, 0);
if (qual === '1' || qual === '5') {
const subQual = getVal(seg, 1, 1);
if (subQual === 'SA') currentItem.supplierMat = getVal(seg, 1, 0);
else if (subQual === 'EC') currentItem.revisionLevel = getVal(seg, 1, 0);
else if (subQual === 'NB' || subQual === 'BT') currentItem.batchNo = getVal(seg, 1, 0);
}
} else if (seg.tag === 'IMD' && currentItem) {
if (getVal(seg, 2, 3)) currentItem.desc = getVal(seg, 2, 3);
} else if (seg.tag === 'INV' && currentItem) {
currentItem.inventories.push({
direction: getVal(seg, 0, 0),
reason: getVal(seg, 2, 0),
movementQty: null,
balanceQty: null,
unit: 'PCE',
location: '',
date: '',
qualifier: ''
});
} else if (seg.tag === 'QTY' && currentItem && currentItem.inventories.length > 0) {
const currentInv = currentItem.inventories[currentItem.inventories.length - 1];
const qual = getVal(seg, 0, 0);
const qtyVal = parseFloat(getVal(seg, 0, 1)) || 0;
const unit = getVal(seg, 0, 2);
if (unit) currentInv.unit = unit;
if (qual === '156') {
currentInv.movementQty = qtyVal;
currentInv.qualifier = '156';
} else if (qual === '145') {
currentInv.balanceQty = qtyVal;
if (!currentInv.qualifier) currentInv.qualifier = '145';
} else if (qual === '1') {
currentInv.balanceQty = qtyVal;
if (!currentInv.qualifier) currentInv.qualifier = '1';
}
} else if (seg.tag === 'LOC' && currentItem && currentItem.inventories.length > 0) {
const currentInv = currentItem.inventories[currentItem.inventories.length - 1];
if (getVal(seg, 0, 0) === '18' || getVal(seg, 0, 0) === '11') {
currentInv.location = getVal(seg, 1, 0);
}
}
}
if (currentItem) items.push(currentItem);
return { header, items };
}
renderInvrptTemplate(data) {
let html = `
KÄUFER: ${data.header.buyer.id}
${data.header.buyer.name}
${data.header.buyer.street}
${data.header.buyer.city}
VERKÄUFER: ${data.header.seller.id}
${data.header.seller.name}
${data.header.seller.street}
${data.header.seller.city}
`;
const getReasonText = (dir, reason) => {
if (reason === '1') return 'Wareneingang';
if (reason === '2') return 'Lieferung';
if (reason === '3') return 'Ausschuss / Verschrottung';
if (reason === '4') return 'Bestandsdifferenz';
if (reason === '5') return 'Umlagerung';
if (reason === '6') return 'Recycling';
if (reason === '7') return 'Storno vorheriger Bewegung';
if (reason === '11') return 'Verbrauch aus dem Lager';
if (dir === '1') return 'Bewegung aus dem Lager (Ausgang)';
if (dir === '2') return 'Bewegung ins Lager (Eingang)';
return 'Bestandsmeldung';
};
data.items.forEach((item, index) => {
html += `
#${index + 1}
Kunde:
${item.itemNumber}
${item.supplierMat ? `
Lieferant:
${item.supplierMat}
` : ''}
${item.desc || 'Keine Beschreibung'}
${item.revisionLevel ? `Rev: ${item.revisionLevel}` : ''}
${item.batchNo ? `Charge: ${item.batchNo}` : ''}
Bestandsdetails
| Datum |
Bewegungsgrund |
Bewegungsmenge |
Aktueller Bestand |
Status |
${item.inventories && item.inventories.length > 0 ? item.inventories.map((inv, idx) => {
const reasonText = getReasonText(inv.direction, inv.reason);
const movQtyStr = inv.movementQty !== null ? `${this.formatNumber(inv.movementQty)} ${inv.unit}` : '-';
const balQtyStr = inv.balanceQty !== null ? `${this.formatNumber(inv.balanceQty)} ${inv.unit}` : '-';
let statusText = '';
if (inv.movementQty !== null && inv.balanceQty !== null) {
statusText = `Nach der Bewegung verbleiben ${this.formatNumber(inv.balanceQty)} ${inv.unit} im Bestand.`;
} else if (inv.movementQty === null && inv.balanceQty !== null) {
statusText = `Dies ist eine Meldung des aktuellen Lagerbestands ohne eine Warenbewegung.`;
} else if (inv.movementQty !== null) {
statusText = `Bewegung erfasst (kein resultierender Bestand gemeldet).`;
}
return `
| ${inv.date || '-'} |
${reasonText} ${inv.location ? `${inv.location}` : ''} |
${movQtyStr} |
${balQtyStr} |
${statusText} |
`;
}).join('') : '| Keine Bestandsdaten gefunden |
'}
`;
});
html += `
`;
this.showViewer(html);
}
renderVDA(content) {
const parser = window.EDIBridge.VDAParser;
if (!parser) throw new Error('VDAParser nicht geladen.');
const parsed = parser.parse(content);
// Ensure there is at least one interchange
if (!parsed.interchanges || parsed.interchanges.length === 0) {
throw new Error('Kein gültiger VDA-Inhalt gefunden.');
}
const interchange = parsed.interchanges[0];
// Check if it's VDA 4905 (511 header)
if (interchange.header && content.startsWith('511')) {
const data = this.extractVda4905Data(interchange);
this.renderTemplate(data);
}
// Check if it's VDA 4913 (711 header)
else if (interchange.header && content.startsWith('711')) {
const data = this.extractVda4913Data(interchange);
this.renderVda4913Template(data);
}
else {
alert('Aktuell werden nur VDA 4905 und VDA 4913 Formate im Viewer unterstützt.');
}
}
extractVda4905Data(interchange) {
let header = {
docNo: interchange.header.newTransNo,
oldDocNo: interchange.header.oldTransNo,
date: this.formatVdaDate(interchange.header.date),
buyer: { id: interchange.header.customerId, name: '', street: '', city: '' },
seller: { id: interchange.header.supplierId, name: '', street: '', city: '' },
shipTo: { id: '', name: '', street: '', city: '', abladestelle: '', plant: '' }
};
let articles = [];
if (interchange.articles) {
interchange.articles.forEach(art => {
header.shipTo.abladestelle = art.article.unloadingPoint;
header.shipTo.plant = art.article.plant;
let articleData = {
itemNumber: art.article.suppMat,
buyerItem: art.article.custMat,
desc: '', // Not typically in VDA 4905
orderNo: art.article.orderNo,
eingangsfortschrittszahl: art.efz || 0,
nullStellung: art.nullStellung ? this.formatVdaDate(art.nullStellung) : '',
deliveries: art.deliveries || [],
schedules: []
};
// Format deliveries
articleData.deliveries = articleData.deliveries.map(d => ({
docNo: d.docNo,
date: this.formatVdaDate(d.date),
qty: d.qty,
unit: d.unit
}));
// Process schedules
if (art.schedules) {
art.schedules.forEach(sched => {
const fmtDate = this.formatVdaDate(sched.date);
let monthKey = '';
if (sched.date && sched.date.length === 6) {
monthKey = sched.date.substring(2, 4) + '.' + '20' + sched.date.substring(0, 2);
}
articleData.schedules.push({
date: fmtDate,
monthKey: monthKey,
qty: sched.qty,
unit: art.article.unit || 'ST'
});
});
}
articles.push(articleData);
});
}
return { header, articles };
}
extractVda4913Data(interchange) {
let header = {
docNo: interchange.header.transNo2,
oldDocNo: interchange.header.transNo1,
date: this.formatVdaDate(interchange.header.date),
buyer: { id: interchange.header.targetId, name: '', street: '', city: '' },
seller: { id: interchange.header.sourceId, name: '', street: '', city: '' }
};
let transports = [];
if (interchange.transports) {
interchange.transports.forEach(t => {
let transport = {
carrier: t.data.carrierName,
date: this.formatVdaDate(t.data.date),
grossWeight: t.data.grossWeight,
netWeight: t.data.netWeight,
deliveryNotes: []
};
t.deliveryNotes.forEach(dn => {
let dnote = {
dnNo: dn.header.dnNo,
date: this.formatVdaDate(dn.header.date),
unloadingPoint: dn.header.unloadingPoint,
orderNo: dn.header.orderNo,
positions: [],
texts: dn.texts || []
};
dn.positions.forEach(pos => {
let position = {
suppMat: pos.data.suppMat,
custMat: pos.data.custMat,
qty: pos.data.qty,
unit: pos.data.unit,
refNo: pos.data.refNo,
batchNo: pos.data.batchNo || '',
revisionLevel: pos.data.revisionLevel || '',
packaging: pos.packaging || []
};
dnote.positions.push(position);
});
transport.deliveryNotes.push(dnote);
});
transports.push(transport);
});
}
return { header, transports };
}
getWeekDateRange(year, week) {
const simple = new Date(year, 0, 1 + (week - 1) * 7);
const dow = simple.getDay();
const ISOweekStart = simple;
if (dow <= 4)
ISOweekStart.setDate(simple.getDate() - simple.getDay() + 1);
else
ISOweekStart.setDate(simple.getDate() + 8 - simple.getDay());
const ISOweekEnd = new Date(ISOweekStart);
ISOweekEnd.setDate(ISOweekStart.getDate() + 6);
const fmt = (d) => String(d.getDate()).padStart(2, '0') + '.' + String(d.getMonth() + 1).padStart(2, '0') + '.' + String(d.getFullYear()).substring(2);
return `W ${fmt(ISOweekStart)} - ${fmt(ISOweekEnd)} (KW: ${String(week).padStart(2, '0')
})`;
}
getMonthDateRange(year, month) {
const start = new Date(year, month - 1, 1);
const end = new Date(year, month, 0);
const fmt = (d) => String(d.getDate()).padStart(2, '0') + '.' + String(d.getMonth() + 1).padStart(2, '0') + '.' + String(d.getFullYear()).substring(2);
return `M ${fmt(start)} - ${fmt(end)} (MO: ${String(month).padStart(2, '0')})`;
}
formatVdaDate(rawDate) {
if (!rawDate) return '';
let val = typeof rawDate === 'string' ? rawDate : '';
if (val === '000000' || val.trim() === '') return '';
// Handle week/month (length 6)
if (val.length === 6) {
// Check for format YY00WW or YY00MM
if (val.substring(2, 4) === '00') {
const yyStr = val.substring(0, 2);
const partStr = val.substring(4, 6);
const year = 2000 + parseInt(yyStr, 10);
const part = parseInt(partStr, 10);
if (part > 12) {
return this.getWeekDateRange(year, part);
} else {
return this.getMonthDateRange(year, part);
}
} else if (val.endsWith(' ') || val.endsWith('00')) {
// If it's YYMM00 or YYMM
let trimVal = val.trim();
if (trimVal.endsWith('00')) trimVal = trimVal.substring(0, 4);
if (trimVal.length === 4) {
let yyStr = trimVal.substring(0, 2);
let partStr = trimVal.substring(2, 4);
let year = 2000 + parseInt(yyStr, 10);
let part = parseInt(partStr, 10);
if (part > 12) {
return this.getWeekDateRange(year, part);
} else {
return this.getMonthDateRange(year, part);
}
}
}
// Standard YYMMDD format
return val.substring(4, 6) + '.' + val.substring(2, 4) + '.' + '20' + val.substring(0, 2);
}
return val;
}
formatNumber(val) {
if (val === undefined || val === null || isNaN(val)) return '0';
// Round to nearest integer if the user says there are no decimals (pieces)
// or round to 3 decimals to avoid floating point errors.
const rounded = Math.round(val * 1000) / 1000;
return rounded.toLocaleString('de-DE', {
minimumFractionDigits: 0,
maximumFractionDigits: 3
});
}
renderTemplate(data) {
let html = `
KÄUFER: ${data.header.buyer.id}
${data.header.buyer.name}
${data.header.buyer.street}
${data.header.buyer.city}
VERKÄUFER: ${data.header.seller.id}
${data.header.seller.name}
${data.header.seller.street}
${data.header.seller.city}
WARENEMPFÄNGER: ${data.header.shipTo.id}
${data.header.shipTo.name}
${data.header.shipTo.street}
${data.header.shipTo.city}
Abladestelle: ${data.header.shipTo.abladestelle || '-'}
${data.header.shipTo.plant ? `
Werk: ${data.header.shipTo.plant}
` : ''}
`;
data.articles.forEach((art, index) => {
html += `
#${index + 1}
Kunde:
${art.buyerItem}
Pos:
${art.orderPos || '-'}
${art.desc || 'Keine Beschreibung'}
FZ: ${this.formatNumber(art.eingangsfortschrittszahl)}
${art.nullStellung ? `Nullstellung: ${art.nullStellung}` : ''}
Bestellung:
${art.orderNo || '-'}
${art.deliveries.length > 0 ? `
Letzte Lieferungen
| Lieferschein | WE-Datum | Menge |
${art.deliveries.map(d => `
| ${d.docNo} |
${d.date} |
${this.formatNumber(d.qty)} ${d.unit} |
`).join('')}
` : ''}
Einteilungen
| Typ |
Termin (Abholung / Lieferung) |
Menge |
Kum.Menge |
Soll-FZ |
`;
let currentMonth = null;
let monthSum = 0;
let kumSum = 0;
art.schedules.forEach((sched, sIndex) => {
if (currentMonth !== null && sched.monthKey !== currentMonth) {
html += `
| Monatssumme: |
${this.formatNumber(monthSum)} |
|
`;
monthSum = 0;
}
currentMonth = sched.monthKey;
monthSum = Math.round((monthSum + sched.qty) * 1000) / 1000;
kumSum = Math.round((kumSum + sched.qty) * 1000) / 1000;
html += `
| ${sched.type || 'Abruf'} |
${sched.pickupDate && sched.deliveryDate && sched.pickupDate !== sched.deliveryDate ? `
Abholung: ${sched.pickupDate}
Lieferung: ${sched.deliveryDate}
` : `
${sched.date}
`}
|
${this.formatNumber(sched.qty)} ${sched.unit} |
${this.formatNumber(kumSum)} |
${sched.targetEFZ !== null ? `${this.formatNumber(sched.targetEFZ)}` : '-'}
|
`;
if (sIndex === art.schedules.length - 1) {
html += `
| Monatssumme: |
${this.formatNumber(monthSum)} |
|
`;
}
});
html += `
`;
});
html += `
`;
this.showViewer(html);
}
reset() {
this.renderArea.innerHTML = '';
this.renderArea.style.display = 'none';
this.dropZone.style.display = 'flex';
if (this.btnPrint) this.btnPrint.style.display = 'none';
if (this.btnBack) this.btnBack.style.display = 'none';
if (this.fileInput) this.fileInput.value = '';
}
renderVda4913Template(data) {
let html = `
KÄUFER: ${data.header.customerId}
${data.header.buyer.name}
${data.header.buyer.street}
${data.header.buyer.city}
VERKÄUFER: ${data.header.supplierId}
${data.header.seller.name}
${data.header.seller.street}
${data.header.seller.city}
ÜBERTRAGUNG
Übertragungs-Nr: ${data.header.docNo}
Datum: ${data.header.date}
Ref-Nr: ${data.header.oldDocNo || '-'}
`;
if (!data.transports || data.transports.length === 0) {
html += `
Keine Transportdaten in der Nachricht gefunden.
`;
} else {
data.transports.forEach((transport, tIdx) => {
html += `
`;
transport.deliveryNotes.forEach((dn, dnIdx) => {
html += `
`;
if (dn.texts && dn.texts.length > 0) {
html += `
Bemerkungen: ${dn.texts.join(' | ')}
`;
}
html += `
`;
dn.positions.forEach((pos, pIdx) => {
html += `
#${pIdx + 1}
Kunde:
${pos.custMat}
Lieferant:
${pos.suppMat}
Keine Beschreibung
${pos.revisionLevel ? `Rev: ${pos.revisionLevel}` : ''}
${pos.batchNo ? `Charge: ${pos.batchNo}` : ''}
Ref: ${pos.refNo}
${this.formatNumber(pos.qty)}
${pos.unit}
${pos.packaging && pos.packaging.length > 0 ? `
${pos.packaging.map(pk => `
${pk.qty}x ${pk.packMatCust}
(${pk.packMatSupp || '-'})
(S: ${pk.labelFrom} - ${pk.labelTo || pk.labelFrom})
`).join('')}
` : ''}
`;
});
html += `
`;
});
html += `
`;
});
}
html += `
`;
this.showViewer(html);
}
printDocument() {
window.print();
}
}
window.EDIBridge.Viewer = Viewer;