{ "name": "Lommerse email", "nodes": [ { "parameters": { "operation": "xml", "options": {} }, "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ 592, -192 ], "id": "32cc76af-2abc-43b6-8e06-ca4e8ab1cc60", "name": "Extract from File" }, { "parameters": { "assignments": { "assignments": [ { "id": "d56701fc-95ba-46f3-8e1f-5e5fcc23a97f", "name": "LoadingDate", "value": "={{ $json.LoadingDate }}", "type": "string" }, { "id": "8fb36d6e-f61f-4cfd-8f22-88e303d1a606", "name": "LoadingPlace", "value": "={{ $json.LoadingPlace }}", "type": "string" }, { "id": "33ad14dc-de29-442d-8201-4200e9b56da0", "name": "Cargonr", "value": "={{ $json.Cargonr }}", "type": "number" }, { "id": "83d62b1a-eae8-4b5d-a7df-836509287d34", "name": "LoadingTripID", "value": "={{ $json.LoadingTripID }}", "type": "number" }, { "id": "897ae901-02d7-4b9c-a180-5f8e93dd67fd", "name": "LoadingTruck", "value": "={{ $json.LoadingTruck }}", "type": "string" }, { "id": "112ae60f-081d-4d65-83f6-4bf93d30600b", "name": "UnloadingPlace", "value": "={{ $json.UnloadingPlace }}", "type": "string" }, { "id": "1bd8e2ca-3c95-4a8e-8592-54f99cbfbbf0", "name": "Cargo_UnitAmount", "value": "={{ $json.Cargo_UnitAmount }}", "type": "number" }, { "id": "ec312e85-67a9-4dbe-9fb3-6173ae2da9a6", "name": "Cargo_UnitDescription", "value": "={{ $json.Cargo_UnitDescription }}", "type": "string" }, { "id": "2a245dfb-4d8e-4276-b5b3-64fb50eb0115", "name": "Sequence", "value": "={{ $json.Sequence }}", "type": "number" } ] }, "options": {} }, "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [ 1264, -192 ], "id": "c3e642c3-096f-4e21-8a3b-e49ee9fed022", "name": "Edit Fields" }, { "parameters": { "toRecipients": "zwitserland@dewittransport.nl", "subject": "={{$json.subject}}", "bodyContent": "={{$json.html}}", "additionalFields": { "bodyContentType": "html" } }, "type": "n8n-nodes-base.microsoftOutlook", "typeVersion": 2, "position": [ 1712, -192 ], "id": "c505a2a1-545b-401c-ad2f-7c9d0b6546c8", "name": "Send a message", "webhookId": "d45b20c1-ceb8-46c5-ac1e-9e94e0d77396", "credentials": { "microsoftOutlookOAuth2Api": { "id": "PGANiL6y4sGEEvao", "name": "Microsoft Outlook Palletways" } } }, { "parameters": { "jsCode": "// n8n Code node (Run Once for All Items)\n// Output: 1 item met { subject, html }\n\nfunction esc(v) {\n return String(v ?? '')\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\nfunction formatDateShort(dstr) {\n const s = String(dstr ?? '').substring(0, 10); // YYYY-MM-DD\n const parts = s.split('-');\n if (parts.length === 3) return `${parts[2]}-${parts[1]}-${parts[0]}`; // DD-MM-YYYY\n return s;\n}\n\nconst rows = $input.all().map(i => i.json);\n\nif (!rows.length) {\n return [{\n json: {\n subject: 'Laadoverzicht',\n html: `\n \n \n Geen gegevens.\n \n `\n }\n }];\n}\n\n// --- Group by LoadingTripID ---\nconst tripMap = new Map();\n\nfor (const r of rows) {\n const tripId = r.LoadingTripID ?? 'Onbekend';\n const tripKey = String(tripId);\n\n if (!tripMap.has(tripKey)) {\n tripMap.set(tripKey, {\n tripId,\n truck: r.LoadingTruck ?? '',\n loadingDate: r.LoadingDate ?? '',\n lines: []\n });\n }\n\n const trip = tripMap.get(tripKey);\n\n trip.lines.push({\n sequence: Number(r.Sequence ?? 999999),\n cargonr: r.Cargonr ?? '',\n loadingPlace: String(r.LoadingPlace ?? '').trim(),\n unloadingPlace: String(r.UnloadingPlace ?? '').trim(),\n unitAmount: Number(r.Cargo_UnitAmount ?? 0) || 0,\n unitDescription: String(r.Cargo_UnitDescription ?? '').trim() || 'Onbekend'\n });\n}\n\n// sort trips numeriek als het kan, anders op string\nconst trips = Array.from(tripMap.values()).sort((a, b) => {\n const an = Number(a.tripId);\n const bn = Number(b.tripId);\n if (!Number.isNaN(an) && !Number.isNaN(bn)) return an - bn;\n return String(a.tripId).localeCompare(String(b.tripId));\n});\n\n// sort regels binnen trip op Sequence\nfor (const t of trips) {\n t.lines.sort((a, b) => {\n if (a.sequence !== b.sequence) return a.sequence - b.sequence;\n\n const ac = Number(a.cargonr);\n const bc = Number(b.cargonr);\n if (!Number.isNaN(ac) && !Number.isNaN(bc)) return ac - bc;\n\n return String(a.cargonr).localeCompare(String(b.cargonr));\n });\n}\n\nconst overallDate = formatDateShort(rows[0].LoadingDate);\n\n// --- Build HTML ---\nlet html = '';\n\nhtml += `\n\n \n
\n`;\n\nhtml += `\n
\n
LoadingDate
\n
${esc(overallDate)}
\n
\n`;\n\nfor (const t of trips) {\n html += `\n
\n
\n LoadingTripID   |   LoadingTruck\n
\n
\n ${esc(t.tripId)}   \n ${esc(t.truck)}\n
\n
\n `;\n\n html += `\n \n \n \n \n \n \n \n \n \n \n \n \n `;\n\n for (const line of t.lines) {\n const cargoText = `${esc(line.unitAmount)} ${esc(line.unitDescription)}${line.unloadingPlace ? ` (${esc(line.unloadingPlace)})` : ''}`;\n\n html += `\n \n \n \n \n `;\n }\n\n html += `\n \n
LoadingPlaceAmount / DeliveryPlace
\n ${esc(line.loadingPlace)}\n \n ${cargoText}\n
\n `;\n}\n\nhtml += `\n
\n Dit overzicht is automatisch gegenereerd.\n
\n
\n \n\n`;\n\nconst subject = `Laadoverzicht ${overallDate}`;\n\nreturn [{\n json: { subject, html }\n}];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1488, -192 ], "id": "0daa4276-1ac6-412d-bdec-467f5cc5209c", "name": "Code in JavaScript" }, { "parameters": { "protocol": "sftp", "operation": "list", "path": "\\dewit\\lommerse", "options": {} }, "id": "1583d688-1128-4f0c-a2c4-2d4865a2f6fe", "name": "List XML Files", "type": "n8n-nodes-base.ftp", "typeVersion": 1, "position": [ 144, -192 ], "credentials": { "sftp": { "id": "nuxGYvlfDSPCAONf", "name": "n8n 2" } } }, { "parameters": { "protocol": "sftp", "path": "={{ $json.path }}", "options": {} }, "id": "fca75cef-d197-4341-90d5-68e28b2790e4", "name": "Download XML File", "type": "n8n-nodes-base.ftp", "typeVersion": 1, "position": [ 368, -192 ], "credentials": { "sftp": { "id": "nuxGYvlfDSPCAONf", "name": "n8n 2" } } }, { "parameters": { "options": {} }, "type": "n8n-nodes-base.xml", "typeVersion": 1, "position": [ 816, -192 ], "id": "f6cbc7bc-152c-4597-bb03-1cf16357f4d5", "name": "XML" }, { "parameters": { "jsCode": "// Split $json.shipments.shipments -> losse items\n// Werkt ook als er meerdere input items binnenkomen\n\nconst out = [];\n\nfor (const row of $input.all()) {\n const list = row.json?.shipments?.shipments;\n\n if (!list) continue;\n\n const arr = Array.isArray(list) ? list : [list];\n\n for (const s of arr) {\n out.push({ json: s });\n }\n}\n\nout.sort((a, b) => {\n const ta = Number(a.json.LoadingTripID ?? 0);\n const tb = Number(b.json.LoadingTripID ?? 0);\n if (ta !== tb) return ta - tb;\n\n const sa = Number(a.json.Sequence ?? 0);\n const sb = Number(b.json.Sequence ?? 0);\n return sa - sb;\n});\n\nreturn out;\n\n" }, "id": "219af45b-77f2-407c-963f-7eb1409d5754", "name": "Splits items", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1040, -192 ] }, { "parameters": { "protocol": "sftp", "operation": "delete", "path": "={{ $('List XML Files').item.json.path }}", "options": {} }, "type": "n8n-nodes-base.ftp", "typeVersion": 1, "position": [ 832, 0 ], "id": "9b8604f1-b6e8-4079-ac50-38d36cd07ce9", "name": "FTP", "credentials": { "sftp": { "id": "nuxGYvlfDSPCAONf", "name": "n8n 2" } } }, { "parameters": { "rule": { "interval": [ { "field": "minutes" } ] } }, "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1.3, "position": [ -48, -192 ], "id": "f3e50052-287a-460c-854e-e3b47333579b", "name": "Schedule Trigger" }, { "parameters": { "jsCode": "// n8n Code node (Run Once for All Items)\n// Output: 1 item met { subject, html }\n\nfunction esc(v) {\n return String(v ?? '')\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\nfunction formatDateShort(dstr) {\n const s = String(dstr ?? '').substring(0, 10); // YYYY-MM-DD\n const parts = s.split('-');\n if (parts.length === 3) return `${parts[2]}-${parts[1]}-${parts[0]}`; // DD-MM-YYYY\n return s;\n}\n\nconst rows = $input.all().map(i => i.json);\n\nif (!rows.length) {\n return [{\n json: {\n subject: 'Laadoverzicht',\n html: `\n \n \n Geen gegevens.\n \n `\n }\n }];\n}\n\n// --- Group by LoadingTripID (in insertion order) ---\nconst tripMap = new Map();\n\nfor (const r of rows) {\n const tripId = r.LoadingTripID ?? 'Onbekend';\n const tripKey = String(tripId);\n\n if (!tripMap.has(tripKey)) {\n tripMap.set(tripKey, {\n tripId,\n truck: r.LoadingTruck ?? '',\n loadingDate: r.LoadingDate ?? '',\n cargos: new Map(),\n order: []\n });\n }\n\n const trip = tripMap.get(tripKey);\n\n const cargonr = r.Cargonr ?? 'Onbekend';\n let cargoKey = String(cargonr);\n\n let cg = trip.cargos.get(cargoKey);\n if (cg && (cg.place ?? '') !== (r.LoadingPlace ?? '')) {\n cargoKey = `${cargoKey}||${r.LoadingPlace ?? ''}`;\n cg = trip.cargos.get(cargoKey);\n }\n\n if (!cg) {\n cg = {\n cargonr,\n place: r.LoadingPlace ?? '',\n date: r.LoadingDate ?? trip.loadingDate ?? '',\n units: new Map()\n };\n trip.cargos.set(cargoKey, cg);\n trip.order.push(cargoKey);\n }\n\n const desc = String(r.Cargo_UnitDescription ?? '').trim() || 'Onbekend';\n const amt = Number(r.Cargo_UnitAmount ?? 0) || 0;\n cg.units.set(desc, (cg.units.get(desc) || 0) + amt);\n}\n\n// sort trips numeriek als het kan, anders op string\nconst trips = Array.from(tripMap.values()).sort((a, b) => {\n const an = Number(a.tripId), bn = Number(b.tripId);\n if (!Number.isNaN(an) && !Number.isNaN(bn)) return an - bn;\n return String(a.tripId).localeCompare(String(b.tripId));\n});\n\nconst overallDate = formatDateShort(rows[0].LoadingDate);\n\n// --- Build HTML (base font-size via CSS var; rest via em) ---\nlet html = '';\nhtml += `\n\n \n`;\n\nhtml += `\n
\n
LoadingDate
\n
${esc(overallDate)}
\n
\n`;\n\nfor (const t of trips) {\n const tripDate = formatDateShort(t.loadingDate);\n\n html += `\n
\n
\n LoadingTripID   |   LoadingTruck\n
\n\n
\n ${esc(t.tripId)}   \n ${esc(t.truck)}\n
\n\n ${tripDate ? `
LoadingDate: ${esc(tripDate)}
` : ''}\n
\n `;\n\n html += `\n \n \n \n \n \n \n \n \n `;\n\n for (const key of t.order) {\n const cg = t.cargos.get(key);\n\n const placeCell = `
${esc(cg.place)}
`;\n\n const unitLines = Array.from(cg.units.entries())\n .sort((a, b) => String(a[0]).localeCompare(String(b[0])))\n .map(([desc, amt]) => `${esc(amt)} ${esc(desc)}`)\n .join('
');\n\n html += `\n \n \n \n \n `;\n }\n\n html += `\n \n
LoadingPlaceCargo_UnitAmount / Cargo_UnitDescription
${placeCell}${unitLines || ' '}
\n `;\n}\n\nhtml += `\n
\n Dit overzicht is automatisch gegenereerd.\n
\n`;\n\nhtml += `\n \n\n`;\n\nconst subject = `Laadoverzicht ${overallDate}`;\n\nreturn [{\n json: { subject, html }\n}];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1472, -352 ], "id": "ac4ee625-1439-4bc0-aa2f-8c109098cd6f", "name": "Code in JavaScript1" } ], "pinData": {}, "connections": { "Extract from File": { "main": [ [ { "node": "XML", "type": "main", "index": 0 }, { "node": "FTP", "type": "main", "index": 0 } ] ] }, "Edit Fields": { "main": [ [ { "node": "Code in JavaScript", "type": "main", "index": 0 } ] ] }, "Code in JavaScript": { "main": [ [ { "node": "Send a message", "type": "main", "index": 0 } ] ] }, "List XML Files": { "main": [ [ { "node": "Download XML File", "type": "main", "index": 0 } ] ] }, "Download XML File": { "main": [ [ { "node": "Extract from File", "type": "main", "index": 0 } ] ] }, "XML": { "main": [ [ { "node": "Splits items", "type": "main", "index": 0 } ] ] }, "Splits items": { "main": [ [ { "node": "Edit Fields", "type": "main", "index": 0 } ] ] }, "Schedule Trigger": { "main": [ [ { "node": "List XML Files", "type": "main", "index": 0 } ] ] } }, "active": true, "settings": { "executionOrder": "v1", "binaryMode": "separate", "availableInMCP": false }, "versionId": "7bb65e02-3a9a-45c4-824a-bd5805f6d2d3", "meta": { "templateCredsSetupCompleted": true, "instanceId": "bef8d409866a58c0777dfe7cca1b9c2400fd051c056d361501393ab423006b5f" }, "nodeGroups": [], "id": "7WqVSI1DxMlMrIw-ArAaM", "tags": [] }