Stuurt mail naar afdeling Zwitserland met zendingen die geladen moeten worden

This commit is contained in:
2026-07-03 11:14:25 +02:00
parent 6c529e7120
commit 98e52fb90f
+361
View File
@@ -0,0 +1,361 @@
{
"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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\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 <html>\n <body style=\"margin:0;font-family:Arial,sans-serif;font-size:10px;color:#111;line-height:1.3;\">\n Geen gegevens.\n </body>\n </html>`\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<html>\n <body style=\"margin:0;font-family:Arial,sans-serif;font-size:10px;color:#111;line-height:1.3;\">\n <div style=\"max-width:760px;\">\n`;\n\nhtml += `\n <div style=\"margin-bottom:12px;\">\n <div style=\"font-size:10px;color:#333;\">LoadingDate</div>\n <div style=\"font-size:14px;font-weight:700;\">${esc(overallDate)}</div>\n </div>\n`;\n\nfor (const t of trips) {\n html += `\n <div style=\"margin:16px 0 6px 0;\">\n <div style=\"font-size:10px;color:#333;\">\n LoadingTripID&nbsp;&nbsp;&nbsp;<span style=\"color:#999;\">|</span>&nbsp;&nbsp;&nbsp;LoadingTruck\n </div>\n <div style=\"font-size:10px;font-weight:700;color:#111;margin-top:1px;\">\n ${esc(t.tripId)} &nbsp;&nbsp;\n <span style=\"font-weight:400;color:#444;\">${esc(t.truck)}</span>\n </div>\n </div>\n `;\n\n html += `\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"border-collapse:collapse;width:760px;table-layout:fixed;font-size:10px;margin-bottom:14px;\">\n <colgroup>\n <col style=\"width:260px;\">\n <col style=\"width:500px;\">\n </colgroup>\n <thead>\n <tr>\n <th align=\"left\" style=\"border:1px solid #ddd;padding:8px;background:#f7f7f7;vertical-align:top;\">LoadingPlace</th>\n <th align=\"left\" style=\"border:1px solid #ddd;padding:8px;background:#f7f7f7;vertical-align:top;\">Amount / DeliveryPlace</th>\n </tr>\n </thead>\n <tbody>\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 <tr>\n <td valign=\"top\" style=\"border:1px solid #ddd;padding:8px;font-weight:700;word-wrap:break-word;overflow-wrap:anywhere;\">\n ${esc(line.loadingPlace)}\n </td>\n <td valign=\"top\" style=\"border:1px solid #ddd;padding:8px;word-wrap:break-word;overflow-wrap:anywhere;\">\n ${cargoText}\n </td>\n </tr>\n `;\n }\n\n html += `\n </tbody>\n </table>\n `;\n}\n\nhtml += `\n <div style=\"margin-top:14px;font-size:10px;color:#777;\">\n Dit overzicht is automatisch gegenereerd.\n </div>\n </div>\n </body>\n</html>\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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\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 <html>\n <body style=\"margin:0;font-family:Arial,sans-serif;--fs:9px;font-size:var(--fs);color:#111;line-height:1.35;\">\n Geen gegevens.\n </body>\n </html>`\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<html>\n <body style=\"margin:0;font-family:Arial,sans-serif;--fs:8px;font-size:var(--fs);color:#111;line-height:1.35;\">\n`;\n\nhtml += `\n <div style=\"margin-bottom:1.2em;\">\n <div style=\"font-size:1em;color:#333;\">LoadingDate</div>\n <div style=\"font-size:1.45em;font-weight:700;\">${esc(overallDate)}</div>\n </div>\n`;\n\nfor (const t of trips) {\n const tripDate = formatDateShort(t.loadingDate);\n\n html += `\n <div style=\"margin:1.6em 0 0.7em;\">\n <div style=\"font-size:1em;color:#333;\">\n LoadingTripID&nbsp;&nbsp;&nbsp;<span style=\"color:#999;\">|</span>&nbsp;&nbsp;&nbsp;LoadingTruck\n </div>\n\n <div style=\"font-size:1.25em;font-weight:700;\">\n ${esc(t.tripId)} &nbsp;&nbsp;\n <span style=\"font-weight:400;color:#444;\">${esc(t.truck)}</span>\n </div>\n\n ${tripDate ? `<div style=\"font-size:0.95em;color:#666;margin-top:0.2em;\">LoadingDate: ${esc(tripDate)}</div>` : ''}\n </div>\n `;\n\n html += `\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"border-collapse:collapse;width:100%;max-width:900px;font-size:1em;\">\n <thead>\n <tr>\n <th align=\"left\" style=\"border:1px solid #ddd;padding:0.65em;background:#f7f7f7;font-size:1em;\">LoadingPlace</th>\n <th align=\"left\" style=\"border:1px solid #ddd;padding:0.65em;background:#f7f7f7;font-size:1em;\">Cargo_UnitAmount / Cargo_UnitDescription</th>\n </tr>\n </thead>\n <tbody>\n `;\n\n for (const key of t.order) {\n const cg = t.cargos.get(key);\n\n const placeCell = `<div style=\"font-weight:700;\">${esc(cg.place)}</div>`;\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('<br>');\n\n html += `\n <tr>\n <td valign=\"top\" style=\"border:1px solid #ddd;padding:0.65em;\">${placeCell}</td>\n <td valign=\"top\" style=\"border:1px solid #ddd;padding:0.65em;\">${unitLines || '&nbsp;'}</td>\n </tr>\n `;\n }\n\n html += `\n </tbody>\n </table>\n `;\n}\n\nhtml += `\n <div style=\"margin-top:1.4em;font-size:0.95em;color:#777;\">\n Dit overzicht is automatisch gegenereerd.\n </div>\n`;\n\nhtml += `\n </body>\n</html>\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": []
}