359 lines
43 KiB
JSON
359 lines
43 KiB
JSON
{
|
|
"name": "Lightmakers",
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"operation": "xml",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.extractFromFile",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
96,
|
|
272
|
|
],
|
|
"id": "c1234889-58d7-4d4f-8776-9f5b889358f8",
|
|
"name": "Extract from File1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "upload",
|
|
"path": "=/prod/totp/{{ $json.filename }}",
|
|
"binaryData": false,
|
|
"fileContent": "={{ $json.xml }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.ftp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
800,
|
|
304
|
|
],
|
|
"id": "9d4781ed-2b86-4b26-8762-fdcd42ffbdb5",
|
|
"name": "FTP Upload1",
|
|
"credentials": {
|
|
"ftp": {
|
|
"id": "oLAZ4OgmkOopMHAq",
|
|
"name": "FTP De Wit Transport"
|
|
}
|
|
},
|
|
"onError": "continueErrorOutput"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"options": {}
|
|
},
|
|
"id": "e1665e95-6d3f-43d8-81cc-91bc99c54c25",
|
|
"name": "Parse XML to JSON",
|
|
"type": "n8n-nodes-base.xml",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
288,
|
|
272
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"triggerTimes": {
|
|
"item": [
|
|
{
|
|
"mode": "everyX",
|
|
"value": 15,
|
|
"unit": "minutes"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"id": "d044c9e8-2ebf-40e4-be76-35889c123660",
|
|
"name": "Check FTP Every 15 Minutes",
|
|
"type": "n8n-nodes-base.cron",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-752,
|
|
272
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"mode": "runOnceForEachItem",
|
|
"jsCode": "// n8n Code node: NYCE.LOGIC CustomerOrderDelivery -> Transpas XML\n\n// ---------- helpers ----------\nconst AMP_FIX_RE = /&(?!amp;|lt;|gt;|quot;|apos;|#\\d+;|#x[0-9A-Fa-f]+;)/g;\n\n/**\n * Escapet tekst veilig voor XML (zonder bestaande entities dubbel te escapen)\n */\nconst xmlEsc = (s) =>\n String(s ?? '')\n // eerst ampersand, maar NIET als het al een geldige entity is\n .replace(AMP_FIX_RE, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n\n/**\n * Vangnet: repareert overgebleven losse & in de complete XML-output\n * (bijv. vaste tekst of een veld dat per ongeluk niet door xmlEsc ging)\n */\nconst repairXml = (xml) => String(xml ?? '').replace(AMP_FIX_RE, '&');\n\n\nconst ensureArray = (v) =>\n v == null ? [] : Array.isArray(v) ? v : [v];\n\nconst joinLines = (s) =>\n Array.isArray(s) ? s.join(' ') : (s ?? '');\n\nconst get = (obj, path) =>\n path.split('.').reduce((acc, key) => (acc && acc[key] != null ? acc[key] : undefined), obj);\n\n// zoek in hele objectboom op een key-fragment (ongeacht namespace/prefix)\nfunction findNodeRecursive(obj, fragment) {\n if (!obj || typeof obj !== 'object') return null;\n const frag = fragment.toLowerCase();\n const stack = [obj];\n\n while (stack.length) {\n const cur = stack.pop();\n if (!cur || typeof cur !== 'object') continue;\n\n for (const [k, v] of Object.entries(cur)) {\n if (k.toLowerCase().includes(frag)) return v;\n if (v && typeof v === 'object') stack.push(v);\n }\n }\n return null;\n}\n\n// zoek directe child met key-fragment\nfunction child(obj, fragment) {\n if (!obj || typeof obj !== 'object') return undefined;\n const frag = fragment.toLowerCase();\n for (const [k, v] of Object.entries(obj)) {\n if (k.toLowerCase().includes(frag)) return v;\n }\n return undefined;\n}\n\n// \"vandaag\" als yyyy-mm-dd in een timezone (Amsterdam)\nfunction todayYMD(tz = 'Europe/Amsterdam') {\n const parts = new Intl.DateTimeFormat('en-CA', {\n timeZone: tz,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n }).formatToParts(new Date());\n\n const m = {};\n for (const p of parts) if (p.type !== 'literal') m[p.type] = p.value;\n return `${m.year}-${m.month}-${m.day}`; // yyyy-mm-dd\n}\n\n// datum naar yyyy-mm-dd\nfunction fmtDate(raw) {\n if (!raw) return '';\n let d = String(raw).trim();\n\n // ISO: 2025-11-17T09:58:26\n const iso = d.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (iso) return `${iso[1]}-${iso[2]}-${iso[3]}`;\n\n d = d.replace(/\\./g, '-').replace(/\\//g, '-');\n\n const m = d.match(/^(\\d{1,2})-(\\d{1,2})-(\\d{2,4})$/);\n if (m) {\n let day = m[1].padStart(2, '0');\n let month = m[2].padStart(2, '0');\n let year = m[3];\n if (year.length === 2) year = '20' + year;\n return `${year}-${month}-${day}`;\n }\n\n const n = d.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n if (n) return d;\n\n const dt = new Date(d);\n if (!isNaN(dt.getTime())) return dt.toISOString().substring(0, 10);\n\n return d;\n}\n\n// yyyy-mm-dd -> eerstvolgende werkdag (ma-vr), altijd minimaal +1 dag\nfunction nextWorkdayYMD(ymd) {\n if (!ymd || !/^\\d{4}-\\d{2}-\\d{2}$/.test(ymd)) return ymd;\n\n const [Y, M, D] = ymd.split('-').map(Number);\n const dt = new Date(Date.UTC(Y, M - 1, D));\n\n dt.setUTCDate(dt.getUTCDate() + 1);\n\n while (dt.getUTCDay() === 0 || dt.getUTCDay() === 6) {\n dt.setUTCDate(dt.getUTCDate() + 1);\n }\n\n return dt.toISOString().slice(0, 10);\n}\n\nfunction fmtTimeFromIso(raw) {\n if (!raw) return '';\n const m = String(raw).match(/T(\\d{2}:\\d{2})(:\\d{2})?/);\n return m ? m[1] : '';\n}\n\nconst normDec = (s) =>\n s ? String(s).replace(',', '.') : '';\n\nconst toNumber = (s) => {\n const n = parseFloat(String(s ?? '0').replace(',', '.'));\n return isNaN(n) ? 0 : n;\n};\n\n// ---------- juiste bron-node pakken ----------\n\n// Soms zit alles onder één top-key (bijv. \"NYCE.LOGIC\")\nlet root = $json;\nconst rootKeys = Object.keys(root);\nif (rootKeys.length === 1 && typeof root[rootKeys[0]] === 'object') {\n root = root[rootKeys[0]];\n}\n\n// zoek CustomerOrderDelivery in de boom\nconst cod = findNodeRecursive(root, 'CustomerOrderDelivery') || root;\nif (!cod || typeof cod !== 'object') {\n throw new Error('CustomerOrderDelivery niet gevonden in XML-JSON. Check de output van de XML-node.');\n}\n\n// ---------- CustomProperties (voor TMS_Number, etc.) ----------\nconst customPropsContainer =\n child(cod, 'CustomProperties') || {};\n\nconst propsRaw =\n child(customPropsContainer, 'CustomerOrderDeliveryCustomProperty') || [];\n\nconst props = ensureArray(propsRaw);\n\nconst findProp = (name) => {\n const p = props.find(p =>\n (p.Name ?? p.name ?? (p.Name?._) ?? '').toString() === name\n );\n if (!p) return '';\n const v = p.Value ?? p.value ?? (p.Value?._);\n return v != null ? String(v) : '';\n};\n\nconst tmsNumber = findProp('TMS_Number'); // 189255\nconst totalQtyProp = findProp('TotalQuantity');\nconst totalWeightProp = findProp('TotalWeight');\nconst totalVolumeProp = findProp('TotalVolume');\n\n// ---------- Warehouse (pickup) ----------\nconst warehouse = child(cod, 'Warehouse') || {};\nconst whAddr = child(warehouse, 'Address') || {};\nconst whAddrLines = child(whAddr, 'AddressLines') || {};\nconst whAddr1 = joinLines(whAddrLines.String);\nconst whCity = whAddr.City || '';\nconst whZip = whAddr.ZipCode || '';\nconst whCountryObj = whAddr.Country || {};\nconst whCountry = whCountryObj.Code || whCountryObj.IsoCode || '';\n\n// ---------- Order + lines ----------\nconst customerOrders = child(cod, 'CustomerOrders') || {};\nconst firstOrderRaw = child(customerOrders, 'CustomerOrder') || {};\nconst order = Array.isArray(firstOrderRaw) ? firstOrderRaw[0] : firstOrderRaw;\n\nconst shipDate = order.ShipDate; // bv. 2025-11-13T09:58:26\n\n// Texts -> TRANSPORT_INSTRUCTIONS\nlet transportInstr = '';\nconst textsContainer = order.Texts || {};\nconst orderTexts = ensureArray(textsContainer.CustomerOrderText);\norderTexts.forEach(t => {\n const tt = t.TextType || {};\n const ttCode = tt.Code || (tt.Code?._);\n if (ttCode === 'TRANSPORT_INSTRUCTIONS') {\n const tl = t.TextLines || {};\n transportInstr = joinLines(tl.String);\n }\n});\n\n// Lines -> first line for RequestedDeliveryDate\nconst linesContainer = child(cod, 'Lines') || {};\nconst linesRaw = child(linesContainer, 'CustomerOrderLineDelivery') || [];\nconst lines = ensureArray(linesRaw);\nconst firstLine = lines[0] || {};\nconst reqDelDate = firstLine.RequestedDeliveryDate;\n\n// ---------- Delivery address ----------\nconst delAddr = child(cod, 'CustomerOrderDeliveryCustomerAddress') || {};\nconst delAddrLines = child(delAddr, 'AddressLines') || {};\nconst delAddr1 = joinLines(delAddrLines.String);\nconst delCity = delAddr.City || '';\nconst delZip = delAddr.ZipCode || '';\nconst delCountryObj = delAddr.Country || {};\nconst delCountry = delCountryObj.Code || '';\n\nconst delContact = delAddr.MainContact || {};\nconst delContactPhone = delContact.MobilePhoneNumber || '';\nconst delContactName = delContact.Name || '';\n\n// klantnaam\nconst custName =\n cod.CustomerOrderDeliveryCustomerName ||\n (cod.CustomerOrderDeliveryCustomerName && cod.CustomerOrderDeliveryCustomerName._) ||\n '';\n\n// ---------- ShipmentReferences -> goodsLines met afmetingen ----------\nconst shipmentRefsContainer = child(cod, 'ShipmentReferences') || {};\nconst shipmentRefsRaw = child(shipmentRefsContainer, 'ShipmentReference') || [];\nconst shipmentRefs = ensureArray(shipmentRefsRaw);\n\nconst goodsLinesArr = [];\n\nshipmentRefs.forEach(sr => {\n const srWeight = toNumber(sr.Weight);\n const srVolume = toNumber(sr.Volume);\n const srLength = toNumber(sr.Length);\n const srWidth = toNumber(sr.Width);\n const srHeight = toNumber(sr.Height);\n const srLM = toNumber(sr.LoadingMeters);\n\n const pmContainer = child(sr, 'PackingMaterials') || {};\n const pmRaw = child(pmContainer, 'ShipmentReferenceToPackingMaterialQuantity') || [];\n const pmList = ensureArray(pmRaw);\n\n // totaal aantal colli/pallets binnen deze ShipmentReference\n let totalQtyThisSR = 0;\n pmList.forEach(pm => {\n const qty = toNumber(pm.Quantity ?? (pm.Quantity && pm.Quantity._));\n totalQtyThisSR += qty;\n });\n if (totalQtyThisSR <= 0) totalQtyThisSR = 1;\n\n pmList.forEach(pm => {\n const packing = pm.PackingMaterial || {};\n const code = packing.Code || (packing.Code && packing.Code._) || '';\n const qty = toNumber(pm.Quantity ?? (pm.Quantity && pm.Quantity._));\n if (!code || qty <= 0) return;\n\n const share = qty / totalQtyThisSR;\n\n goodsLinesArr.push({\n code,\n qty,\n length: srLength,\n width: srWidth,\n height: srHeight,\n loadingmeter: srLM * share,\n weight: srWeight * share,\n volume: srVolume * share,\n });\n });\n});\n\n// aggregaten voor cargo\nlet totalPallets = 0;\nlet totalWeightFromRefs = 0;\nlet totalVolumeFromRefs = 0;\nlet totalLoadingMeters = 0;\n\ngoodsLinesArr.forEach(gl => {\n totalPallets += gl.qty;\n totalWeightFromRefs += gl.weight;\n totalVolumeFromRefs += gl.volume;\n totalLoadingMeters += gl.loadingmeter;\n});\n\n// fallback op custom properties als ShipmentReferences leeg zijn\nif (totalPallets === 0 && totalQtyProp) {\n totalPallets = toNumber(totalQtyProp);\n}\nif (totalWeightFromRefs === 0 && totalWeightProp) {\n totalWeightFromRefs = toNumber(totalWeightProp);\n}\nif (totalVolumeFromRefs === 0 && totalVolumeProp) {\n totalVolumeFromRefs = toNumber(totalVolumeProp);\n}\n\n// cargo unit: gebruik goodsline unit als er precies één unieke eenheid is, anders 591\nconst goodsLineUnitCodes = [...new Set(\n goodsLinesArr\n .map(gl => String(gl.code ?? '').trim())\n .filter(Boolean)\n)];\n\nconst cargoUnitId = goodsLineUnitCodes.length === 1 ? goodsLineUnitCodes[0] : '591';\n\n// ---------- referenties ----------\nconst tourId = tmsNumber || cod.Number || order.Number || '';\nconst shipmentRef = order.Number || cod.DeliveryKeyCode || cod.Number || '';\n\n// DeliveryKeyCode als referentie 29 (als aanwezig)\nconst deliveryKeyCode = cod.DeliveryKeyCode || '';\n\n// DeliveryCustomerCode (voor combinatie in booking reference)\nconst deliveryCustomerCode = cod.DeliveryCustomerCode || '';\n\n// data/tijden\nconst pickupDateRaw = todayYMD('Europe/Amsterdam');\nconst pickupDate = nextWorkdayYMD(pickupDateRaw);\nconst pickupTime = fmtTimeFromIso(shipDate);\nconst deliveryDate = fmtDate(reqDelDate || shipDate);\nconst deliveryTime = fmtTimeFromIso(reqDelDate);\n\n// cargo aggregate\nconst cargoUnitAmount = totalPallets > 0 ? String(totalPallets) : '1';\nconst cargoWeight = totalWeightFromRefs > 0 ? String(totalWeightFromRefs) : (normDec(totalWeightProp) || '0');\nconst cargoVolume = totalVolumeFromRefs > 0 ? String(totalVolumeFromRefs) : (normDec(totalVolumeProp) || '0');\n// afmetingen & LM NIET meer op cargo; daar 0 / leeg\nconst cargoLength = '0';\nconst cargoWidth = '0';\nconst cargoHeight = '0';\nconst cargoLoadingMeter= '0';\n\nconst cargoDescription = `Order ${shipmentRef} - ${custName}`;\n\n// ---------- Transpas XML bouwen ----------\n// bool2 in cargo: true als transportInstr (escaped) \"AVIS\" bevat\nconst bool2Cargo = xmlEsc(transportInstr).toUpperCase().includes('AVIS');\nconst bool1Cargo = xmlEsc(transportInstr).toUpperCase().includes('TAILLIFT'); \nlet out = '';\nout += '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\nout += '<transportbookings>\\n';\nout += ' <transportbooking>\\n';\nout += ` <edireference>${xmlEsc(tourId)}</edireference>\\n`;\n\n// referentie = DeliveryCustomerCode + '_' + shipmentRef\nconst bookingRef =\n (deliveryCustomerCode || '') +\n (deliveryCustomerCode && shipmentRef ? ' - ' : '') +\n (shipmentRef || '');\n\nout += ` <reference>${xmlEsc(bookingRef)}</reference>\\n`;\nout += ' <customer_id matchmode=\"1\">9850434</customer_id>\\n';\nout += ' <debtor_id matchmode=\"1\">9850434</debtor_id>\\n';\nout += ' <department_id matchmode=\"3\">Dep DeWit</department_id>\\n';\nout += ' <shipments>\\n';\nout += ' <shipment>\\n';\nout += ` <edireference>${xmlEsc(shipmentRef)}</edireference>\\n`;\nout += ` <reference>${xmlEsc(shipmentRef)}</reference>\\n`;\nout += ' <shipmentkind_id matchmode=\"1\"/>\\n';\nout += ' <version>1</version>\\n';\n\n// Pickup (warehouse)\nout += ' <pickupaddress>\\n';\nout += ' <address_id matchmode=\"11\" overwrite=\"false\"></address_id>\\n';\nout += ` <date>${pickupDate}</date>\\n`;\nout += ` <time>${xmlEsc(pickupTime)}</time>\\n`;\nout += ` <datetill>${pickupDate}</datetill>\\n`;\nout += ' <timetill></timetill>\\n';\nout += ` <reference>${xmlEsc(shipmentRef)} - ${xmlEsc(tourId)}</reference>\\n`;\nout += ` <name>${xmlEsc(warehouse.Name ?? '')}</name>\\n`;\nout += ` <address1>${xmlEsc(whAddr1)}</address1>\\n`;\nout += ` <zipcode>${xmlEsc(whZip)}</zipcode>\\n`;\nout += ` <city_id matchmode=\"4\">${xmlEsc(whCity)}</city_id>\\n`;\nout += ` <country_id matchmode=\"2\">${xmlEsc(whCountry)}</country_id>\\n`;\nout += ' </pickupaddress>\\n';\n\n// Delivery (klant)\nout += ' <deliveryaddress>\\n';\nout += ' <address_id matchmode=\"5\" overwrite=\"false\"></address_id>\\n';\nout += ` <date>${deliveryDate}</date>\\n`;\n// out += ` <time>${xmlEsc(deliveryTime)}</time>\\n`;\nout += ` <datetill>${deliveryDate}</datetill>\\n`;\n// out += ` <timetill>${xmlEsc(deliveryTime)}</timetill>\\n`;\nout += ` <reference>Light & Living</reference>\\n`;\nout += ` <name>${xmlEsc(custName)}</name>\\n`;\nout += ` <address1>${xmlEsc(delAddr1)}</address1>\\n`;\nout += ` <zipcode>${xmlEsc(delZip)}</zipcode>\\n`;\nout += ` <city_id matchmode=\"4\">${xmlEsc(delCity)}</city_id>\\n`;\nout += ` <country_id matchmode=\"2\">${xmlEsc(delCountry)}</country_id>\\n`;\nout += ' <email></email>\\n';\nout += ` <phone>${xmlEsc(delContactPhone)}</phone>\\n`;\nout += ` <driverinfo>${xmlEsc(transportInstr)}</driverinfo>\\n`;\nout += ' </deliveryaddress>\\n';\n\n// References\nout += ' <references>\\n';\nout += ' <reference>\\n';\nout += ' <referencekind_id matchmode=\"0\" autocreate=\"false\">29</referencekind_id>\\n';\nout += ` <description>${xmlEsc(deliveryKeyCode)}</description>\\n`;\nout += ' </reference>\\n';\nout += ' </references>\\n';\n\n// Cargo aggregate\nout += ' <cargo>\\n';\nout += ` <unitamount>${xmlEsc(cargoUnitAmount)}</unitamount>\\n`; // totaal # pallets\nout += ` <unit_id matchmode=\"1\">${xmlEsc(cargoUnitId)}</unit_id>\\n`;\nout += ' <product_id matchmode=\"1\">123</product_id>\\n';\nout += ` <productdescription>${xmlEsc(cargoDescription)}</productdescription>\\n`;\nout += ` <weight>${xmlEsc(cargoWeight)}</weight>\\n`;\nout += ` <length>${xmlEsc(cargoLength)}</length>\\n`; // blijft 0\nout += ` <width>${xmlEsc(cargoWidth)}</width>\\n`; // blijft 0\n// out += ` <loadingmeter>${xmlEsc(cargoLoadingMeter)}</loadingmeter>\\n`; // blijft 0\n//out += ` <volume>${xmlEsc(cargoVolume)}</volume>\\n`;\nout += ` <height>${xmlEsc(cargoHeight)}</height>\\n`; // blijft 0\nout += ` <bool1>${bool1Cargo ? 'true' : 'false'}</bool1>\\n`;\nout += ` <bool2>${bool2Cargo ? 'true' : 'false'}</bool2>\\n`;\n\n// Goodslines: één per ShipmentReferenceToPackingMaterialQuantity\nout += ' <goodslines>\\n';\n\nif (goodsLinesArr.length > 0) {\n let seq = 1;\n goodsLinesArr.forEach(gl => {\n out += ' <goodsline>\\n';\n out += ` <sequence>${seq++}</sequence>\\n`;\n out += ` <unitamount>${xmlEsc(String(gl.qty))}</unitamount>\\n`;\n out += ` <unit_id matchmode=\"1\">${xmlEsc(gl.code)}</unit_id>\\n`; // bv. EUROPALLET_HT\n out += ' <product_id matchmode=\"1\">123</product_id>\\n';\n out += ` <length>${xmlEsc(String(gl.length || 0))}</length>\\n`;\n out += ` <width>${xmlEsc(String(gl.width || 0))}</width>\\n`;\n out += ` <height>${xmlEsc(String(gl.height || 0))}</height>\\n`;\n// out += ` <loadingmeter>${xmlEsc(String(gl.loadingmeter || 0))}</loadingmeter>\\n`;\n out += ` <weight>${xmlEsc(String(gl.weight || 0))}</weight>\\n`;\n // out += ` <volume>${xmlEsc(String(gl.volume || 0))}</volume>\\n`;\n out += ' <exchangeemballage>false</exchangeemballage>\\n';\n out += ' </goodsline>\\n';\n });\n} else {\n // fallback (oude gedrag)\n out += ' <goodsline>\\n';\n out += ' <sequence>1</sequence>\\n';\n out += ` <unitamount>${xmlEsc(cargoUnitAmount)}</unitamount>\\n`;\n out += ' <unit_id matchmode=\"1\">591</unit_id>\\n';\n out += ' <product_id matchmode=\"1\">123</product_id>\\n';\n out += ' <length>0</length>\\n';\n out += ' <width>0</width>\\n';\n out += ' <height>0</height>\\n';\n out += ' <loadingmeter>0</loadingmeter>\\n';\n out += ` <weight>${xmlEsc(cargoWeight)}</weight>\\n`;\n out += ` <volume>${xmlEsc(cargoVolume)}</volume>\\n`;\n out += ' <exchangeemballage>false</exchangeemballage>\\n';\n out += ' </goodsline>\\n';\n}\n\nout += ' </goodslines>\\n';\n\nout += ' </cargo>\\n';\nout += ' </shipment>\\n';\nout += ' </shipments>\\n';\nout += ' </transportbooking>\\n';\nout += '</transportbookings>\\n';\nout = repairXml(out);\n\n\n// Bestandsnaam\nconst filename = `lightlivinx_${tourId || shipmentRef || 'unknown'}.xml`;\nconst inputPath = $json.path || null;\n\n// Per item precies één object teruggeven\nreturn {\n json: {\n filename,\n path: inputPath,\n xml: out,\n }\n};"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
608,
|
|
320
|
|
],
|
|
"id": "d8f20c76-12bd-41d8-8be3-9d02d0a5a440",
|
|
"name": "Transform europal → Transpas1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"protocol": "sftp",
|
|
"operation": "list",
|
|
"path": "/production/out/",
|
|
"options": {
|
|
"timeout": 10000
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.ftp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-560,
|
|
272
|
|
],
|
|
"id": "0a82fd07-1d4d-4096-9473-f809f35b5283",
|
|
"name": "FTP VOS",
|
|
"credentials": {
|
|
"sftp": {
|
|
"id": "av3OQ74HR6RQnbMe",
|
|
"name": "Lightmakers - VOS"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"protocol": "sftp",
|
|
"path": "={{ $json.path }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.ftp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-256,
|
|
272
|
|
],
|
|
"id": "94e9efaf-1be6-42cd-add1-539e9a11ec75",
|
|
"name": "FTP",
|
|
"credentials": {
|
|
"sftp": {
|
|
"id": "av3OQ74HR6RQnbMe",
|
|
"name": "Lightmakers - VOS"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"protocol": "sftp",
|
|
"operation": "delete",
|
|
"path": "={{ $json.path }}",
|
|
"options": {}
|
|
},
|
|
"id": "8f397a49-1228-4c35-8138-13dc5dea8bd3",
|
|
"name": "Delete file",
|
|
"type": "n8n-nodes-base.ftp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
112,
|
|
464
|
|
],
|
|
"credentials": {
|
|
"sftp": {
|
|
"id": "av3OQ74HR6RQnbMe",
|
|
"name": "Lightmakers - VOS"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"dataTableId": {
|
|
"__rl": true,
|
|
"value": "4ueEkfDvi9oNJuEl",
|
|
"mode": "list",
|
|
"cachedResultName": "Lightmakers",
|
|
"cachedResultUrl": "/projects/G8Z9HFBaKElWGUCs/datatables/4ueEkfDvi9oNJuEl"
|
|
},
|
|
"columns": {
|
|
"mappingMode": "defineBelow",
|
|
"value": {
|
|
"bestandsnaam": "={{ $('FTP').item.json.name }}"
|
|
},
|
|
"matchingColumns": [],
|
|
"schema": [
|
|
{
|
|
"id": "Order_nummer",
|
|
"displayName": "Order_nummer",
|
|
"required": false,
|
|
"defaultMatch": false,
|
|
"display": true,
|
|
"type": "string",
|
|
"readOnly": false,
|
|
"removed": false
|
|
},
|
|
{
|
|
"id": "bestandsnaam",
|
|
"displayName": "bestandsnaam",
|
|
"required": false,
|
|
"defaultMatch": false,
|
|
"display": true,
|
|
"type": "string",
|
|
"readOnly": false,
|
|
"removed": false
|
|
}
|
|
],
|
|
"attemptToConvertTypes": false,
|
|
"convertFieldsToString": false
|
|
},
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.dataTable",
|
|
"typeVersion": 1.1,
|
|
"position": [
|
|
480,
|
|
128
|
|
],
|
|
"id": "861faf00-e1c1-41e4-bbee-f0da04f0a7e1",
|
|
"name": "Insert row",
|
|
"disabled": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"protocol": "sftp",
|
|
"operation": "upload",
|
|
"path": "=/dewit/verwerkt/{{ $json.name }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.ftp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
96,
|
|
80
|
|
],
|
|
"id": "a371618e-03c8-43da-98fb-3d8df66f6622",
|
|
"name": "Save file in Verwerkt",
|
|
"credentials": {
|
|
"sftp": {
|
|
"id": "uKyzg5cSXQqXOuHI",
|
|
"name": "SFTP n8n"
|
|
}
|
|
},
|
|
"disabled": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"mode": "runOnceForEachItem",
|
|
"jsCode": "// n8n Code node: NYCE.LOGIC CustomerOrderDelivery -> Transpas XML\n\n// ---------- helpers ----------\nconst AMP_FIX_RE = /&(?!amp;|lt;|gt;|quot;|apos;|#\\d+;|#x[0-9A-Fa-f]+;)/g;\n\n/**\n * Escapet tekst veilig voor XML (zonder bestaande entities dubbel te escapen)\n */\nconst xmlEsc = (s) =>\n String(s ?? '')\n // eerst ampersand, maar NIET als het al een geldige entity is\n .replace(AMP_FIX_RE, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n\n/**\n * Vangnet: repareert overgebleven losse & in de complete XML-output\n * (bijv. vaste tekst of een veld dat per ongeluk niet door xmlEsc ging)\n */\nconst repairXml = (xml) => String(xml ?? '').replace(AMP_FIX_RE, '&');\n\n\nconst ensureArray = (v) =>\n v == null ? [] : Array.isArray(v) ? v : [v];\n\nconst joinLines = (s) =>\n Array.isArray(s) ? s.join(' ') : (s ?? '');\n\nconst get = (obj, path) =>\n path.split('.').reduce((acc, key) => (acc && acc[key] != null ? acc[key] : undefined), obj);\n\n// zoek in hele objectboom op een key-fragment (ongeacht namespace/prefix)\nfunction findNodeRecursive(obj, fragment) {\n if (!obj || typeof obj !== 'object') return null;\n const frag = fragment.toLowerCase();\n const stack = [obj];\n\n while (stack.length) {\n const cur = stack.pop();\n if (!cur || typeof cur !== 'object') continue;\n\n for (const [k, v] of Object.entries(cur)) {\n if (k.toLowerCase().includes(frag)) return v;\n if (v && typeof v === 'object') stack.push(v);\n }\n }\n return null;\n}\n\n// zoek directe child met key-fragment\nfunction child(obj, fragment) {\n if (!obj || typeof obj !== 'object') return undefined;\n const frag = fragment.toLowerCase();\n for (const [k, v] of Object.entries(obj)) {\n if (k.toLowerCase().includes(frag)) return v;\n }\n return undefined;\n}\n\n// \"vandaag\" als yyyy-mm-dd in een timezone (Amsterdam)\nfunction todayYMD(tz = 'Europe/Amsterdam') {\n const parts = new Intl.DateTimeFormat('en-CA', {\n timeZone: tz,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n }).formatToParts(new Date());\n\n const m = {};\n for (const p of parts) if (p.type !== 'literal') m[p.type] = p.value;\n return `${m.year}-${m.month}-${m.day}`; // yyyy-mm-dd\n}\n\n// datum naar yyyy-mm-dd\nfunction fmtDate(raw) {\n if (!raw) return '';\n let d = String(raw).trim();\n\n // ISO: 2025-11-17T09:58:26\n const iso = d.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (iso) return `${iso[1]}-${iso[2]}-${iso[3]}`;\n\n d = d.replace(/\\./g, '-').replace(/\\//g, '-');\n\n const m = d.match(/^(\\d{1,2})-(\\d{1,2})-(\\d{2,4})$/);\n if (m) {\n let day = m[1].padStart(2, '0');\n let month = m[2].padStart(2, '0');\n let year = m[3];\n if (year.length === 2) year = '20' + year;\n return `${year}-${month}-${day}`;\n }\n\n const n = d.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n if (n) return d;\n\n const dt = new Date(d);\n if (!isNaN(dt.getTime())) return dt.toISOString().substring(0, 10);\n\n return d;\n}\n\n// yyyy-mm-dd -> eerstvolgende werkdag (ma-vr), altijd minimaal +1 dag\nfunction nextWorkdayYMD(ymd) {\n if (!ymd || !/^\\d{4}-\\d{2}-\\d{2}$/.test(ymd)) return ymd;\n\n const [Y, M, D] = ymd.split('-').map(Number);\n const dt = new Date(Date.UTC(Y, M - 1, D));\n\n dt.setUTCDate(dt.getUTCDate() + 1);\n\n while (dt.getUTCDay() === 0 || dt.getUTCDay() === 6) {\n dt.setUTCDate(dt.getUTCDate() + 1);\n }\n\n return dt.toISOString().slice(0, 10);\n}\n\nfunction fmtTimeFromIso(raw) {\n if (!raw) return '';\n const m = String(raw).match(/T(\\d{2}:\\d{2})(:\\d{2})?/);\n return m ? m[1] : '';\n}\n\nconst normDec = (s) =>\n s ? String(s).replace(',', '.') : '';\n\nconst toNumber = (s) => {\n const n = parseFloat(String(s ?? '0').replace(',', '.'));\n return isNaN(n) ? 0 : n;\n};\n\n// ---------- juiste bron-node pakken ----------\n\n// Soms zit alles onder één top-key (bijv. \"NYCE.LOGIC\")\nlet root = $json;\nconst rootKeys = Object.keys(root);\nif (rootKeys.length === 1 && typeof root[rootKeys[0]] === 'object') {\n root = root[rootKeys[0]];\n}\n\n// zoek CustomerOrderDelivery in de boom\nconst cod = findNodeRecursive(root, 'CustomerOrderDelivery') || root;\nif (!cod || typeof cod !== 'object') {\n throw new Error('CustomerOrderDelivery niet gevonden in XML-JSON. Check de output van de XML-node.');\n}\n\n// ---------- CustomProperties (voor TMS_Number, etc.) ----------\nconst customPropsContainer =\n child(cod, 'CustomProperties') || {};\n\nconst propsRaw =\n child(customPropsContainer, 'CustomerOrderDeliveryCustomProperty') || [];\n\nconst props = ensureArray(propsRaw);\n\nconst findProp = (name) => {\n const p = props.find(p =>\n (p.Name ?? p.name ?? (p.Name?._) ?? '').toString() === name\n );\n if (!p) return '';\n const v = p.Value ?? p.value ?? (p.Value?._);\n return v != null ? String(v) : '';\n};\n\nconst tmsNumber = findProp('TMS_Number'); // 189255\nconst totalQtyProp = findProp('TotalQuantity');\nconst totalWeightProp = findProp('TotalWeight');\nconst totalVolumeProp = findProp('TotalVolume');\n\n// ---------- Warehouse (pickup) ----------\nconst warehouse = child(cod, 'Warehouse') || {};\nconst whAddr = child(warehouse, 'Address') || {};\nconst whAddrLines = child(whAddr, 'AddressLines') || {};\nconst whAddr1 = joinLines(whAddrLines.String);\nconst whCity = whAddr.City || '';\nconst whZip = whAddr.ZipCode || '';\nconst whCountryObj = whAddr.Country || {};\nconst whCountry = whCountryObj.Code || whCountryObj.IsoCode || '';\n\n// ---------- Order + lines ----------\nconst customerOrders = child(cod, 'CustomerOrders') || {};\nconst firstOrderRaw = child(customerOrders, 'CustomerOrder') || {};\nconst order = Array.isArray(firstOrderRaw) ? firstOrderRaw[0] : firstOrderRaw;\n\nconst shipDate = order.ShipDate; // bv. 2025-11-13T09:58:26\n\n// Texts -> TRANSPORT_INSTRUCTIONS\nlet transportInstr = '';\nconst textsContainer = order.Texts || {};\nconst orderTexts = ensureArray(textsContainer.CustomerOrderText);\norderTexts.forEach(t => {\n const tt = t.TextType || {};\n const ttCode = tt.Code || (tt.Code?._);\n if (ttCode === 'TRANSPORT_INSTRUCTIONS') {\n const tl = t.TextLines || {};\n transportInstr = joinLines(tl.String);\n }\n});\n\n// Lines -> first line for RequestedDeliveryDate\nconst linesContainer = child(cod, 'Lines') || {};\nconst linesRaw = child(linesContainer, 'CustomerOrderLineDelivery') || [];\nconst lines = ensureArray(linesRaw);\nconst firstLine = lines[0] || {};\nconst reqDelDate = firstLine.RequestedDeliveryDate;\n\n// ---------- Delivery address ----------\nconst delAddr = child(cod, 'CustomerOrderDeliveryCustomerAddress') || {};\nconst delAddrLines = child(delAddr, 'AddressLines') || {};\nconst delAddr1 = joinLines(delAddrLines.String);\nconst delCity = delAddr.City || '';\nconst delZip = delAddr.ZipCode || '';\nconst delCountryObj = delAddr.Country || {};\nconst delCountry = delCountryObj.Code || '';\n\nconst delContact = delAddr.MainContact || {};\nconst delContactPhone = delContact.MobilePhoneNumber || '';\nconst delContactName = delContact.Name || '';\n\n// klantnaam\nconst custName =\n cod.CustomerOrderDeliveryCustomerName ||\n (cod.CustomerOrderDeliveryCustomerName && cod.CustomerOrderDeliveryCustomerName._) ||\n '';\n\n// ---------- ShipmentReferences -> goodsLines met afmetingen ----------\nconst shipmentRefsContainer = child(cod, 'ShipmentReferences') || {};\nconst shipmentRefsRaw = child(shipmentRefsContainer, 'ShipmentReference') || [];\nconst shipmentRefs = ensureArray(shipmentRefsRaw);\n\nconst goodsLinesArr = [];\n\nshipmentRefs.forEach(sr => {\n const srWeight = toNumber(sr.Weight);\n const srVolume = toNumber(sr.Volume);\n const srLength = toNumber(sr.Length);\n const srWidth = toNumber(sr.Width);\n const srHeight = toNumber(sr.Height);\n const srLM = toNumber(sr.LoadingMeters);\n\n const pmContainer = child(sr, 'PackingMaterials') || {};\n const pmRaw = child(pmContainer, 'ShipmentReferenceToPackingMaterialQuantity') || [];\n const pmList = ensureArray(pmRaw);\n\n // totaal aantal colli/pallets binnen deze ShipmentReference\n let totalQtyThisSR = 0;\n pmList.forEach(pm => {\n const qty = toNumber(pm.Quantity ?? (pm.Quantity && pm.Quantity._));\n totalQtyThisSR += qty;\n });\n if (totalQtyThisSR <= 0) totalQtyThisSR = 1;\n\n pmList.forEach(pm => {\n const packing = pm.PackingMaterial || {};\n const code = packing.Code || (packing.Code && packing.Code._) || '';\n const qty = toNumber(pm.Quantity ?? (pm.Quantity && pm.Quantity._));\n if (!code || qty <= 0) return;\n\n const share = qty / totalQtyThisSR;\n\n goodsLinesArr.push({\n code,\n qty,\n length: srLength,\n width: srWidth,\n height: srHeight,\n loadingmeter: srLM * share,\n weight: srWeight * share,\n volume: srVolume * share,\n });\n });\n});\n\n// aggregaten voor cargo\nlet totalPallets = 0;\nlet totalWeightFromRefs = 0;\nlet totalVolumeFromRefs = 0;\nlet totalLoadingMeters = 0;\n\ngoodsLinesArr.forEach(gl => {\n totalPallets += gl.qty;\n totalWeightFromRefs += gl.weight;\n totalVolumeFromRefs += gl.volume;\n totalLoadingMeters += gl.loadingmeter;\n});\n\n// fallback op custom properties als ShipmentReferences leeg zijn\nif (totalPallets === 0 && totalQtyProp) {\n totalPallets = toNumber(totalQtyProp);\n}\nif (totalWeightFromRefs === 0 && totalWeightProp) {\n totalWeightFromRefs = toNumber(totalWeightProp);\n}\nif (totalVolumeFromRefs === 0 && totalVolumeProp) {\n totalVolumeFromRefs = toNumber(totalVolumeProp);\n}\n\n// ---------- referenties ----------\nconst tourId = tmsNumber || cod.Number || order.Number || '';\nconst shipmentRef = order.Number || cod.DeliveryKeyCode || cod.Number || '';\n\n// DeliveryKeyCode als referentie 29 (als aanwezig)\nconst deliveryKeyCode = cod.DeliveryKeyCode || '';\n\n// DeliveryCustomerCode (voor combinatie in booking reference)\nconst deliveryCustomerCode = cod.DeliveryCustomerCode || '';\n\n// data/tijden\nconst pickupDateRaw = todayYMD('Europe/Amsterdam');\nconst pickupDate = nextWorkdayYMD(pickupDateRaw);\nconst pickupTime = fmtTimeFromIso(shipDate);\nconst deliveryDate = fmtDate(reqDelDate || shipDate);\nconst deliveryTime = fmtTimeFromIso(reqDelDate);\n\n// cargo aggregate\nconst cargoUnitAmount = totalPallets > 0 ? String(totalPallets) : '1';\nconst cargoWeight = totalWeightFromRefs > 0 ? String(totalWeightFromRefs) : (normDec(totalWeightProp) || '0');\nconst cargoVolume = totalVolumeFromRefs > 0 ? String(totalVolumeFromRefs) : (normDec(totalVolumeProp) || '0');\n// afmetingen & LM NIET meer op cargo; daar 0 / leeg\nconst cargoLength = '0';\nconst cargoWidth = '0';\nconst cargoHeight = '0';\nconst cargoLoadingMeter= '0';\n\nconst cargoDescription = `Order ${shipmentRef} - ${custName}`;\n\n// ---------- Transpas XML bouwen ----------\n// bool2 in cargo: true als transportInstr (escaped) \"AVIS\" bevat\nconst bool2Cargo = xmlEsc(transportInstr).toUpperCase().includes('AVIS');\nconst bool1Cargo = xmlEsc(transportInstr).toUpperCase().includes('TAILLIFT'); \nlet out = '';\nout += '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\nout += '<transportbookings>\\n';\nout += ' <transportbooking>\\n';\nout += ` <edireference>${xmlEsc(tourId)}</edireference>\\n`;\n\n// referentie = DeliveryCustomerCode + '_' + shipmentRef\nconst bookingRef =\n (deliveryCustomerCode || '') +\n (deliveryCustomerCode && shipmentRef ? ' - ' : '') +\n (shipmentRef || '');\n\nout += ` <reference>${xmlEsc(bookingRef)}</reference>\\n`;\nout += ' <customer_id matchmode=\"1\">9850434</customer_id>\\n';\nout += ' <debtor_id matchmode=\"1\">9850434</debtor_id>\\n';\nout += ' <department_id matchmode=\"3\">Dep DeWit</department_id>\\n';\nout += ' <shipments>\\n';\nout += ' <shipment>\\n';\nout += ` <edireference>${xmlEsc(shipmentRef)}</edireference>\\n`;\nout += ` <reference>${xmlEsc(shipmentRef)}</reference>\\n`;\nout += ' <shipmentkind_id matchmode=\"1\"/>\\n';\nout += ' <version>1</version>\\n';\n\n// Pickup (warehouse)\nout += ' <pickupaddress>\\n';\nout += ' <address_id matchmode=\"11\" overwrite=\"false\"></address_id>\\n';\nout += ` <date>${pickupDate}</date>\\n`;\nout += ` <time>${xmlEsc(pickupTime)}</time>\\n`;\nout += ` <datetill>${pickupDate}</datetill>\\n`;\nout += ' <timetill></timetill>\\n';\nout += ` <reference>${xmlEsc(shipmentRef)} - ${xmlEsc(tourId)}</reference>\\n`;\nout += ` <name>${xmlEsc(warehouse.Name ?? '')}</name>\\n`;\nout += ` <address1>${xmlEsc(whAddr1)}</address1>\\n`;\nout += ` <zipcode>${xmlEsc(whZip)}</zipcode>\\n`;\nout += ` <city_id matchmode=\"4\">${xmlEsc(whCity)}</city_id>\\n`;\nout += ` <country_id matchmode=\"2\">${xmlEsc(whCountry)}</country_id>\\n`;\nout += ' </pickupaddress>\\n';\n\n// Delivery (klant)\nout += ' <deliveryaddress>\\n';\nout += ' <address_id matchmode=\"5\" overwrite=\"false\"></address_id>\\n';\nout += ` <date>${deliveryDate}</date>\\n`;\n// out += ` <time>${xmlEsc(deliveryTime)}</time>\\n`;\nout += ` <datetill>${deliveryDate}</datetill>\\n`;\n// out += ` <timetill>${xmlEsc(deliveryTime)}</timetill>\\n`;\nout += ` <reference>Light & Living</reference>\\n`;\nout += ` <name>${xmlEsc(custName)}</name>\\n`;\nout += ` <address1>${xmlEsc(delAddr1)}</address1>\\n`;\nout += ` <zipcode>${xmlEsc(delZip)}</zipcode>\\n`;\nout += ` <city_id matchmode=\"4\">${xmlEsc(delCity)}</city_id>\\n`;\nout += ` <country_id matchmode=\"2\">${xmlEsc(delCountry)}</country_id>\\n`;\nout += ' <email></email>\\n';\nout += ` <phone>${xmlEsc(delContactPhone)}</phone>\\n`;\nout += ` <driverinfo>${xmlEsc(transportInstr)}</driverinfo>\\n`;\nout += ' </deliveryaddress>\\n';\n\n// References\nout += ' <references>\\n';\nout += ' <reference>\\n';\nout += ' <referencekind_id matchmode=\"0\" autocreate=\"false\">29</referencekind_id>\\n';\nout += ` <description>${xmlEsc(deliveryKeyCode)}</description>\\n`;\nout += ' </reference>\\n';\nout += ' </references>\\n';\n\n// Cargo aggregate\nout += ' <cargo>\\n';\nout += ` <unitamount>${xmlEsc(cargoUnitAmount)}</unitamount>\\n`; // totaal # pallets\nout += ' <unit_id matchmode=\"1\">591</unit_id>\\n'; // generieke pallet in Transpas\nout += ' <product_id matchmode=\"1\">123</product_id>\\n';\nout += ` <productdescription>${xmlEsc(cargoDescription)}</productdescription>\\n`;\nout += ` <weight>${xmlEsc(cargoWeight)}</weight>\\n`;\nout += ` <length>${xmlEsc(cargoLength)}</length>\\n`; // blijft 0\nout += ` <width>${xmlEsc(cargoWidth)}</width>\\n`; // blijft 0\n// out += ` <loadingmeter>${xmlEsc(cargoLoadingMeter)}</loadingmeter>\\n`; // blijft 0\n//out += ` <volume>${xmlEsc(cargoVolume)}</volume>\\n`;\nout += ` <height>${xmlEsc(cargoHeight)}</height>\\n`; // blijft 0\nout += ` <bool1>${bool1Cargo ? 'true' : 'false'}</bool1>\\n`;\nout += ` <bool2>${bool2Cargo ? 'true' : 'false'}</bool2>\\n`;\n\n// Goodslines: één per ShipmentReferenceToPackingMaterialQuantity\nout += ' <goodslines>\\n';\n\nif (goodsLinesArr.length > 0) {\n let seq = 1;\n goodsLinesArr.forEach(gl => {\n out += ' <goodsline>\\n';\n out += ` <sequence>${seq++}</sequence>\\n`;\n out += ` <unitamount>${xmlEsc(String(gl.qty))}</unitamount>\\n`;\n out += ` <unit_id matchmode=\"1\">${xmlEsc(gl.code)}</unit_id>\\n`; // bv. EUROPALLET_HT\n out += ' <product_id matchmode=\"1\">123</product_id>\\n';\n out += ` <length>${xmlEsc(String(gl.length || 0))}</length>\\n`;\n out += ` <width>${xmlEsc(String(gl.width || 0))}</width>\\n`;\n out += ` <height>${xmlEsc(String(gl.height || 0))}</height>\\n`;\n// out += ` <loadingmeter>${xmlEsc(String(gl.loadingmeter || 0))}</loadingmeter>\\n`;\n out += ` <weight>${xmlEsc(String(gl.weight || 0))}</weight>\\n`;\n // out += ` <volume>${xmlEsc(String(gl.volume || 0))}</volume>\\n`;\n out += ' <exchangeemballage>false</exchangeemballage>\\n';\n out += ' </goodsline>\\n';\n });\n} else {\n // fallback (oude gedrag)\n out += ' <goodsline>\\n';\n out += ' <sequence>1</sequence>\\n';\n out += ` <unitamount>${xmlEsc(cargoUnitAmount)}</unitamount>\\n`;\n out += ' <unit_id matchmode=\"1\">591</unit_id>\\n';\n out += ' <product_id matchmode=\"1\">123</product_id>\\n';\n out += ' <length>0</length>\\n';\n out += ' <width>0</width>\\n';\n out += ' <height>0</height>\\n';\n out += ' <loadingmeter>0</loadingmeter>\\n';\n out += ` <weight>${xmlEsc(cargoWeight)}</weight>\\n`;\n out += ` <volume>${xmlEsc(cargoVolume)}</volume>\\n`;\n out += ' <exchangeemballage>false</exchangeemballage>\\n';\n out += ' </goodsline>\\n';\n}\n\nout += ' </goodslines>\\n';\n\nout += ' </cargo>\\n';\nout += ' </shipment>\\n';\nout += ' </shipments>\\n';\nout += ' </transportbooking>\\n';\nout += '</transportbookings>\\n';\nout = repairXml(out);\n\n\n// Bestandsnaam\nconst filename = `lightlivinx_${tourId || shipmentRef || 'unknown'}.xml`;\nconst inputPath = $json.path || null;\n\n// Per item precies één object teruggeven\nreturn {\n json: {\n filename,\n path: inputPath,\n xml: out,\n }\n};\n"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
800,
|
|
144
|
|
],
|
|
"id": "67f2ac6b-7469-4eeb-80ac-2bcb4b1c962b",
|
|
"name": "Transform europal → Transpas"
|
|
}
|
|
],
|
|
"pinData": {},
|
|
"connections": {
|
|
"Extract from File1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Parse XML to JSON",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"FTP Upload1": {
|
|
"main": [
|
|
[],
|
|
[]
|
|
]
|
|
},
|
|
"Parse XML to JSON": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Transform europal → Transpas1",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "Insert row",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Check FTP Every 15 Minutes": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "FTP VOS",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Transform europal → Transpas1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "FTP Upload1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"FTP VOS": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "FTP",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"FTP": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Extract from File1",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "Delete file",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "Save file in Verwerkt",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Delete file": {
|
|
"main": [
|
|
[]
|
|
]
|
|
}
|
|
},
|
|
"active": true,
|
|
"settings": {
|
|
"executionOrder": "v1",
|
|
"binaryMode": "separate",
|
|
"timeSavedMode": "fixed",
|
|
"errorWorkflow": "1Ps5lukDf2sgcGL7",
|
|
"callerPolicy": "workflowsFromSameOwner",
|
|
"availableInMCP": false
|
|
},
|
|
"versionId": "ce653f7b-0083-488a-b3c4-db7062904ed4",
|
|
"meta": {
|
|
"templateCredsSetupCompleted": true,
|
|
"instanceId": "bef8d409866a58c0777dfe7cca1b9c2400fd051c056d361501393ab423006b5f"
|
|
},
|
|
"nodeGroups": [],
|
|
"id": "cQHDrzPhT9jeViFR",
|
|
"tags": []
|
|
} |