Files
vda-to-edifact-converter/js/vda4913-to-delvry03.js
2026-03-13 09:53:40 +01:00

349 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* VDA 4913 → DELVRY03 (XML-IDoc) Converter
* Based on: ifm electronic EDI Guide XML-DELVRY03 v1.0 (30.06.2020)
* Input: Parsed VDA 4913 object from VDAParser (vda-parser.js)
* Output: EDIFACT-style IDoc field map object ready for XML serialisation
* OR a flat key=value string for direct IDoc inbound processing.
*
* Segment mapping:
* VDA 711 → EDI_DC40 + E1EDL20 (delivery header)
* VDA 712 → E1EDL20 transport fields (carrier, weight)
* VDA 713 → E1EDL20 delivery note fields + E1ADRM1 (ship-to)
* VDA 714 → E1EDL24 (delivery item)
* VDA 715 → E1EDL37 (handling unit)
*/
window.EDIBridge = window.EDIBridge || {};
class VDA4913ToDELVRY03 {
// ── Unit mapping VDA → SAP/IDoc ──────────────────────────────────────────
static UNIT_MAP = {
'ST': 'PCE',
'PCE': 'PCE',
'KG': 'KG',
'KGM': 'KG',
'L': 'L',
'MTR': 'M',
'M': 'M',
'PAL': 'PAL',
'CTN': 'CTN',
};
// ── Transport type mapping VDA → SAP TRAGY ────────────────────────────────
static TRANSPORT_MAP = {
'1': '01', // Sea
'2': '02', // Rail
'3': '03', // Road
'4': '04', // Air
'5': '05', // Post
};
// ─────────────────────────────────────────────────────────────────────────
// Main entry point
// @param {object} parsed result of VDAParser.parse()
// @param {object} config { senderId, receiverId, plant, salesOrg, ... }
// @returns {object} IDoc structure { control: {}, segments: [] }
// ─────────────────────────────────────────────────────────────────────────
static convert(parsed, config = {}) {
const idocs = [];
for (const interchange of (parsed.interchanges || [])) {
const hdr711 = interchange.header || {};
for (const transport of (interchange.transports || [])) {
const hdr712 = transport.data || {};
for (const dn of (transport.deliveryNotes || [])) {
const hdr713 = dn.header || {};
idocs.push(this._buildIDoc(hdr711, hdr712, hdr713, dn, config));
}
}
}
return idocs;
}
// ─────────────────────────────────────────────────────────────────────────
// Build one IDoc per delivery note
// ─────────────────────────────────────────────────────────────────────────
static _buildIDoc(hdr711, hdr712, hdr713, dn, config) {
const now = new Date();
const dateNow = this._fmtDate(now);
const timeNow = this._fmtTime(now);
const senderId = config.senderId || hdr711.sourceId || 'SUPPLIER';
const receiverId = config.receiverId || hdr711.targetId || 'IFM';
const docNum = hdr713.dnNo || hdr712.refNo || '';
const delivDate = this._vdaDate(hdr713.date || hdr712.date || dateNow);
// ── EDI_DC40 Control Record ─────────────────────────────────────────
const control = {
TABNAM: 'EDI_DC40',
MANDT: config.client || '600',
DOCNUM: docNum.padStart(16, '0'),
DOCREL: config.docrel || '750',
STATUS: '30',
DIRECT: '1',
OUTMOD: '2',
IDOCTYP: 'DELVRY03',
MESTYP: 'STPOD',
STD: 'E',
STDVRS: '003',
STDMES: 'DELVRY',
SNDPOR: config.sndPort || 'EDIPORT',
SNDPRT: 'LS',
SNDPRN: senderId.substring(0, 10),
SNDLAD: senderId.substring(0, 21),
RCVPOR: config.rcvPort || 'SAPPORT',
RCVPRT: 'LI',
RCVPRN: receiverId.substring(0, 10),
RCVLAD: '1000280',
CREDAT: dateNow,
CRETIM: timeNow,
};
const segments = [];
// ── E1EDL20 Delivery Header ─────────────────────────────────────────
// VDA 712: carrier (16-30), date (30-36), time (36-40)
// VDA 713: dnNo (5-13), date (13-19), unloadingPoint (19-24)
const e1edl20 = {
_SEGMENT: 'E1EDL20',
VBELN: docNum.padStart(10, '0'), // Delivery note number
VSTEL: hdr713.unloadingPoint || config.shippingPoint || '', // Shipping point
VKORG: config.salesOrg || '',
LGNUM: config.warehouseNo || '',
ABLAD: hdr713.unloadingPoint || '', // Unloading point
BTGEW: String(hdr712.grossWeight || ''), // Total weight
NTGEW: String(hdr712.netWeight || ''), // Net weight
GEWEI: 'KG',
BOLNR: hdr712.refNo || '', // Bill of lading
TRATY: this.TRANSPORT_MAP[hdr712.transportType] || '',
TRAID: hdr712.carrierName || '', // Carrier name / transport ID
LIFEX: docNum, // External delivery note no.
PARID: senderId, // External partner number
PODAT: this._vdaDate(hdr713.date || hdr712.date || ''), // Proof of delivery date
POTIM: hdr712.time || '',
};
segments.push(e1edl20);
// ── E1ADRM1 Ship-To Address (partner function WE) ───────────────────
// From VDA 713: qualifier (24-30), orderNo (30-38)
const e1adrm1 = {
_SEGMENT: 'E1ADRM1',
PARTNER_Q: 'WE',
ADDRESS_T: '1',
PARTNER_ID: config.shipToId || hdr713.qualifier || '',
NAME1: config.shipToName || 'ifm electronic gmbh',
STREET1: config.shipToStreet || '',
CITY1: config.shipToCity || '',
POSTL_COD1: config.shipToZip || '',
COUNTRY1: config.shipToCountry || 'DE',
};
segments.push(e1adrm1);
// ── E1ADRM1 Supplier Address (partner function LF) ─────────────────
const e1adrm1_lf = {
_SEGMENT: 'E1ADRM1',
PARTNER_Q: 'LF',
ADDRESS_T: '1',
PARTNER_ID: senderId,
NAME1: config.supplierName || '',
STREET1: config.supplierStreet || '',
CITY1: config.supplierCity || '',
POSTL_COD1: config.supplierZip || '',
COUNTRY1: config.supplierCountry || 'DE',
};
segments.push(e1adrm1_lf);
// ── E1EDL24 Delivery Items ──────────────────────────────────────────
// VDA 714: custMat (5-27), suppMat (27-49), qty (53-65)/1000, unit (65-67), refNo (77-90)
let posNr = 10;
for (const pos of (dn.positions || [])) {
const d = pos.data || {};
const unit = this.UNIT_MAP[d.unit] || d.unit || 'PCE';
const e1edl24 = {
_SEGMENT: 'E1EDL24',
POSNR: String(posNr).padStart(6, '0'),
MATNR: d.suppMat || '', // Supplier material number
MATWA: d.suppMat || '', // Material entered
ARKTX: '', // Short text
KDMAT: d.custMat || '', // Customer material number
LGMNG: String(d.qty || 0), // Actual delivery qty
VRKME: unit, // Sales unit
MEINS: unit, // Base unit
VGBEL: hdr713.orderNo || '', // Reference document (order)
VGPOS: String(posNr).padStart(6, '0'),
WERKS: config.plant || '1110', // Plant
};
segments.push(e1edl24);
// ── E1EDL37 Handling Units (from VDA 715 packaging lines) ───────
// VDA 715: packMatCust (5-27), packMatSupp (27-49), qty (59-62),
// labelFrom (78-87), labelTo (87-96)
let huCounter = 1;
for (const pack of (pos.packaging || [])) {
const huId = String(huCounter).padStart(10, '0');
const e1edl37 = {
_SEGMENT: 'E1EDL37',
VPOSNR: String(posNr).padStart(6, '0'),
VEGR1: pack.packMatSupp || pack.packMatCust || '', // HU type
ANZPK: String(pack.qty || 1), // Number of packages
EXIDV: pack.labelFrom || huId, // External HU ID (label from)
EXIDV2: pack.labelTo || '', // Label to
};
segments.push(e1edl37);
huCounter++;
}
// ── E1EDL14 DG Control Data (delivery item) only if needed ───
// Skipped unless config.includeDG = true
posNr += 10;
}
// ── Free texts from VDA 716 ───────────────────────────────────────────
if (dn.texts && dn.texts.length > 0) {
const e1txth8 = {
_SEGMENT: 'E1TXTH8',
FUNCTION: 'H',
TDOBJECT: 'VBBK',
TDOBNAME: docNum,
TDID: '0001',
TDSPRAS: '1',
TDTEXTTYPE: 'GRKO',
LANGUA_ISO: 'DE',
};
segments.push(e1txth8);
for (const txt of dn.texts) {
segments.push({
_SEGMENT: 'E1TXTP8',
TDFORMAT: '*',
TDLINE: txt.substring(0, 132),
});
}
}
return { control, segments };
}
// ─────────────────────────────────────────────────────────────────────────
// Serialise IDoc array to flat IDoc text format (for file-based inbound)
// ─────────────────────────────────────────────────────────────────────────
static toIdocText(idocs) {
const lines = [];
let docNum = 1;
for (const idoc of idocs) {
const dn = String(docNum++).padStart(16, '0');
// Control record
const ctrl = { ...idoc.control, DOCNUM: dn };
lines.push(this._flatRecord('EDI_DC40', ctrl));
// Data segments
let segNum = 1;
for (const seg of idoc.segments) {
const segName = seg._SEGMENT;
const fields = { ...seg };
delete fields._SEGMENT;
lines.push(this._flatRecord(segName, fields, dn, segNum++));
}
}
return lines.join('\n');
}
// ─────────────────────────────────────────────────────────────────────────
// Serialise to structured JSON (for XML generation or further processing)
// ─────────────────────────────────────────────────────────────────────────
static toJSON(idocs) {
return JSON.stringify(idocs, null, 2);
}
// ─────────────────────────────────────────────────────────────────────────
// Serialise to XML-IDoc format (as expected by ifm DELVRY03 guide)
// ─────────────────────────────────────────────────────────────────────────
static toXML(idocs) {
const xmlParts = ['<?xml version="1.0" encoding="UTF-8"?>', '<DELVRY03>'];
for (const idoc of idocs) {
xmlParts.push(' <IDOC BEGIN="1">');
// Control record
xmlParts.push(' <EDI_DC40 SEGMENT="1">');
for (const [k, v] of Object.entries(idoc.control)) {
if (v !== undefined && v !== '') {
xmlParts.push(` <${k}>${this._xmlEsc(v)}</${k}>`);
}
}
xmlParts.push(' </EDI_DC40>');
// Data segments
for (const seg of idoc.segments) {
const name = seg._SEGMENT;
xmlParts.push(` <${name} SEGMENT="1">`);
for (const [k, v] of Object.entries(seg)) {
if (k === '_SEGMENT') continue;
if (v !== undefined && v !== '') {
xmlParts.push(` <${k}>${this._xmlEsc(v)}</${k}>`);
}
}
xmlParts.push(` </${name}>`);
}
xmlParts.push(' </IDOC>');
}
xmlParts.push('</DELVRY03>');
return xmlParts.join('\n');
}
// ─────────────────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────────────────
/** VDA date YYMMDD → YYYYMMDD */
static _vdaDate(d) {
if (!d) return '';
d = String(d).replace(/\D/g, '');
if (d.length === 6) return '20' + d; // YYMMDD → YYYYMMDD
if (d.length === 8) return d; // already YYYYMMDD
return d;
}
/** Date object → YYYYMMDD */
static _fmtDate(dt) {
return dt.toISOString().replace(/-/g, '').substring(0, 8);
}
/** Date object → HHMMSS */
static _fmtTime(dt) {
return dt.toISOString().replace(/:/g, '').substring(11, 17);
}
/** Flat IDoc record line */
static _flatRecord(segName, fields, docNum = '', segNum = 0) {
const parts = [segName.padEnd(30)];
if (docNum) parts.push(docNum.padStart(16, '0'));
if (segNum) parts.push(String(segNum).padStart(6, '0'));
for (const [k, v] of Object.entries(fields)) {
parts.push(`${k}=${v}`);
}
return parts.join(' ');
}
/** XML escape */
static _xmlEsc(v) {
return String(v)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
}
window.EDIBridge.VDA4913ToDELVRY03 = VDA4913ToDELVRY03;