Files
n8n-workflows/palletways/Palletways leveringen - outbound.json

643 lines
39 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"name": "Palletways leveringen - outbound",
"nodes": [
{
"parameters": {
"jsCode": "// Get today's date\nconst today = new Date();\n\n\nconst lastWorkday = new Date(today);\n\n// Format as YYYY-MM-DD\nconst yyyy = lastWorkday.getFullYear();\nconst mm = String(lastWorkday.getMonth() + 1).padStart(2, '0');\nconst dd = String(lastWorkday.getDate()).padStart(2, '0');\nconst formattedDate = `${yyyy}-${mm}-${dd}`;\n\nreturn [\n {\n json: {\n lastWorkday: formattedDate\n }\n }\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
0,
176
],
"id": "79604baf-6a53-4f92-8d60-6a9517c89753",
"name": "Last workday1"
},
{
"parameters": {
"jsCode": "/**\n * n8n “Function” node\n * Input : first items json.data (XML string)\n * Output: one item per unique <dc_syscon>, shaped as { json: { dc_syscon: \"<value>\" } }\n */\n\n// 1️⃣ Grab the XML from the first incoming item\nconst xmlData = items[0]?.json?.data;\nif (typeof xmlData !== 'string') {\n\tthrow new Error('Expected an XML string in $input.first().json.data');\n}\n\n// 2️⃣ Collect every <dc_syscon>...</dc_syscon> match\nconst dcSysconValues = [];\nconst regex = /<TrackingID>(.*?)<\\/TrackingID>/g;\nlet match;\nwhile ((match = regex.exec(xmlData)) !== null) {\n\tdcSysconValues.push(match[1].trim().toUpperCase());\n}\n\n// 3️⃣ Deduplicate\nconst uniqueValues = [...new Set(dcSysconValues)];\n\n// 4️⃣ Build one output item per unique value\nreturn uniqueValues.map(value => ({\n\tjson: { trackingid: value },\n}));\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
448,
176
],
"id": "86570f68-7269-41fe-b468-af52f4d16b02",
"name": "Code"
},
{
"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": "814a05a5-b8c8-4a26-bfed-7a5744e33851",
"name": "Get Paying Depot Address",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
1360,
48
],
"continueOnFail": true
},
{
"parameters": {
"options": {
"explicitArray": false,
"mergeAttrs": true
}
},
"id": "e666bcfa-ac35-46f2-8179-4610923cf64f",
"name": "Parse Paying Depot XML",
"type": "n8n-nodes-base.xml",
"typeVersion": 1,
"position": [
1584,
48
]
},
{
"parameters": {
"options": {
"explicitArray": false,
"mergeAttrs": true
}
},
"id": "2d662be5-4701-4f75-8d4e-1aa06c3813ad",
"name": "Parse XML Response",
"type": "n8n-nodes-base.xml",
"typeVersion": 1,
"position": [
1200,
176
]
},
{
"parameters": {
"mode": "combine",
"combineBy": "combineByPosition",
"numberInputs": 3,
"options": {}
},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
1904,
160
],
"id": "fb7a9070-3276-4800-98e6-7b625c2a9875",
"name": "Merge"
},
{
"parameters": {
"url": "=https://api.palletways.com/getconsignment/{{ $json.trackingid }}?apikey=SXJaM7PLjZDqfsnXSDP8Y9wDY6crxJySBg705MQEPus%3D",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1008,
176
],
"id": "5a484f33-249d-46af-b664-cdecbb16a2ee",
"name": "Get Consigment"
},
{
"parameters": {
"fileFormat": "csv",
"options": {
"headerRow": false,
"includeEmptyCells": true
}
},
"id": "b7226c3d-4d33-4547-9617-020e5c62c164",
"name": "Import CSV",
"type": "n8n-nodes-base.spreadsheetFile",
"position": [
256,
0
],
"typeVersion": 2
},
{
"parameters": {
"url": "https://portal.palletways.com/api/Planning/?apikey=SXJaM7PLjZDqfsnXSDP8Y9wDY6crxJySBg705MQEPus%3D&output=csv",
"options": {
"response": {
"response": {
"responseFormat": "file"
}
}
}
},
"id": "874b6714-43dc-44d2-912f-a212c410e084",
"name": "Download CSV",
"type": "n8n-nodes-base.httpRequest",
"position": [
0,
0
],
"typeVersion": 4.1
},
{
"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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\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 // 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('<?xml version=\"1.0\" encoding=\"UTF-8\"?>');\n xml.push('<transportbookings>');\n xml.push(' <transportbooking>');\n xml.push(' <autoacceptmethod>0</autoacceptmethod>');\n xml.push(` <edireference>${xmlEsc(trackingId)}</edireference>`);\n xml.push(' <customer_id matchmode=\"8\">55107</customer_id>');\n xml.push(' <department_id>1</department_id>');\n xml.push(' <currency_id>1</currency_id>');\n xml.push(' <shipments>');\n xml.push(' <shipment>');\n xml.push(` <edireference>${xmlEsc(trackingId)}</edireference>`);\n xml.push(` <reference>${xmlEsc(trackingId)}</reference>`);\n xml.push(' <plangroup_id>33</plangroup_id>');\n xml.push(' <debtor_id>55107</debtor_id>');\n xml.push(' <version>1</version>');\n xml.push(` <fixedprice>${xmlEsc(csvPrice)}</fixedprice>`);\n\n // sender\n xml.push(' <sender>');\n xml.push(` <name>${xmlEsc(nz(collectionAddress.CompanyName, ''))}</name>`);\n xml.push(` <address1>${xmlEsc(nz(collectionAddress.Addr1, ''))}</address1>`);\n xml.push(` <address2>${xmlEsc(nz(collectionAddress.Addr2, ''))}</address2>`);\n xml.push(` <zipcode>${xmlEsc(nz(collectionAddress.PostCode, ''))}</zipcode>`);\n xml.push(` <city_id matchmode=\"4\">${xmlEsc(nz(collectionAddress.Town, ''))}</city_id>`);\n xml.push(` <country_id matchmode=\"2\">${xmlEsc(cc(collectionAddress.Country || 'NL'))}</country_id>`);\n xml.push(` <contact>${xmlEsc(nz(collectionAddress.ContactName, ''))}</contact>`);\n xml.push(` <phone>${xmlEsc(nz(collectionAddress.Telephone, ''))}</phone>`);\n xml.push(' </sender>');\n\n // receiver\n xml.push(' <receiver>');\n xml.push(` <name>${xmlEsc(nz(deliveryAddress.CompanyName, ''))}</name>`);\n xml.push(` <address1>${xmlEsc(nz(deliveryAddress.Addr1, ''))}</address1>`);\n xml.push(` <address2>${xmlEsc(nz(deliveryAddress.Addr2, ''))}</address2>`);\n xml.push(` <zipcode>${xmlEsc(nz(deliveryAddress.PostCode, ''))}</zipcode>`);\n xml.push(` <city_id matchmode=\"4\">${xmlEsc(nz(deliveryAddress.Town, ''))}</city_id>`);\n xml.push(` <country_id matchmode=\"2\">${xmlEsc(cc(deliveryAddress.Country || 'NL'))}</country_id>`);\n xml.push(` <contact>${xmlEsc(nz(deliveryAddress.ContactName, ''))}</contact>`);\n xml.push(` <phone>${xmlEsc(nz(deliveryAddress.Telephone, ''))}</phone>`);\n xml.push(' </receiver>');\n\n const todayStr = new Date().toISOString().split('T')[0];\n\n // pickupaddress\n xml.push(' <pickupaddress>');\n xml.push(' <address_id matchmode=\"11\" overwrite=\"false\"></address_id>');\n xml.push(` <date>${xmlEsc(todayStr)}</date>`);\n xml.push(` <datetill>${xmlEsc(todayStr)}</datetill>`);\n xml.push(' <name>Palletways UK</name>');\n xml.push(' <address1>Bijsterhuizen 1103 A</address1>');\n xml.push(' <zipcode>6546 AS</zipcode>');\n xml.push(' <city_id matchmode=\"4\">Nijmegen</city_id>');\n xml.push(' <country_id matchmode=\"2\">NL</country_id>');\n xml.push(' </pickupaddress>');\n\n // deliveryaddress\n xml.push(' <deliveryaddress>');\n xml.push(' <address_id matchmode=\"11\" overwrite=\"false\"></address_id>');\n xml.push(` <date>${xmlEsc(dueDate)}</date>`);\n xml.push(` <datetill>${xmlEsc(dueDate)}</datetill>`);\n xml.push(` <timetill>${xmlEsc(dueTime)}</timetill>`);\n xml.push(` <name>${xmlEsc(nz(deliveryAddress.CompanyName, ''))}</name>`);\n xml.push(` <address1>${xmlEsc(nz(deliveryAddress.Addr1, ''))}</address1>`);\n xml.push(` <address2>${xmlEsc(nz(deliveryAddress.Addr2, ''))}</address2>`);\n xml.push(` <zipcode>${xmlEsc(nz(deliveryAddress.PostCode, ''))}</zipcode>`);\n xml.push(` <city_id matchmode=\"4\">${xmlEsc(nz(deliveryAddress.Town, ''))}</city_id>`);\n xml.push(` <country_id matchmode=\"2\">${xmlEsc(cc(deliveryAddress.Country || 'NL'))}</country_id>`);\n xml.push(` <contact>${xmlEsc(nz(deliveryAddress.ContactName, ''))}</contact>`);\n xml.push(` <phone>${xmlEsc(nz(deliveryAddress.Telephone, ''))}</phone>`);\n xml.push(` <driverinfo>${xmlEsc(driverInfo)}</driverinfo>`);\n xml.push(' </deliveryaddress>');\n\n // references\n xml.push(' <references>');\n xml.push(\n ` <reference><referencekind_id>15</referencekind_id><description>${xmlEsc(\n trackingId\n )}</description></reference>`\n );\n xml.push(\n ` <reference><referencekind_id>16</referencekind_id><description>${xmlEsc(\n number\n )}</description></reference>`\n );\n if (accountName) {\n xml.push(\n ` <reference><referencekind_id>31</referencekind_id><description>${xmlEsc(\n accountName\n )}</description></reference>`\n );\n }\n xml.push(' </references>');\n\n // parties\n xml.push(' <parties>');\n xml.push(' <party>');\n xml.push(' <partytype_id>5</partytype_id>');\n xml.push(` <name>${xmlEsc(payingDepot.name)}</name>`);\n xml.push(` <address1>${xmlEsc(payingDepot.address1)}</address1>`);\n xml.push(` <address2>${xmlEsc(payingDepot.address2)}</address2>`);\n xml.push(` <zipcode>${xmlEsc(payingDepot.zipcode)}</zipcode>`);\n xml.push(` <city_id matchmode=\"4\">${xmlEsc(payingDepot.city)}</city_id>`);\n xml.push(` <country_id matchmode=\"2\">${xmlEsc(payingDepot.country)}</country_id>`);\n xml.push(` <phone>${xmlEsc(payingDepot.phone)}</phone>`);\n xml.push(` <email>${xmlEsc(payingDepot.email)}</email>`);\n xml.push(' </party>');\n xml.push(' </parties>');\n\n // cargo\n xml.push(' <cargo>');\n xml.push(` <unitamount>${xmlEsc(lifts)}</unitamount>`);\n xml.push(' <unit_id>591</unit_id>');\n xml.push(' <product_id>123</product_id>');\n xml.push(' <productdescription>General Cargo</productdescription>');\n xml.push(` <weight>${xmlEsc(weight)}</weight>`);\n xml.push(` <qty1>${xmlEsc(euroQty1)}</qty1>`);\n xml.push(` <bool1>${bool1 ? 'true' : 'false'}</bool1>`);\n xml.push(` <bool2>${bool2 ? 'true' : 'false'}</bool2>`);\n xml.push(' <goodslines>');\n\n for (const g of goods) {\n xml.push(' <goodsline>');\n xml.push(` <sequence>${xmlEsc(g.seq)}</sequence>`);\n xml.push(` <unitamount>${xmlEsc(g.unitamount)}</unitamount>`);\n xml.push(` <unit_id matchmode=\"1\">${xmlEsc(g.unit_id)}</unit_id>`);\n xml.push(` <weight>${xmlEsc(g.weight)}</weight>`);\n\n // barcode alleen als aanwezig\n if (g.barcode) {\n xml.push(` <barcode>${xmlEsc(g.barcode)}</barcode>`);\n }\n\n // oversized: productdescription op goodsline\n if (g.oversized) {\n xml.push(` <productdescription>Oversized pallet</productdescription>`);\n }\n\n xml.push(' </goodsline>');\n }\n\n xml.push(' </goodslines>');\n xml.push(' </cargo>');\n\n xml.push(' </shipment>');\n xml.push(' </shipments>');\n xml.push(' </transportbooking>');\n xml.push('</transportbookings>');\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": "7329843a-18b7-42b9-a6b3-2e37cfdedbc8",
"name": "Transform to Transpas Format v3",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
2208,
240
]
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
720,
176
],
"id": "bad74b47-8076-4fd2-a4c0-3e1c74b117d8",
"name": "Loop Over Items"
},
{
"parameters": {
"protocol": "sftp",
"operation": "upload",
"path": "=/dewit/test/to_tp/delivery_{{ $json.filename }}",
"binaryData": false,
"fileContent": "={{ $json.xml }}",
"options": {}
},
"type": "n8n-nodes-base.ftp",
"typeVersion": 1,
"position": [
2464,
240
],
"id": "bd99d659-63ee-4f66-98ee-f69e96030ee4",
"name": "FTP",
"credentials": {
"sftp": {
"id": "uKyzg5cSXQqXOuHI",
"name": "SFTP n8n"
}
}
},
{
"parameters": {
"amount": 0.5
},
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
2752,
320
],
"id": "a7c6078c-d03a-465e-a5e3-a50b82d2877f",
"name": "Wait",
"webhookId": "aca350c1-7a0b-4a9b-844e-c6be79e44f17"
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 21 * * 1-5"
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-288,
96
],
"id": "2cda5c63-4d88-48d7-a46d-0c7b360a6d05",
"name": "Schedule Trigger"
},
{
"parameters": {
"operation": "deleteRows",
"dataTableId": {
"__rl": true,
"value": "DFLK6lzDC3xNneBe",
"mode": "list",
"cachedResultName": "getobman",
"cachedResultUrl": "/projects/h6yWgTPhk7LTrXDI/datatables/DFLK6lzDC3xNneBe"
},
"filters": {
"conditions": [
{
"keyName": "trackingid",
"condition": "isNotEmpty"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
1008,
16
],
"id": "f38c1493-4716-43b4-aa41-b88722804062",
"name": "Delete row(s)"
},
{
"parameters": {
"operation": "upsert",
"dataTableId": {
"__rl": true,
"value": "DFLK6lzDC3xNneBe",
"mode": "list",
"cachedResultName": "getobman",
"cachedResultUrl": "/projects/h6yWgTPhk7LTrXDI/datatables/DFLK6lzDC3xNneBe"
},
"filters": {
"conditions": [
{
"keyName": "trackingid",
"keyValue": "={{ $json.row[0] }}"
}
]
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"trackingid": "={{ $json.row[0] }}",
"bedrag": "={{ $json.row[76] }}"
},
"matchingColumns": [],
"schema": [
{
"id": "trackingid",
"displayName": "trackingid",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "bedrag",
"displayName": "bedrag",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
512,
0
],
"id": "0071e496-3009-4217-91a7-132fdb546003",
"name": "Upsert row(s)"
},
{
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"value": "DFLK6lzDC3xNneBe",
"mode": "list",
"cachedResultName": "getobman",
"cachedResultUrl": "/projects/h6yWgTPhk7LTrXDI/datatables/DFLK6lzDC3xNneBe"
},
"filters": {
"conditions": [
{
"keyName": "trackingid",
"keyValue": "={{ $json.trackingid }}"
}
]
}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
1424,
336
],
"id": "18f6bde1-aaf1-4c49-91f9-846b160fc409",
"name": "Get row(s)",
"alwaysOutputData": true
},
{
"parameters": {
"url": "=https://portal.palletways.com/api/getObman/{{ $json.lastWorkday }}?apikey=&output=xml",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "apikey",
"value": "SXJaM7PLjZDqfsnXSDP8Y9wDY6crxJySBg705MQEPus%3D"
},
{
"name": "date",
"value": "={{ $json[\"lastWorkday\"] }}"
},
{
"name": "output",
"value": "xml"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
208,
176
],
"id": "8e44181f-ebe3-43f0-a54f-9000bbac8c5b",
"name": "HTTP Request"
},
{
"parameters": {
"functionCode": "// ---------- helpers ----------\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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n\nconst parseNum = (v) => {\n if (v === undefined || v === null || v === '') return 0;\n if (typeof v === 'number') return v;\n\n // Spaties weg\n let s = String(v).trim();\n\n // Komma naar punt\n s = s.replace(',', '.');\n\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\n// ---- bron ----\nconst input = $json || {};\nconst cons = get(input, 'Response.Detail.Data.Manifest.Depot.Account.Consignment') || {};\nconst depotRoot = get(input, 'Response.Detail.Data') || {};\n\n// Basisvelden\nconst trackingId = nz(cons.TrackingID, nz(cons.Reference, ''));\nconst reference = nz(cons.Reference, trackingId);\nconst weight = parseNum(nz(cons.Weight, 0));\nconst dueDate = nz(cons.DueDate, new Date().toISOString().split('T')[0]);\nconst dueTime = nz(cons.DueTime, '');\nconst driverInfo = nz(cons.ManifestNote, '');\n\n// Adressen\nconst addresses = ensureArray(cons.Address);\nconst deliveryAddress = addresses.find((a) => /delivery/i.test(a?.Type || '')) || {};\nconst collectionAddress = addresses.find((a) => /collection/i.test(a?.Type || '')) || {};\n\n// Paying depot\nconst 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// --- Goodsline ---\nlet goods = [];\n\nconst palletDetail = get(cons, 'PalletDetail');\nif (palletDetail) {\n // gebruik PalletDetail\n goods.push({\n seq: 1,\n unitamount: 1,\n unit_id: nz(palletDetail.PalletType, 'HP'),\n weight: parseNum(palletDetail.Weight || weight),\n length: parseNum(palletDetail.Length || 120),\n width: parseNum(palletDetail.Width || 80),\n height: parseNum(palletDetail.Height || 150),\n });\n} else {\n // BillUnits\n const billUnits = ensureArray(cons.BillUnit);\n let seq = 1;\n for (const bu of billUnits) {\n const amount = parseNum(bu.Amount || 0);\n if (amount > 0) {\n goods.push({\n seq: seq++,\n unitamount: amount,\n unit_id: nz(bu.Type, 'HP'),\n weight: weight > 0 ? weight / amount : 0,\n length: 120,\n width: 80,\n height: 150,\n });\n }\n }\n // fallback\n if (goods.length === 0) {\n goods.push({\n seq: 1,\n unitamount: 1,\n unit_id: 'HP',\n weight: weight,\n length: 120,\n width: 80,\n height: 150,\n });\n }\n}\n\n// BookInRequest -> bool2\nconst bookInRaw = String(nz(firstOf(cons, ['BookInRequest', 'Delivery.BookInRequest'], ''))).toLowerCase();\nconst bool2 = ['yes', 'true', '1', 'y', 'ja'].includes(bookInRaw);\n\n// taillift -> bool1\nconst taillift = String(nz(firstOf(cons, ['TailLift', 'Delivery.TailLift'], ''))).toLowerCase();\nconst bool1 = ['yes', 'true', '1', 'y', 'ja'].includes(taillift);\n\n// Prijs\nconst csvPrice = parseNum(firstOf(input, ['bedrag'], 0));\n\n// --------- XML ----------\nconst xml = [];\nxml.push('<?xml version=\"1.0\" encoding=\"UTF-8\"?>');\nxml.push('<transportbookings>');\nxml.push(' <transportbooking>');\nxml.push(' <autoacceptmethod>0</autoacceptmethod>');\nxml.push(` <edireference>${xmlEsc(trackingId)}</edireference>`);\nxml.push(' <customer_id matchmode=\"1\">55107</customer_id>');\nxml.push(' <department_id>1</department_id>');\nxml.push(' <currency_id>1</currency_id>');\nxml.push(' <shipments>');\nxml.push(' <shipment>');\nxml.push(` <edireference>${xmlEsc(trackingId)}</edireference>`);\nxml.push(` <reference>${xmlEsc(trackingId)}</reference>`);\nxml.push(' <plangroup_id>33</plangroup_id>');\nxml.push(' <debtor_id>55107</debtor_id>');\nxml.push(' <version>1</version>');\nxml.push(` <fixedprice>${xmlEsc(csvPrice)}</fixedprice>`);\n\n// sender\nxml.push(' <sender>');\nxml.push(` <name>${xmlEsc(nz(collectionAddress.CompanyName, ''))}</name>`);\nxml.push(` <address1>${xmlEsc(nz(collectionAddress.Addr1, ''))}</address1>`);\nxml.push(` <address2>${xmlEsc(nz(collectionAddress.Addr2, ''))}</address2>`);\nxml.push(` <zipcode>${xmlEsc(nz(collectionAddress.PostCode, ''))}</zipcode>`);\nxml.push(` <city_id matchmode=\"4\">${xmlEsc(nz(collectionAddress.Town, ''))}</city_id>`);\nxml.push(` <country_id matchmode=\"2\">${xmlEsc(cc(collectionAddress.Country || 'NL'))}</country_id>`);\nxml.push(` <contact>${xmlEsc(nz(collectionAddress.ContactName, ''))}</contact>`);\nxml.push(` <phone>${xmlEsc(nz(collectionAddress.Telephone, ''))}</phone>`);\nxml.push(' </sender>');\n\n// receiver\nxml.push(' <receiver>');\nxml.push(` <name>${xmlEsc(nz(deliveryAddress.CompanyName, ''))}</name>`);\nxml.push(` <address1>${xmlEsc(nz(deliveryAddress.Addr1, ''))}</address1>`);\nxml.push(` <address2>${xmlEsc(nz(deliveryAddress.Addr2, ''))}</address2>`);\nxml.push(` <zipcode>${xmlEsc(nz(deliveryAddress.PostCode, ''))}</zipcode>`);\nxml.push(` <city_id matchmode=\"4\">${xmlEsc(nz(deliveryAddress.Town, ''))}</city_id>`);\nxml.push(` <country_id matchmode=\"2\">${xmlEsc(cc(deliveryAddress.Country || 'NL'))}</country_id>`);\nxml.push(` <contact>${xmlEsc(nz(deliveryAddress.ContactName, ''))}</contact>`);\nxml.push(` <phone>${xmlEsc(nz(deliveryAddress.Telephone, ''))}</phone>`);\nxml.push(' </receiver>');\n\nconst todayStr = new Date().toISOString().split('T')[0];\n// pickupaddress\nxml.push(' <pickupaddress>');\nxml.push(` <date>${xmlEsc(todayStr)}</date>`);\nxml.push(` <datetill>${xmlEsc(todayStr)}</datetill>`);\nxml.push(' <name>Palletways UK</name>');\nxml.push(' <address1>Bijsterhuizen 1103 A</address1>');\nxml.push(' <zipcode>6546 AS</zipcode>');\nxml.push(' <city_id matchmode=\"4\">Nijmegen</city_id>');\nxml.push(' <country_id matchmode=\"2\">NL</country_id>');\nxml.push(' </pickupaddress>');\n\n// deliveryaddress\nxml.push(' <deliveryaddress>');\nxml.push(' <address_id matchmode=\"11\" overwrite=\"false\"></address_id>');\nxml.push(` <date>${xmlEsc(dueDate)}</date>`);\nxml.push(` <datetill>${xmlEsc(dueDate)}</datetill>`);\nxml.push(` <timetill>${xmlEsc(dueTime)}</timetill>`);\nxml.push(` <name>${xmlEsc(nz(deliveryAddress.CompanyName, ''))}</name>`);\nxml.push(` <address1>${xmlEsc(nz(deliveryAddress.Addr1, ''))}</address1>`);\nxml.push(` <address2>${xmlEsc(nz(deliveryAddress.Addr2, ''))}</address2>`);\nxml.push(` <zipcode>${xmlEsc(nz(deliveryAddress.PostCode, ''))}</zipcode>`);\nxml.push(` <city_id matchmode=\"4\">${xmlEsc(nz(deliveryAddress.Town, ''))}</city_id>`);\nxml.push(` <country_id matchmode=\"2\">${xmlEsc(cc(deliveryAddress.Country || 'NL'))}</country_id>`);\nxml.push(` <contact>${xmlEsc(nz(deliveryAddress.ContactName, ''))}</contact>`);\nxml.push(` <phone>${xmlEsc(nz(deliveryAddress.Telephone, ''))}</phone>`);\nxml.push(` <driverinfo>${xmlEsc(driverInfo)}</driverinfo>`);\nxml.push(' </deliveryaddress>');\n\n// references\nxml.push(' <references>');\nxml.push(\n ` <reference><referencekind_id>15</referencekind_id><description>${xmlEsc(\n trackingId\n )}</description></reference>`\n);\nxml.push(\n ` <reference><referencekind_id>16</referencekind_id><description>${xmlEsc(\n reference\n )}</description></reference>`\n);\nxml.push(' </references>');\n\n// parties\nxml.push(' <parties>');\nxml.push(' <party>');\nxml.push(` <partytype_id>5</partytype_id>`);\nxml.push(` <name>${xmlEsc(payingDepot.name)}</name>`);\nxml.push(` <address1>${xmlEsc(payingDepot.address1)}</address1>`);\nxml.push(` <address2>${xmlEsc(payingDepot.address2)}</address2>`);\nxml.push(` <zipcode>${xmlEsc(payingDepot.zipcode)}</zipcode>`);\nxml.push(` <city_id matchmode=\"4\">${xmlEsc(payingDepot.city)}</city_id>`);\nxml.push(` <country_id matchmode=\"2\">${xmlEsc(payingDepot.country)}</country_id>`);\nxml.push(` <phone>${xmlEsc(payingDepot.phone)}</phone>`);\nxml.push(` <email>${xmlEsc(payingDepot.email)}</email>`);\nxml.push(' </party>');\nxml.push(' </parties>');\n\n// cargo\nxml.push(' <cargo>');\nxml.push(' <unitamount>1</unitamount>');\nxml.push(' <unit_id>591</unit_id>');\nxml.push(' <product_id>123</product_id>');\nxml.push(' <productdescription>General Cargo</productdescription>');\nxml.push(` <weight>${xmlEsc(weight)}</weight>`);\nxml.push(` <bool1>${bool1 ? 'true' : 'false'}</bool1>`);\nxml.push(` <bool2>${bool2 ? 'true' : 'false'}</bool2>`);\nxml.push(' <goodslines>');\nfor (const g of goods) {\n xml.push(' <goodsline>');\n xml.push(` <sequence>${xmlEsc(g.seq)}</sequence>`);\n xml.push(` <unitamount>${xmlEsc(g.unitamount)}</unitamount>`);\n xml.push(` <unit_id>${xmlEsc(g.unit_id)}</unit_id>`);\n xml.push(` <weight>${xmlEsc(g.weight)}</weight>`);\n xml.push(` <length>${xmlEsc(g.length)}</length>`);\n xml.push(` <width>${xmlEsc(g.width)}</width>`);\n xml.push(` <height>${xmlEsc(g.height)}</height>`);\n xml.push(' </goodsline>');\n}\nxml.push(' </goodslines>');\nxml.push(' </cargo>');\n\nxml.push(' </shipment>');\nxml.push(' </shipments>');\nxml.push(' </transportbooking>');\nxml.push('</transportbookings>');\n\nconst xmlStr = xml.join('\\n');\nconst fileName = trackingId ? `${trackingId}.xml` : `booking_${Date.now()}.xml`;\n\nreturn [\n {\n json: {\n filename: fileName,\n xml: xmlStr,\n },\n },\n];\n"
},
"id": "0bdffb34-d13d-459e-b953-10a558a77dcd",
"name": "Transform to Transpas Format v",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
2144,
-512
]
},
{
"parameters": {
"content": "## Outbound manifest wordt gebruikt voor deze flow",
"width": 352
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
80,
-208
],
"id": "7e6030ff-95b0-4ec7-a087-5ba496ce5876",
"name": "Sticky Note"
}
],
"pinData": {},
"connections": {
"Schedule Trigger": {
"main": [
[
{
"node": "Download CSV",
"type": "main",
"index": 0
},
{
"node": "Last workday1",
"type": "main",
"index": 0
}
]
]
},
"Download CSV": {
"main": [
[
{
"node": "Import CSV",
"type": "main",
"index": 0
}
]
]
},
"Import CSV": {
"main": [
[
{
"node": "Upsert row(s)",
"type": "main",
"index": 0
}
]
]
},
"Last workday1": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[
{
"node": "Delete row(s)",
"type": "main",
"index": 0
}
],
[
{
"node": "Get Consigment",
"type": "main",
"index": 0
},
{
"node": "Get row(s)",
"type": "main",
"index": 0
}
]
]
},
"Get Consigment": {
"main": [
[
{
"node": "Parse XML Response",
"type": "main",
"index": 0
}
]
]
},
"Get Paying Depot Address": {
"main": [
[
{
"node": "Parse Paying Depot XML",
"type": "main",
"index": 0
}
]
]
},
"Parse XML Response": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
},
{
"node": "Get Paying Depot Address",
"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": [
[
{
"node": "Wait",
"type": "main",
"index": 0
}
]
]
},
"Wait": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Get row(s)": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 2
}
]
]
},
"HTTP Request": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate",
"timezone": "Europe/Amsterdam",
"callerPolicy": "workflowsFromSameOwner",
"availableInMCP": false
},
"versionId": "d069831f-6a06-4f66-aecc-ae1d79985359",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "bef8d409866a58c0777dfe7cca1b9c2400fd051c056d361501393ab423006b5f"
},
"nodeGroups": [],
"id": "qDrWrANHJWpTQm6P",
"tags": [
{
"updatedAt": "2025-11-11T04:59:39.416Z",
"createdAt": "2025-11-11T04:59:39.416Z",
"id": "ClOZrAjJKoGLFvaO",
"name": "Palletways"
},
{
"updatedAt": "2025-11-09T14:44:19.471Z",
"createdAt": "2025-11-09T14:44:19.471Z",
"id": "DHSMO2U01lFZRZ27",
"name": "Productie"
}
]
}