{ "name": "Palletways Truckload Manifest", "nodes": [ { "parameters": { "url": "=https://api.palletways.com/lookupDepotNo/{{ $json.Response.Detail.Data.Manifest.Depot.Number }}?apikey=SXJaM7PLjZDqfsnXSDP8Y9wDY6crxJySBg705MQEPus%3D", "options": { "response": { "response": { "responseFormat": "text" } }, "timeout": 10000 } }, "id": "71e6c357-2553-4bb1-8673-9f79a1f29a34", "name": "Get Paying Depot Address", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.1, "position": [ 1408, -368 ], "continueOnFail": true }, { "parameters": { "options": { "explicitArray": false, "mergeAttrs": true } }, "id": "b6c0ed30-64e2-4981-848c-5ca4333851ff", "name": "Parse Paying Depot XML", "type": "n8n-nodes-base.xml", "typeVersion": 1, "position": [ 1632, -368 ] }, { "parameters": { "mode": "combine", "combineBy": "combineByPosition", "options": {} }, "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ 1856, -304 ], "id": "d9f28643-b9d4-450b-b1f6-055261bbf730", "name": "Merge" }, { "parameters": { "functionCode": "// Zet deze Code node bij voorkeur op: \"Run once for all items\"\n\nconst get = (obj, path) =>\n path.split('.').reduce((a, k) => (a && a[k] !== undefined ? a[k] : undefined), obj);\n\nconst nz = (v, d = '') => (v === undefined || v === null ? d : v);\nconst ensureArray = (v) => (Array.isArray(v) ? v : v == null ? [] : [v]);\n\nconst cc = (c) => {\n if (!c) return 'NL';\n const u = String(c).toUpperCase();\n return u === 'UK' ? 'GB' : u;\n};\n\nconst xmlEsc = (s) =>\n String(s ?? '')\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n\nconst parseNum = (v) => {\n if (v === undefined || v === null || v === '') return 0;\n if (typeof v === 'number') return v;\n let s = String(v).trim();\n s = s.replace(',', '.');\n const n = Number(s);\n return isNaN(n) ? 0 : n;\n};\n\nconst firstOf = (obj, paths, def = undefined) => {\n for (const p of paths) {\n const val = get(obj, p);\n if (val !== undefined) return val;\n }\n return def;\n};\n\nfunction buildOne(input) {\n const cons = get(input, 'Response.Detail.Data.Manifest.Depot.Account.Consignment') || {};\n const depotRoot = get(input, 'Response.Detail.Data') || {};\n\n const accountName = nz(get(input, 'Response.Detail.Data.Manifest.Depot.Account.Name'), '');\n const number = nz(cons.Number, '');\n\n // Basisvelden\n const trackingId = nz(cons.TrackingID, nz(cons.Reference, ''));\n const reference = nz(cons.Reference, trackingId);\n const weight = parseNum(nz(cons.Weight, 0));\n const dueDate = nz(cons.DueDate, new Date().toISOString().split('T')[0]);\n const dueTime = nz(cons.DueTime, '') === '20:00' ? '' : nz(cons.DueTime, '');\n const driverInfo = nz(cons.ManifestNote, '');\n\n // Adressen\n const addresses = ensureArray(cons.Address);\n const deliveryAddress = addresses.find((a) => /delivery/i.test(a?.Type || '')) || {};\n const collectionAddress = addresses.find((a) => /collection/i.test(a?.Type || '')) || {};\n\n // Paying depot\n const payingDepot = {\n name: nz(\n depotRoot.CompanyName,\n nz(get(input, 'Response.Detail.Data.Manifest.Depot.Account.Name'), 'Palletways')\n ),\n address1: nz(depotRoot.AddressLine1, ''),\n address2: nz(depotRoot.AddressLine2, ''),\n zipcode: nz(depotRoot.Postcode, ''),\n city: nz(depotRoot.Town, ''),\n country: cc(nz(depotRoot.Countrycode, 'NL')),\n phone: nz(depotRoot.Phone, ''),\n email: nz(depotRoot.Email, ''),\n };\n\n // Return/EU => cargo qty1 (leeg als ontbreekt)\n let euroQty1 = '';\n const returnLines = ensureArray(get(cons, 'Return'));\n for (const r of returnLines) {\n const t = String(nz(r?.Type, '')).toUpperCase();\n if (t === 'EU') {\n const raw = r?.Amount;\n if (raw !== undefined && raw !== null && String(raw).trim() !== '') {\n euroQty1 = String(parseNum(raw));\n }\n break;\n }\n }\n\n // cargo unitamount = lifts\n const lifts = parseNum(nz(cons.Lifts, 1)) || 1;\n\n // BookInRequest -> bool2\n const bookInRaw = String(nz(firstOf(cons, ['BookInRequest', 'Delivery.BookInRequest'], ''))).toLowerCase();\n const bool2 = ['yes', 'true', '1', 'y', 'ja'].includes(bookInRaw);\n\n // taillift -> bool1\n const taillift = String(nz(firstOf(cons, ['TailLift', 'Delivery.TailLift'], ''))).toLowerCase();\n const bool1 = ['yes', 'true', '1', 'y', 'ja'].includes(taillift);\n\n // Prijs\n const csvPrice = parseNum(firstOf(input, ['bedrag'], 0));\n\n // ---- Goodsline opbouw ----\n // 1 goodsline per \"stuk\" in BillUnit (Amount), unitamount=1\n const billUnitsRaw = ensureArray(get(cons, 'BillUnit'));\n const billUnits = billUnitsRaw\n .map((bu) => ({\n type: String(nz(bu?.Type, 'HP')).trim(),\n amount: parseNum(nz(bu?.Amount, 0)),\n }))\n .filter((x) => x.type && x.amount > 0);\n\n // Barcodes uit cons.Pallet (kan string of array zijn)\n const palletsArr = ensureArray(get(cons, 'Pallet'))\n .map((x) => String(x || '').trim())\n .filter((x) => x);\n\n // Eerste barcode ook op shipment-niveau\n const shipmentBarcode = palletsArr.length > 0 ? palletsArr[0] : '';\n\n // goodsUnits: 1 entry = 1 goodsline\n const goodsUnits = [];\n\n if (billUnits.length > 0) {\n for (const bu of billUnits) {\n let cnt = Math.round(bu.amount);\n if (cnt <= 0) cnt = 1;\n for (let i = 0; i < cnt; i++) {\n goodsUnits.push({ unit_id: bu.type });\n }\n }\n } else if (palletsArr.length > 0) {\n // geen BillUnit, wel pallets => 1 goodsline per pallet-barcode\n for (let i = 0; i < palletsArr.length; i++) goodsUnits.push({ unit_id: 'HP' });\n } else {\n goodsUnits.push({ unit_id: 'HP' });\n }\n\n const totalLines = goodsUnits.length || 1;\n const weightPerLine = totalLines > 0 ? (weight / totalLines) : 0;\n\n // Als goodsLines > lifts: vanaf lifts-index => oversized (geen barcode + goodsline productdescription)\n const hasOversized = totalLines > lifts;\n\n const goods = [];\n for (let i = 0; i < goodsUnits.length; i++) {\n const oversized = hasOversized && i >= lifts;\n\n // Barcode alleen invullen als beschikbaar én niet oversized\n const barcode = (!oversized && palletsArr[i]) ? palletsArr[i] : '';\n\n goods.push({\n seq: i + 1,\n unitamount: 1,\n unit_id: goodsUnits[i].unit_id,\n barcode,\n oversized,\n weight: weightPerLine,\n });\n }\n\n // --------- XML ----------\n const xml = [];\n xml.push('');\n xml.push('');\n xml.push(' ');\n xml.push(' 0');\n xml.push(` ${xmlEsc(trackingId)}`);\n xml.push(' 55107');\n //xml.push(' 55107');\n xml.push(' 1');\n xml.push(' 1');\n xml.push(' ');\n xml.push(' ');\n xml.push(` ${xmlEsc(trackingId)}`);\n xml.push(` ${xmlEsc(trackingId)}`);\n\n xml.push(' 33');\n xml.push(' 55107');\n xml.push(' false');\n xml.push(' false');\n xml.push(' 2');\n // xml.push(` ${xmlEsc(csvPrice)}`);\n\n // sender\n xml.push(' ');\n xml.push(` ${xmlEsc(nz(collectionAddress.CompanyName, ''))}`);\n xml.push(` ${xmlEsc(nz(collectionAddress.Addr1, ''))}`);\n xml.push(` ${xmlEsc(nz(collectionAddress.Addr2, ''))}`);\n xml.push(` ${xmlEsc(nz(collectionAddress.PostCode, ''))}`);\n xml.push(` ${xmlEsc(nz(collectionAddress.Town, ''))}`);\n xml.push(` ${xmlEsc(cc(collectionAddress.Country || 'NL'))}`);\n xml.push(` ${xmlEsc(nz(collectionAddress.ContactName, ''))}`);\n xml.push(` ${xmlEsc(nz(collectionAddress.Telephone, ''))}`);\n xml.push(' ');\n\n // receiver\n xml.push(' ');\n xml.push(` ${xmlEsc(nz(deliveryAddress.CompanyName, ''))}`);\n xml.push(` ${xmlEsc(nz(deliveryAddress.Addr1, ''))}`);\n xml.push(` ${xmlEsc(nz(deliveryAddress.Addr2, ''))}`);\n xml.push(` ${xmlEsc(nz(deliveryAddress.PostCode, ''))}`);\n xml.push(` ${xmlEsc(nz(deliveryAddress.Town, ''))}`);\n xml.push(` ${xmlEsc(cc(deliveryAddress.Country || 'NL'))}`);\n xml.push(` ${xmlEsc(nz(deliveryAddress.ContactName, ''))}`);\n xml.push(` ${xmlEsc(nz(deliveryAddress.Telephone, ''))}`);\n xml.push(' ');\n\n const todayStr = new Date().toISOString().split('T')[0];\n\n // pickupaddress\n xml.push(' ');\n xml.push(' ');\n xml.push(` ${xmlEsc(todayStr)}`);\n xml.push(` ${xmlEsc(todayStr)}`);\n xml.push(' Palletways (UK) Ltd');\n xml.push(' Bijsterhuizen 1103 A');\n xml.push(' 6546 AS');\n xml.push(' Nijmegen');\n xml.push(' NL');\n xml.push(' ');\n\n // deliveryaddress\n xml.push(' ');\n xml.push(' ');\n xml.push(` ${xmlEsc(dueDate)}`);\n xml.push(` ${xmlEsc(dueDate)}`);\n xml.push(` ${xmlEsc(dueTime)}`);\n xml.push(` ${xmlEsc(nz(deliveryAddress.CompanyName, ''))}`);\n xml.push(` ${xmlEsc(nz(deliveryAddress.Addr1, ''))}`);\n xml.push(` ${xmlEsc(nz(deliveryAddress.Addr2, ''))}`);\n xml.push(` ${xmlEsc(nz(deliveryAddress.PostCode, ''))}`);\n xml.push(` ${xmlEsc(nz(deliveryAddress.Town, ''))}`);\n xml.push(` ${xmlEsc(cc(deliveryAddress.Country || 'NL'))}`);\n xml.push(` ${xmlEsc(nz(deliveryAddress.ContactName, ''))}`);\n xml.push(` ${xmlEsc(nz(deliveryAddress.Telephone, ''))}`);\n xml.push(` ${xmlEsc(driverInfo)}`);\n xml.push(' ');\n\n // references\n xml.push(' ');\n xml.push(\n ` 15${xmlEsc(\n trackingId\n )}`\n );\n xml.push(\n ` 16${xmlEsc(\n number\n )}`\n );\n if (accountName) {\n xml.push(\n ` 31${xmlEsc(\n accountName\n )}`\n );\n }\n xml.push(' ');\n\n // parties\n xml.push(' ');\n xml.push(' ');\n xml.push(' 5');\n xml.push(` ${xmlEsc(payingDepot.name)}`);\n xml.push(` ${xmlEsc(payingDepot.address1)}`);\n xml.push(` ${xmlEsc(payingDepot.address2)}`);\n xml.push(` ${xmlEsc(payingDepot.zipcode)}`);\n xml.push(` ${xmlEsc(payingDepot.city)}`);\n xml.push(` ${xmlEsc(payingDepot.country)}`);\n xml.push(` ${xmlEsc(payingDepot.phone)}`);\n xml.push(` ${xmlEsc(payingDepot.email)}`);\n xml.push(' ');\n xml.push(' ');\n\n // cargo\n xml.push(' ');\n xml.push(` ${xmlEsc(lifts)}`);\n xml.push(' 591');\n xml.push(' 123');\n xml.push(' General Cargo');\n xml.push(` ${xmlEsc(weight)}`);\n xml.push(` ${xmlEsc(euroQty1)}`);\n xml.push(` ${bool1 ? 'true' : 'false'}`);\n xml.push(` ${bool2 ? 'true' : 'false'}`);\n // eerste barcode onder shipment\n if (shipmentBarcode) {\n xml.push(` ${xmlEsc(shipmentBarcode)}`);\n xml.push(' ');\n }\n for (const g of goods) {\n xml.push(' ');\n xml.push(` ${xmlEsc(g.seq)}`);\n xml.push(` ${xmlEsc(g.unitamount)}`);\n xml.push(` ${xmlEsc(g.unit_id)}`);\n xml.push(` ${xmlEsc(g.weight)}`);\n\n // barcode alleen als aanwezig\n if (g.barcode) {\n xml.push(` ${xmlEsc(g.barcode)}`);\n }\n\n // oversized: productdescription op goodsline\n if (g.oversized) {\n xml.push(` Oversized pallet`);\n }\n\n xml.push(' ');\n }\n\n xml.push(' ');\n xml.push(' ');\n\n xml.push(' ');\n xml.push(' ');\n xml.push(' ');\n xml.push('');\n\n const xmlStr = xml.join('\\n');\n const fileName = trackingId ? `${trackingId}.xml` : `booking_${Date.now()}.xml`;\n\n return { filename: fileName, xml: xmlStr };\n}\n\n// -------- input handling (meerdere zendingen) --------\nlet inputs = [];\nif (typeof $input?.all === 'function') {\n inputs = $input.all().map((i) => i.json);\n} else if (Array.isArray($json)) {\n inputs = $json;\n} else {\n inputs = [$json || {}];\n}\n\n// output: 1 item per consignment\nreturn inputs.map((inp) => ({ json: buildOne(inp) }));" }, "id": "42726aff-fee0-4bee-adc8-500bbb1d4387", "name": "Transform to Transpas Format v3", "type": "n8n-nodes-base.function", "typeVersion": 1, "position": [ 2080, -304 ] }, { "parameters": { "operation": "upload", "path": "=/prod/ToTP/palletways/tlm_{{ $json.filename }}", "binaryData": false, "fileContent": "={{ $json.xml }}", "options": {} }, "type": "n8n-nodes-base.ftp", "typeVersion": 1, "position": [ 2304, -304 ], "id": "fa3517ff-052f-40b6-b833-3e984e69deb9", "name": "FTP", "credentials": { "ftp": { "id": "oLAZ4OgmkOopMHAq", "name": "FTP De Wit Transport" } } }, { "parameters": { "rule": { "interval": [ { "field": "minutes", "minutesInterval": 20 } ] } }, "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1.2, "position": [ 288, -208 ], "id": "baab84b8-4064-4126-8eb1-5e612c9a6b25", "name": "Schedule Trigger" }, { "parameters": { "operation": "list", "path": "/from_pw/TLM/", "options": {} }, "type": "n8n-nodes-base.ftp", "typeVersion": 1, "position": [ 512, -208 ], "id": "1bdb9819-1a83-4221-bf90-3a356450fbff", "name": "FTP List", "credentials": { "ftp": { "id": "m8VXjzicG82nM2ph", "name": "FTP Palletways" } } }, { "parameters": { "path": "={{ $json.path }}", "options": {} }, "type": "n8n-nodes-base.ftp", "typeVersion": 1, "position": [ 736, -208 ], "id": "34f7bef7-9c4a-43b4-ac67-d2aa3b01ee99", "name": "FTP Download", "credentials": { "ftp": { "id": "m8VXjzicG82nM2ph", "name": "FTP Palletways" } } }, { "parameters": { "operation": "xml", "options": {} }, "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ 960, -208 ], "id": "69aebc53-64c6-4596-a060-a3a19b66dcbf", "name": "Extract from File" }, { "parameters": { "operation": "delete", "path": "={{ $('FTP Download').item.json.path }}", "options": {} }, "type": "n8n-nodes-base.ftp", "typeVersion": 1, "position": [ 1184, -112 ], "id": "804fd72e-e41a-4886-817b-76d2f34ea2a7", "name": "FTP1", "credentials": { "ftp": { "id": "m8VXjzicG82nM2ph", "name": "FTP Palletways" } } }, { "parameters": { "options": {} }, "type": "n8n-nodes-base.xml", "typeVersion": 1, "position": [ 1184, -304 ], "id": "1756f324-880b-4967-8f67-75fd55e1e0de", "name": "XML" } ], "pinData": {}, "connections": { "Schedule Trigger": { "main": [ [ { "node": "FTP List", "type": "main", "index": 0 } ] ] }, "Get Paying Depot Address": { "main": [ [ { "node": "Parse Paying Depot XML", "type": "main", "index": 0 } ] ] }, "Parse Paying Depot XML": { "main": [ [ { "node": "Merge", "type": "main", "index": 0 } ] ] }, "Merge": { "main": [ [ { "node": "Transform to Transpas Format v3", "type": "main", "index": 0 } ] ] }, "Transform to Transpas Format v3": { "main": [ [ { "node": "FTP", "type": "main", "index": 0 } ] ] }, "FTP": { "main": [ [] ] }, "FTP List": { "main": [ [ { "node": "FTP Download", "type": "main", "index": 0 } ] ] }, "FTP Download": { "main": [ [ { "node": "Extract from File", "type": "main", "index": 0 } ] ] }, "Extract from File": { "main": [ [ { "node": "FTP1", "type": "main", "index": 0 }, { "node": "XML", "type": "main", "index": 0 } ] ] }, "XML": { "main": [ [ { "node": "Merge", "type": "main", "index": 1 }, { "node": "Get Paying Depot Address", "type": "main", "index": 0 } ] ] } }, "active": true, "settings": { "executionOrder": "v1", "binaryMode": "separate", "timezone": "Europe/Amsterdam", "callerPolicy": "workflowsFromSameOwner", "availableInMCP": false, "timeSavedMode": "fixed", "errorWorkflow": "1Ps5lukDf2sgcGL7" }, "versionId": "7ed6bf92-c7c4-4a29-ab9c-5bbad7f720b8", "meta": { "templateCredsSetupCompleted": true, "instanceId": "bef8d409866a58c0777dfe7cca1b9c2400fd051c056d361501393ab423006b5f" }, "nodeGroups": [], "id": "JJuapLJ3cHDAHYac", "tags": [] }