643 lines
39 KiB
JSON
643 lines
39 KiB
JSON
{
|
||
"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 item’s 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️⃣ De‑duplicate\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, '&')\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 // 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, '&')\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\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"
|
||
}
|
||
]
|
||
} |