diff --git a/Lommerse email.json b/Lommerse email.json new file mode 100644 index 0000000..279d17c --- /dev/null +++ b/Lommerse email.json @@ -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, '&')\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": [] +} \ No newline at end of file