Files
2026-07-03 11:15:50 +02:00

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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\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, '&amp;');\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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\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, '&amp;');\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": []
}