994 lines
72 KiB
JSON
994 lines
72 KiB
JSON
{
|
|
"name": "Palletways Notes import --> TLN",
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"rule": {
|
|
"interval": [
|
|
{
|
|
"field": "minutes"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"id": "40267cf4-e7fe-4d0b-b4f1-d476fd66f603",
|
|
"name": "Schedule Trigger",
|
|
"type": "n8n-nodes-base.scheduleTrigger",
|
|
"typeVersion": 1.1,
|
|
"position": [
|
|
-864,
|
|
304
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"options": {
|
|
"explicitArray": false,
|
|
"mergeAttrs": true
|
|
}
|
|
},
|
|
"id": "86168699-6d8f-479f-bfd0-9532e3ddae53",
|
|
"name": "Parse XML Response",
|
|
"type": "n8n-nodes-base.xml",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
32,
|
|
400
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// n8n Code node (Run once for all items)\n// Split Response.Detail.Data naar losse items\n// Werkt met:\n// - Data als array\n// - Data als enkel object\n// - lege/missende Data\n\nconst input = $input.first().json;\n\n// Pak root netjes\nconst root = Array.isArray(input) ? (input[0] ?? {}) : input;\nconst response = root.Response ?? root;\nconst status = response.Status ?? {};\n\nlet data = response?.Detail?.Data;\n\n// Geen data\nif (!data) {\n return [];\n}\n\n// Als er maar 1 record is, maak er alsnog een array van\nif (!Array.isArray(data)) {\n data = [data];\n}\n\n// Maak 1 n8n item per record\nreturn data.map((row, index) => ({\n json: {\n ...row,\n _status: status,\n _index: index,\n },\n}));"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
512,
|
|
384
|
|
],
|
|
"id": "564a970e-65b2-45a3-b334-c795e80f3553",
|
|
"name": "Split Manifests"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// Fixed timestamp parser - handles Redis data under \"propertyName\"\n\n// Helper function to format timestamps in UK timezone\nfunction formatUKTimestamp(date) {\n const options = {\n timeZone: 'Europe/London',\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hour12: false\n };\n \n const formatter = new Intl.DateTimeFormat('en-CA', options);\n const parts = formatter.formatToParts(date);\n \n const year = parts.find(p => p.type === 'year').value;\n const month = parts.find(p => p.type === 'month').value;\n const day = parts.find(p => p.type === 'day').value;\n const hour = parts.find(p => p.type === 'hour').value;\n const minute = parts.find(p => p.type === 'minute').value;\n const second = parts.find(p => p.type === 'second').value;\n \n return {\n timestamp: `${year}-${month}-${day}T${hour}:${minute}:${second}`,\n date: `${year}-${month}-${day}`,\n time: `${hour}:${minute}:${second}`\n };\n}\n\n// Generate current UK timestamp (this will be saved for the NEXT run)\nconst now = new Date();\nconst currentUK = formatUKTimestamp(now);\n\n// Initialize variables for the PREVIOUS run timestamp\nlet lastRunTimestamp, lastRunDate, lastRunTime;\nlet source = 'unknown';\n\ntry {\n // Get the Redis response\n const input = $input.first()?.json;\n console.log('Full input received:', JSON.stringify(input, null, 2));\n \n // The Redis data is coming in under \"propertyName\" as a JSON string\n let parsedData = null;\n \n if (input?.propertyName) {\n // Parse the JSON string from propertyName\n parsedData = JSON.parse(input.propertyName);\n source = 'input.propertyName';\n console.log('Found and parsed Redis data from propertyName');\n } else if (input?.data) {\n // Fallback: check data property\n if (typeof input.data === 'string') {\n parsedData = JSON.parse(input.data);\n source = 'input.data-parsed';\n } else {\n parsedData = input.data;\n source = 'input.data-object';\n }\n console.log('Found Redis data in input.data');\n } else if (input?.lastRunTimestamp) {\n // Direct object\n parsedData = input;\n source = 'direct-input';\n console.log('Found Redis data directly in input');\n }\n \n console.log('Parsed Redis data:', JSON.stringify(parsedData, null, 2));\n \n if (!parsedData) {\n throw new Error('No Redis data found');\n }\n \n // Extract the previous timestamp - use exactly as stored in Redis\n if (parsedData.lastRunTimestamp) {\n lastRunTimestamp = parsedData.lastRunTimestamp; // Use exact value from Redis\n lastRunDate = parsedData.lastRunDate; // Use exact value from Redis\n lastRunTime = parsedData.lastRunTime; // Use exact value from Redis\n \n console.log(`✅ Successfully loaded PREVIOUS timestamp from Redis:`);\n console.log(` Timestamp: ${lastRunTimestamp}`);\n console.log(` Date: ${lastRunDate}`);\n console.log(` Time: ${lastRunTime}`);\n source = 'redis-success';\n \n } else {\n throw new Error('No lastRunTimestamp found in Redis data');\n }\n \n} catch (error) {\n // Fallback: Use 24 hours ago as the \"previous\" run time\n console.log(`❌ Error loading from Redis: ${error.message}`);\n console.log('Using 24-hour fallback for first run');\n \n const yesterday = new Date();\n yesterday.setHours(yesterday.getHours() - 24);\n const fallbackUK = formatUKTimestamp(yesterday);\n \n lastRunTimestamp = fallbackUK.timestamp;\n lastRunDate = fallbackUK.date;\n lastRunTime = fallbackUK.time;\n source = 'fallback-24h';\n \n console.log(`📅 Using fallback timestamp:`);\n console.log(` Timestamp: ${lastRunTimestamp}`);\n console.log(` Date: ${lastRunDate}`);\n console.log(` Time: ${lastRunTime}`);\n}\n\n// Log the final results\nconsole.log('\\n🔄 FINAL RESULTS:');\nconsole.log(`📍 PREVIOUS run (to use for queries): ${lastRunTimestamp}`);\nconsole.log(`🕐 CURRENT run (to save for next time): ${currentUK.timestamp}`);\nconsole.log(`📊 Data source: ${source}`);\n\n// Return both timestamps with clear separation\nreturn [{\n json: {\n // PREVIOUS run timestamps (use these for your data queries)\n lastRunTimestamp: lastRunTimestamp,\n lastRunDate: lastRunDate,\n lastRunTime: lastRunTime,\n \n // CURRENT run timestamps (save these to Redis for next run)\n currentTimestamp: currentUK.timestamp,\n currentDate: currentUK.date,\n currentTime: currentUK.time,\n \n // Metadata\n workflowId: $workflow.id,\n executionId: $execution.id,\n dataSource: source,\n processedAt: new Date().toISOString()\n }\n}];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
-416,
|
|
304
|
|
],
|
|
"id": "aa5f5031-5c40-48ee-964d-0376a0960e22",
|
|
"name": "Parse Last Timestamp"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "get",
|
|
"key": "notes_lastrun_timestamp",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.redis",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-640,
|
|
304
|
|
],
|
|
"id": "e04add49-6606-4ff1-8a29-cc48e10fa0f8",
|
|
"name": "Get Last Timestamp from Redis",
|
|
"credentials": {
|
|
"redis": {
|
|
"id": "uPJ4dFmf6tm25qHY",
|
|
"name": "Redis account 2"
|
|
}
|
|
},
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "set",
|
|
"key": "notes_lastrun_timestamp",
|
|
"value": "={{ $json.redisValue }}"
|
|
},
|
|
"type": "n8n-nodes-base.redis",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
32,
|
|
208
|
|
],
|
|
"id": "4e70a238-4473-4f3f-842c-4b89e0ad4708",
|
|
"name": "Save Timestamp to Redis",
|
|
"credentials": {
|
|
"redis": {
|
|
"id": "uPJ4dFmf6tm25qHY",
|
|
"name": "Redis account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// Prepare the timestamp data structure for Redis storage\n// This saves the CURRENT execution time for the NEXT run to use\n\nconst input = $input.first().json;\n\n// Create the complete data structure\nconst timestampData = {\n lastRunTimestamp: input.currentTimestamp, // Current becomes \"last\" for next run\n lastRunDate: input.currentDate,\n lastRunTime: input.currentTime,\n workflowId: input.workflowId,\n executionId: input.executionId,\n savedAt: new Date().toISOString(),\n version: '3.0'\n};\n\nconsole.log('💾 Preparing to save CURRENT execution time for NEXT run:');\nconsole.log(` Timestamp: ${timestampData.lastRunTimestamp}`);\nconsole.log(` Date: ${timestampData.lastRunDate}`);\nconsole.log(` Time: ${timestampData.lastRunTime}`);\nconsole.log(` Workflow ID: ${timestampData.workflowId}`);\nconsole.log(` Execution ID: ${timestampData.executionId}`);\n\nreturn [{\n json: {\n success: true,\n timestampData: timestampData,\n redisValue: JSON.stringify(timestampData),\n summary: {\n whatWeUsedThisRun: {\n lastRunTimestamp: input.lastRunTimestamp,\n source: input.dataSource\n },\n whatWeSaveForNextRun: {\n lastRunTimestamp: timestampData.lastRunTimestamp,\n note: 'This current execution time becomes the lastRunTimestamp for next run'\n }\n }\n }\n}];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
-192,
|
|
208
|
|
],
|
|
"id": "a58be23e-a8b6-4c0f-b170-631277ec16e1",
|
|
"name": "Prepare Timestamp Data"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## Timezone staat ingesteld op London",
|
|
"height": 96,
|
|
"width": 336
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"position": [
|
|
-848,
|
|
144
|
|
],
|
|
"typeVersion": 1,
|
|
"id": "28aac797-03af-4f71-9f29-7a860ce2a66b",
|
|
"name": "Sticky Note"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "=https://api.palletways.com/getnotes/daterange/{{ $json.lastRunDate }}/{{ $json.lastRunTime }}/{{ $json.currentDate }}/{{ $json.currentTime }}/?apikey=SXJaM7PLjZDqfsnXSDP8Y9wDY6crxJySBg705MQEPus%3D",
|
|
"options": {
|
|
"response": {
|
|
"response": {
|
|
"responseFormat": "text"
|
|
}
|
|
},
|
|
"timeout": 30000
|
|
}
|
|
},
|
|
"id": "32fccfa6-3482-4da0-8e9f-4dd9f7ccbadf",
|
|
"name": "Get Palletways Notes",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.1,
|
|
"position": [
|
|
-192,
|
|
400
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 3
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "5e894f03-9d8a-4f32-a43e-e0a5170d23ba",
|
|
"leftValue": "={{ $json.PayingDepot }}",
|
|
"rightValue": "464",
|
|
"operator": {
|
|
"type": "string",
|
|
"operation": "equals"
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.filter",
|
|
"typeVersion": 2.3,
|
|
"position": [
|
|
752,
|
|
384
|
|
],
|
|
"id": "c2cfb535-ae39-4b77-83d6-580ce92d8894",
|
|
"name": "Filter Paying depot"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"rules": {
|
|
"values": [
|
|
{
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 3
|
|
},
|
|
"conditions": [
|
|
{
|
|
"leftValue": "={{ $json.NoteCodeID }}",
|
|
"rightValue": "67999",
|
|
"operator": {
|
|
"type": "string",
|
|
"operation": "equals"
|
|
},
|
|
"id": "9c23662b-e4d3-4531-a35b-ae6e6a4f0a99"
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"renameOutput": true,
|
|
"outputKey": "CMR"
|
|
},
|
|
{
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 3
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "575c640a-12ca-4d9d-9398-70a5ce8e437b",
|
|
"leftValue": "={{ $json.NoteCodeID }}",
|
|
"rightValue": "32",
|
|
"operator": {
|
|
"type": "string",
|
|
"operation": "equals"
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"renameOutput": true,
|
|
"outputKey": "Book-in"
|
|
},
|
|
{
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 3
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "bdf4413b-2759-4909-b1cf-90e58fc8b42d",
|
|
"leftValue": "={{ $json.NoteCodeID }}",
|
|
"rightValue": "219999",
|
|
"operator": {
|
|
"type": "string",
|
|
"operation": "equals"
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"renameOutput": true,
|
|
"outputKey": "Afgehaald"
|
|
},
|
|
{
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 3
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "c2c1f674-77bb-44a7-b5d1-8c3570b45abe",
|
|
"leftValue": "={{ $json.NoteCodeID }}",
|
|
"rightValue": "36",
|
|
"operator": {
|
|
"type": "string",
|
|
"operation": "equals"
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"renameOutput": true,
|
|
"outputKey": "Afgeleverd"
|
|
},
|
|
{
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 3
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "bdd8c8ed-546d-4ccc-9e38-a4ebe7f4e235",
|
|
"leftValue": "={{ $json.NoteCodeID }}",
|
|
"rightValue": "218999",
|
|
"operator": {
|
|
"type": "string",
|
|
"operation": "equals"
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"renameOutput": true,
|
|
"outputKey": "ETA"
|
|
},
|
|
{
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 3
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "563f1487-1d9d-45d6-91e0-6bbe03f9761e",
|
|
"leftValue": "={{ $json.NoteCodeID }}",
|
|
"rightValue": "49001",
|
|
"operator": {
|
|
"type": "string",
|
|
"operation": "equals",
|
|
"name": "filter.operator.equals"
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"renameOutput": true,
|
|
"outputKey": "Bij Afleverdepot"
|
|
},
|
|
{
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 3
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "c8b65b34-6be9-4504-9f31-043dbbaae464",
|
|
"leftValue": "={{ $json.NoteCodeID }}",
|
|
"rightValue": "348999",
|
|
"operator": {
|
|
"type": "string",
|
|
"operation": "equals",
|
|
"name": "filter.operator.equals"
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"renameOutput": true,
|
|
"outputKey": "geladen depot"
|
|
},
|
|
{
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 3
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "480b4d6f-0d6f-458a-9152-6bae2d5e8757",
|
|
"leftValue": "={{ $json.NoteCodeID }}",
|
|
"rightValue": "48001",
|
|
"operator": {
|
|
"type": "string",
|
|
"operation": "equals",
|
|
"name": "filter.operator.equals"
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"renameOutput": true,
|
|
"outputKey": "Out for delivery"
|
|
}
|
|
]
|
|
},
|
|
"options": {
|
|
"fallbackOutput": "extra"
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.switch",
|
|
"typeVersion": 3.4,
|
|
"position": [
|
|
976,
|
|
320
|
|
],
|
|
"id": "d689f805-a258-4a1a-9776-6a4762a563f2",
|
|
"name": "Switch"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "=https://portal.palletways.com/show_pod.php?fullsize=true&file={{ $json.NoteText }}",
|
|
"options": {
|
|
"response": {
|
|
"response": {
|
|
"responseFormat": "file"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
1680,
|
|
64
|
|
],
|
|
"id": "0c00fd51-6018-4a56-b4d0-39a8b430238e",
|
|
"name": "Download CMR"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 2
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "9cca43eb-c3b7-49c7-a927-3fd6a406e43d",
|
|
"leftValue": "={{ $('Parse XML Response').item.json.Response.Status.Code }}",
|
|
"rightValue": "OK",
|
|
"operator": {
|
|
"type": "string",
|
|
"operation": "equals",
|
|
"name": "filter.operator.equals"
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
256,
|
|
400
|
|
],
|
|
"id": "e8a7eae7-1398-4a7b-8697-476d9e1f1f48",
|
|
"name": "Check for notes"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// n8n Code node (Run once for all items)\n// Split Response.Detail.Data[] naar losse items\n\nconst input = $input.first().json;\n\n// jouw response komt als [ { Response: ... } ] -> pak netjes de juiste root\nconst root = Array.isArray(input) ? (input[0] ?? {}) : input;\n\nconst response = root.Response ?? root;\nconst status = response.Status ?? {};\nconst data = response?.Detail?.Data;\n\nif (!Array.isArray(data)) {\n throw new Error('Kan Response.Detail.Data (array) niet vinden in de input.');\n}\n\n// Maak 1 n8n item per record in Data[]\nreturn data.map((row, index) => ({\n json: {\n ...row,\n // handig: status info meenemen (mag je weghalen als je dit niet wilt)\n _status: status,\n _index: index,\n },\n}));"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
-416,
|
|
96
|
|
],
|
|
"id": "a6f825a7-aff3-48f3-b248-1f4925ae9620",
|
|
"name": "Split Manifests1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "upload",
|
|
"path": "=/prod/ToTP/TLN/{{ String($json.consignmentNumber).replace(/[^A-Za-z0-9._-]/g, '_') }}_{{ $now.toFormat('yyyyMMddHHmmssSSS') }}_{{ $execution.id }}_{{ $itemIndex }}.xml",
|
|
"binaryData": false,
|
|
"fileContent": "={{ $json.xml }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.ftp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
2640,
|
|
496
|
|
],
|
|
"id": "0c0d86af-f0ff-413a-8d6b-e28158bf657c",
|
|
"name": "FTP2",
|
|
"credentials": {
|
|
"ftp": {
|
|
"id": "oLAZ4OgmkOopMHAq",
|
|
"name": "FTP De Wit Transport"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// n8n Code node\n// Zet op: Run once for all items\n\nconst MAP = {\n id: 'TrackingID',\n status: 'NoteCodeID', // of 'StatusCode' als je die wilt gebruiken\n date: 'NoteDate',\n time: 'NoteTime',\n note: 'NoteText',\n etaDate: 'BookInDate',\n etaTime: 'BookInTime',\n carrier: '', // bv. 'Carrier', 'Creditor', etc. Leeg laten als je hem niet uit input wilt halen\n};\n\nconst carrierFixed = '1024680'; // bv. '1024680' om hard te zetten. Leeg laten als je hem niet wilt meesturen\n\nconst xmlEsc = (s) =>\n String(s ?? '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n\nconst first = (...vals) =>\n vals.find(v => v !== undefined && v !== null && String(v).trim() !== '');\n\nfunction toAT(dateStr, timeStr = '') {\n const d = String(dateStr ?? '').trim();\n const t = String(timeStr ?? '').trim();\n\n // YYYY-MM-DD\n let m = d.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n if (m) {\n const [, year, month, day] = m;\n const tm = t.match(/^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/);\n const hh = tm ? tm[1] : '00';\n const mm = tm ? tm[2] : '00';\n const ss = tm && tm[3] ? tm[3] : '00';\n return `${year}${month}${day}${hh}${mm}${ss}`;\n }\n\n // DD/MM/YYYY\n m = d.match(/^(\\d{2})\\/(\\d{2})\\/(\\d{4})$/);\n if (m) {\n const [, day, month, year] = m;\n const tm = t.match(/^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/);\n const hh = tm ? tm[1] : '00';\n const mm = tm ? tm[2] : '00';\n const ss = tm && tm[3] ? tm[3] : '00';\n return `${year}${month}${day}${hh}${mm}${ss}`;\n }\n\n throw new Error(`Ongeldige datum: \"${d}\"`);\n}\n\nconst items = $input.all();\n\nreturn items.map((item, index) => {\n const row = item.json ?? {};\n\n const trackingId = first(row[MAP.id]);\n const status = first(row[MAP.status]);\n const date = first(row[MAP.date]);\n const time = first(row[MAP.time]);\n const note = first(row[MAP.note], '');\n const carrier = first(\n carrierFixed,\n MAP.carrier ? row[MAP.carrier] : '',\n ''\n );\n\n if (!trackingId) {\n throw new Error(`Item ${index + 1}: ${MAP.id} ontbreekt`);\n }\n\n if (!status) {\n throw new Error(`Item ${index + 1}: ${MAP.status} ontbreekt`);\n }\n\n if (!date) {\n throw new Error(`Item ${index + 1}: ${MAP.date} ontbreekt`);\n }\n\n const atDateTime = toAT(date, time);\n\n const etaDate = first(row[MAP.etaDate], '');\n const etaTime = first(row[MAP.etaTime], '');\n\n let etaAT = '';\n if (etaDate) {\n etaAT = toAT(etaDate, etaTime);\n }\n\n const carrierXml = carrier\n ? `\\n <carrier>${xmlEsc(carrier)}</carrier>`\n : '';\n\n const noteXml = note\n ? `\\n <note>${xmlEsc(note)}</note>`\n : '';\n\n const etaXml = etaAT\n ? `\\n <eta>${xmlEsc(etaAT)}</eta>`\n : '';\n\n const xml = `<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<xmlStat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n <dateTime type=\"AT\">${xmlEsc(atDateTime)}</dateTime>\n <codeList>TLN</codeList>\n <consignmentStat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n <id type=\"TRK\" datatype=\"varchar(50)\">${xmlEsc(trackingId)}</id>\n <status>${xmlEsc(status)}</status>\n <dateTime type=\"AT\">${xmlEsc(atDateTime)}</dateTime>${carrierXml}${noteXml}${etaXml}\n </consignmentStat>\n</xmlStat>`;\n\n const fileNameBase = `${trackingId}_cmr`;\n const fileName = `${fileNameBase}.xml`;\n\n return {\n json: {\n ...row,\n trackingId,\n consignmentNumber: trackingId,\n mappedStatus: status,\n atDateTime,\n etaAT,\n carrier,\n fileNameBase,\n fileName,\n xml,\n }\n };\n});"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1680,
|
|
208
|
|
],
|
|
"id": "5b6bbc7d-3948-48c5-8335-d9483bff46ba",
|
|
"name": "Create bookin xml"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// n8n Code node\n// Zet op: Run once for all items\n\nconst MAP = {\n id: 'TrackingID',\n status: 'NoteCodeID', // of 'StatusCode' als je die wilt gebruiken\n date: 'NoteDate',\n time: 'NoteTime',\n note: 'NoteText',\n etaDate: 'BookInDate',\n etaTime: 'BookInTime',\n carrier: '', // bv. 'Carrier', 'Creditor', etc. Leeg laten als je hem niet uit input wilt halen\n};\n\nconst carrierFixed = '1024680'; // bv. '1024680' om hard te zetten. Leeg laten als je hem niet wilt meesturen\n\nconst xmlEsc = (s) =>\n String(s ?? '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n\nconst first = (...vals) =>\n vals.find(v => v !== undefined && v !== null && String(v).trim() !== '');\n\nfunction toAT(dateStr, timeStr = '') {\n const d = String(dateStr ?? '').trim();\n const t = String(timeStr ?? '').trim();\n\n // YYYY-MM-DD\n let m = d.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n if (m) {\n const [, year, month, day] = m;\n const tm = t.match(/^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/);\n const hh = tm ? tm[1] : '00';\n const mm = tm ? tm[2] : '00';\n const ss = tm && tm[3] ? tm[3] : '00';\n return `${year}${month}${day}${hh}${mm}${ss}`;\n }\n\n // DD/MM/YYYY\n m = d.match(/^(\\d{2})\\/(\\d{2})\\/(\\d{4})$/);\n if (m) {\n const [, day, month, year] = m;\n const tm = t.match(/^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/);\n const hh = tm ? tm[1] : '00';\n const mm = tm ? tm[2] : '00';\n const ss = tm && tm[3] ? tm[3] : '00';\n return `${year}${month}${day}${hh}${mm}${ss}`;\n }\n\n throw new Error(`Ongeldige datum: \"${d}\"`);\n}\n\nconst items = $input.all();\n\nreturn items.map((item, index) => {\n const row = item.json ?? {};\n\n const consignmentNumber = first(row[MAP.id]);\n const status = first(row[MAP.status]);\n const date = first(row[MAP.date]);\n const time = first(row[MAP.time]);\n const note = first(row[MAP.note], '');\n const carrier = first(\n carrierFixed,\n MAP.carrier ? row[MAP.carrier] : '',\n ''\n );\n\n if (!consignmentNumber) {\n throw new Error(`Item ${index + 1}: ${MAP.id} ontbreekt`);\n }\n\n if (!status) {\n throw new Error(`Item ${index + 1}: ${MAP.status} ontbreekt`);\n }\n\n if (!date) {\n throw new Error(`Item ${index + 1}: ${MAP.date} ontbreekt`);\n }\n\n const atDateTime = toAT(date, time);\n\n // ETA uit losse datum + tijd\n const etaDate = first(row[MAP.etaDate], '');\n const etaTime = first(row[MAP.etaTime], '');\n\n let etaAT = '';\n if (etaDate) {\n etaAT = toAT(etaDate, etaTime);\n }\n\n const carrierXml = carrier\n ? `\\n <carrier>${xmlEsc(carrier)}</carrier>`\n : '';\n\n const noteXml = note\n ? `\\n <note>${xmlEsc(note)}</note>`\n : '';\n\n const etaXml = etaAT\n ? `\\n <eta>${xmlEsc(etaAT)}</eta>`\n : '';\n\n const xml = `<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<xmlStat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n <dateTime type=\"AT\">${xmlEsc(atDateTime)}</dateTime>\n <codeList>TLN</codeList>\n <consignmentStat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n <id type=\"TRK\" datatype=\"varchar(50)\">${xmlEsc(consignmentNumber)}</id>\n <status>${xmlEsc(status)}</status>\n <dateTime type=\"AT\">${xmlEsc(atDateTime)}</dateTime>${carrierXml}${noteXml}${etaXml}\n </consignmentStat>\n</xmlStat>`;\n\n return {\n json: {\n ...row,\n consignmentNumber,\n mappedStatus: status,\n atDateTime,\n etaAT,\n carrier,\n xml,\n }\n };\n});"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1680,
|
|
368
|
|
],
|
|
"id": "2604c649-d725-4005-8879-d9a135c8f521",
|
|
"name": "Create POC XML"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// n8n Code node\n// Zet op: Run once for all items\n\nconst MAP = {\n id: 'TrackingID',\n status: 'NoteCodeID', // of 'StatusCode'\n date: 'NoteDate',\n time: 'NoteTime',\n note: 'NoteText',\n etaDate: 'BookInDate',\n etaTime: 'BookInTime',\n signedBy: 'PodSignature', // naam van degene die tekende\n carrier: '', // bv. 'Carrier', 'Creditor', etc. Leeg laten als je hem niet uit input wilt halen\n};\n\nconst carrierFixed = '1024680'; // bv. '1024680' om hard te zetten. Leeg laten als je hem niet wilt meesturen\n\nconst xmlEsc = (s) =>\n String(s ?? '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n\nconst first = (...vals) =>\n vals.find(v => v !== undefined && v !== null && String(v).trim() !== '');\n\nfunction toAT(dateStr, timeStr = '') {\n const d = String(dateStr ?? '').trim();\n const t = String(timeStr ?? '').trim();\n\n let m = d.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n if (m) {\n const [, year, month, day] = m;\n const tm = t.match(/^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/);\n const hh = tm ? tm[1] : '00';\n const mm = tm ? tm[2] : '00';\n const ss = tm && tm[3] ? tm[3] : '00';\n return `${year}${month}${day}${hh}${mm}${ss}`;\n }\n\n m = d.match(/^(\\d{2})\\/(\\d{2})\\/(\\d{4})$/);\n if (m) {\n const [, day, month, year] = m;\n const tm = t.match(/^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/);\n const hh = tm ? tm[1] : '00';\n const mm = tm ? tm[2] : '00';\n const ss = tm && tm[3] ? tm[3] : '00';\n return `${year}${month}${day}${hh}${mm}${ss}`;\n }\n\n throw new Error(`Ongeldige datum: \"${d}\"`);\n}\n\nconst items = $input.all();\n\nreturn items.map((item, index) => {\n const row = item.json ?? {};\n\n const consignmentNumber = first(row[MAP.id]);\n const status = first(row[MAP.status]);\n const date = first(row[MAP.date]);\n const time = first(row[MAP.time]);\n const note = first(row[MAP.note], '');\n const signedBy = first(row[MAP.signedBy], '');\n const carrier = first(\n carrierFixed,\n MAP.carrier ? row[MAP.carrier] : '',\n ''\n );\n\n if (!consignmentNumber) {\n throw new Error(`Item ${index + 1}: ${MAP.id} ontbreekt`);\n }\n\n if (!status) {\n throw new Error(`Item ${index + 1}: ${MAP.status} ontbreekt`);\n }\n\n if (!date) {\n throw new Error(`Item ${index + 1}: ${MAP.date} ontbreekt`);\n }\n\n const atDateTime = toAT(date, time);\n\n const etaDate = first(row[MAP.etaDate], '');\n const etaTime = first(row[MAP.etaTime], '');\n\n let etaAT = '';\n if (etaDate) {\n etaAT = toAT(etaDate, etaTime);\n }\n\n const carrierXml = carrier\n ? `\\n <carrier>${xmlEsc(carrier)}</carrier>`\n : '';\n\n const noteXml = note\n ? `\\n <note>${xmlEsc(note)}</note>`\n : '';\n\n const etaXml = etaAT\n ? `\\n <eta>${xmlEsc(etaAT)}</eta>`\n : '';\n\n const nameXml = signedBy\n ? `\\n <name>${xmlEsc(signedBy)}</name>`\n : '';\n\n const xml = `<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<xmlStat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n <dateTime type=\"AT\">${xmlEsc(atDateTime)}</dateTime>\n <codeList>TLN</codeList>\n <consignmentStat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n <id type=\"TRK\" datatype=\"varchar(50)\">${xmlEsc(consignmentNumber)}</id>\n <status>${xmlEsc(status)}</status>\n <dateTime type=\"AT\">${xmlEsc(atDateTime)}</dateTime>${carrierXml}${noteXml}${etaXml}${nameXml}\n </consignmentStat>\n</xmlStat>`;\n\n return {\n json: {\n ...row,\n consignmentNumber,\n mappedStatus: status,\n atDateTime,\n etaAT,\n signedBy,\n carrier,\n xml,\n }\n };\n});"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1680,
|
|
528
|
|
],
|
|
"id": "39c97c8d-ef34-4319-8a9c-a4caf8c77a78",
|
|
"name": "Afgeleverd"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// n8n Code node\n// Zet op: Run once for all items\n\nconst MAP = {\n id: 'TrackingID',\n status: 'NoteCodeID', // of 'StatusCode' als je die wilt gebruiken\n date: 'NoteDate',\n time: 'NoteTime',\n note: 'NoteText',\n etaDate: 'ETADate',\n etaTime: 'ETATime',\n carrier: '', // bv. 'Carrier', 'Creditor', etc. Leeg laten als je hem niet uit input wilt halen\n};\n\nconst carrierFixed = '1024680'; // bv. '1024680' om hard te zetten. Leeg laten als je hem niet wilt meesturen\n\nconst xmlEsc = (s) =>\n String(s ?? '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n\nconst first = (...vals) =>\n vals.find(v => v !== undefined && v !== null && String(v).trim() !== '');\n\nfunction toAT(dateStr, timeStr = '') {\n const d = String(dateStr ?? '').trim();\n const t = String(timeStr ?? '').trim();\n\n // YYYY-MM-DD\n let m = d.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n if (m) {\n const [, year, month, day] = m;\n const tm = t.match(/^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/);\n const hh = tm ? tm[1] : '00';\n const mm = tm ? tm[2] : '00';\n const ss = tm && tm[3] ? tm[3] : '00';\n return `${year}${month}${day}${hh}${mm}${ss}`;\n }\n\n // DD/MM/YYYY\n m = d.match(/^(\\d{2})\\/(\\d{2})\\/(\\d{4})$/);\n if (m) {\n const [, day, month, year] = m;\n const tm = t.match(/^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/);\n const hh = tm ? tm[1] : '00';\n const mm = tm ? tm[2] : '00';\n const ss = tm && tm[3] ? tm[3] : '00';\n return `${year}${month}${day}${hh}${mm}${ss}`;\n }\n\n throw new Error(`Ongeldige datum: \"${d}\"`);\n}\n\nconst items = $input.all();\n\nreturn items.map((item, index) => {\n const row = item.json ?? {};\n\n const consignmentNumber = first(row[MAP.id]);\n const status = first(row[MAP.status]);\n const date = first(row[MAP.date]);\n const time = first(row[MAP.time]);\n const note = first(row[MAP.note], '');\n const carrier = first(\n carrierFixed,\n MAP.carrier ? row[MAP.carrier] : '',\n ''\n );\n\n if (!consignmentNumber) {\n throw new Error(`Item ${index + 1}: ${MAP.id} ontbreekt`);\n }\n\n if (!status) {\n throw new Error(`Item ${index + 1}: ${MAP.status} ontbreekt`);\n }\n\n if (!date) {\n throw new Error(`Item ${index + 1}: ${MAP.date} ontbreekt`);\n }\n\n const atDateTime = toAT(date, time);\n\n const etaDate = first(row[MAP.etaDate], '');\n const etaTime = first(row[MAP.etaTime], '');\n\n let etaAT = '';\n if (etaDate) {\n etaAT = toAT(etaDate, etaTime);\n }\n\n const carrierXml = carrier\n ? `\\n <carrier>${xmlEsc(carrier)}</carrier>`\n : '';\n\n const noteXml = note\n ? `\\n <note>${xmlEsc(note)}</note>`\n : '';\n\n const etaXml = etaAT\n ? `\\n <eta>${xmlEsc(etaAT)}</eta>`\n : '';\n\n const xml = `<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<xmlStat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n <dateTime type=\"AT\">${xmlEsc(atDateTime)}</dateTime>\n <codeList>TLN</codeList>\n <consignmentStat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n <id type=\"TRK\" datatype=\"varchar(50)\">${xmlEsc(consignmentNumber)}</id>\n <status>${xmlEsc(status)}</status>\n <dateTime type=\"AT\">${xmlEsc(atDateTime)}</dateTime>${carrierXml}${noteXml}${etaXml}\n </consignmentStat>\n</xmlStat>`;\n\n return {\n json: {\n ...row,\n consignmentNumber,\n mappedStatus: status,\n atDateTime,\n etaAT,\n carrier,\n xml,\n }\n };\n});"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1680,
|
|
704
|
|
],
|
|
"id": "208500b0-8b64-4567-9143-5089d37d737a",
|
|
"name": "Create ETA XML"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// n8n Code node\n// Zet op: Run once for all items\n\nconst MAP = {\n id: 'TrackingID',\n status: 'StatusCode', // of 'StatusCode'\n date: 'NoteDate',\n time: 'NoteTime',\n note: 'NoteText',\n etaDate: 'BookInDate',\n etaTime: 'BookInTime',\n signedBy: 'PodSignature', // naam van degene die tekende\n carrier: '', // hier kun je een veldnaam zetten\n};\n\nconst carrierFixed = '1024680'; // vul hier bv '1024680' in om hard te zetten\n\nconst xmlEsc = (s) =>\n String(s ?? '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n\nconst first = (...vals) =>\n vals.find(v => v !== undefined && v !== null && String(v).trim() !== '');\n\nfunction toAT(dateStr, timeStr = '') {\n const d = String(dateStr ?? '').trim();\n const t = String(timeStr ?? '').trim();\n\n let m = d.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n if (m) {\n const [, year, month, day] = m;\n const tm = t.match(/^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/);\n const hh = tm ? tm[1] : '00';\n const mm = tm ? tm[2] : '00';\n const ss = tm && tm[3] ? tm[3] : '00';\n return `${year}${month}${day}${hh}${mm}${ss}`;\n }\n\n m = d.match(/^(\\d{2})\\/(\\d{2})\\/(\\d{4})$/);\n if (m) {\n const [, day, month, year] = m;\n const tm = t.match(/^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/);\n const hh = tm ? tm[1] : '00';\n const mm = tm ? tm[2] : '00';\n const ss = tm && tm[3] ? tm[3] : '00';\n return `${year}${month}${day}${hh}${mm}${ss}`;\n }\n\n throw new Error(`Ongeldige datum: \"${d}\"`);\n}\n\nconst items = $input.all();\n\nreturn items.map((item, index) => {\n const row = item.json ?? {};\n\n const consignmentNumber = first(row[MAP.id]);\n const status = first(row[MAP.status]);\n const date = first(row[MAP.date]);\n const time = first(row[MAP.time]);\n const note = first(row[MAP.note], '');\n const signedBy = first(row[MAP.signedBy], '');\n const carrier = first(carrierFixed, row[MAP.carrier], '');\n\n if (!consignmentNumber) {\n throw new Error(`Item ${index + 1}: ${MAP.id} ontbreekt`);\n }\n\n if (!status) {\n throw new Error(`Item ${index + 1}: ${MAP.status} ontbreekt`);\n }\n\n if (!date) {\n throw new Error(`Item ${index + 1}: ${MAP.date} ontbreekt`);\n }\n\n const atDateTime = toAT(date, time);\n\n const etaDate = first(row[MAP.etaDate], '');\n const etaTime = first(row[MAP.etaTime], '');\n\n let etaAT = '';\n if (etaDate) {\n etaAT = toAT(etaDate, etaTime);\n }\n\n const noteXml = note\n ? `\\n <note>${xmlEsc(note)}</note>`\n : '';\n\n const carrierXml = carrier\n ? `\\n <carrier>${xmlEsc(carrier)}</carrier>`\n : '';\n\n const etaXml = etaAT\n ? `\\n <eta>${xmlEsc(etaAT)}</eta>`\n : '';\n\n const nameXml = signedBy\n ? `\\n <name>${xmlEsc(signedBy)}</name>`\n : '';\n\n const xml = `<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<xmlStat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n <dateTime type=\"AT\">${xmlEsc(atDateTime)}</dateTime>\n <codeList>TLN</codeList>\n <consignmentStat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n <id type=\"TRK\" datatype=\"varchar(50)\">${xmlEsc(consignmentNumber)}</id>\n <status>${xmlEsc(status)}</status>\n <dateTime type=\"AT\">${xmlEsc(atDateTime)}</dateTime>${carrierXml}${noteXml}${etaXml}${nameXml}\n </consignmentStat>\n</xmlStat>`;\n\n return {\n json: {\n ...row,\n consignmentNumber,\n mappedStatus: status,\n atDateTime,\n etaAT,\n signedBy,\n carrier,\n xml,\n }\n };\n});"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1680,
|
|
864
|
|
],
|
|
"id": "8431b66e-d4b6-4d85-b6e1-9bdc5ddb46ac",
|
|
"name": "Delivery depot XML"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const { PDFDocument } = require('pdf-lib');\n\nconst items = $input.all();\nconst BIN_PROP = 'data';\n\nfunction replaceExtWithPdf(name) {\n const fileName = String(name || 'document');\n return /\\.[^.]+$/.test(fileName)\n ? fileName.replace(/\\.[^.]+$/, '') + '.pdf'\n : fileName + '.pdf';\n}\n\nfunction basename(path) {\n return String(path || '').split('/').pop();\n}\n\nconst out = [];\n\nfor (let i = 0; i < items.length; i++) {\n const item = items[i];\n const bin = item.binary?.[BIN_PROP];\n if (!bin) continue;\n\n const originalName =\n bin.fileName ||\n item.json?.filename ||\n basename(item.json?.path) ||\n `file_${i + 1}`;\n\n const lowerName = originalName.toLowerCase();\n const mimeType = String(bin.mimeType || '').toLowerCase();\n\n const isPdf =\n mimeType === 'application/pdf' || lowerName.endsWith('.pdf');\n\n const isJpg =\n mimeType === 'image/jpeg' ||\n mimeType === 'image/jpg' ||\n lowerName.endsWith('.jpg') ||\n lowerName.endsWith('.jpeg');\n\n const isPng =\n mimeType === 'image/png' || lowerName.endsWith('.png');\n\n const isTif =\n mimeType === 'image/tiff' ||\n mimeType === 'image/tif' ||\n lowerName.endsWith('.tif') ||\n lowerName.endsWith('.tiff');\n\n // tif overslaan\n if (isTif) {\n continue;\n }\n\n // pdf ongewijzigd doorgeven\n if (isPdf) {\n out.push({\n json: {\n ...item.json,\n filename: originalName,\n },\n binary: {\n [BIN_PROP]: bin,\n },\n });\n continue;\n }\n\n // alleen jpg/png omzetten\n if (!isJpg && !isPng) {\n continue;\n }\n\n try {\n const buf = await this.helpers.getBinaryDataBuffer(i, BIN_PROP);\n\n if (!buf || !buf.length) {\n throw new Error('Leeg bestand');\n }\n\n const pdfDoc = await PDFDocument.create();\n\n let image;\n if (isJpg) {\n image = await pdfDoc.embedJpg(buf);\n } else {\n image = await pdfDoc.embedPng(buf);\n }\n\n // Gebruik size() uit pdf-lib\n const size = image.size();\n const width = Number(size.width);\n const height = Number(size.height);\n\n if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {\n throw new Error(`Ongeldige afbeeldingsgrootte: width=${width}, height=${height}`);\n }\n\n const page = pdfDoc.addPage();\n page.setSize(width, height);\n\n page.drawImage(image, {\n x: 0,\n y: 0,\n width,\n height,\n });\n\n const pdfBytes = await pdfDoc.save();\n const outputFileName = replaceExtWithPdf(originalName);\n\n const outBin = await this.helpers.prepareBinaryData(\n Buffer.from(pdfBytes),\n outputFileName,\n 'application/pdf'\n );\n\n out.push({\n json: {\n ...item.json,\n filename: outputFileName,\n converted: true,\n },\n binary: {\n [BIN_PROP]: outBin,\n },\n });\n } catch (err) {\n out.push({\n json: {\n ...item.json,\n filename: originalName,\n converted: false,\n error: err.message || String(err),\n },\n });\n }\n}\n\nreturn out;"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1872,
|
|
64
|
|
],
|
|
"id": "e5791874-cc39-45f3-81c9-da2c84f62738",
|
|
"name": "image to pdf1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// n8n Code node\n// Zet op: Run once for all items\n\nconst MAP = {\n id: 'TrackingID',\n status: 'NoteCodeID', // of 'StatusCode'\n date: 'NoteDate',\n time: 'NoteTime',\n note: 'NoteText',\n etaDate: 'BookInDate',\n etaTime: 'BookInTime',\n signedBy: 'PodSignature',\n carrier: '',\n};\n\nconst BIN_PROP = 'data';\nconst carrierFixed = '1024680';\n\nconst xmlEsc = (s) =>\n String(s ?? '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n\nconst first = (...vals) =>\n vals.find(v => v !== undefined && v !== null && String(v).trim() !== '');\n\nfunction toAT(dateStr, timeStr = '') {\n const d = String(dateStr ?? '').trim();\n const t = String(timeStr ?? '').trim();\n\n let m = d.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n if (m) {\n const [, year, month, day] = m;\n const tm = t.match(/^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/);\n const hh = tm ? tm[1] : '00';\n const mm = tm ? tm[2] : '00';\n const ss = tm && tm[3] ? tm[3] : '00';\n return `${year}${month}${day}${hh}${mm}${ss}`;\n }\n\n m = d.match(/^(\\d{2})\\/(\\d{2})\\/(\\d{4})$/);\n if (m) {\n const [, day, month, year] = m;\n const tm = t.match(/^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/);\n const hh = tm ? tm[1] : '00';\n const mm = tm ? tm[2] : '00';\n const ss = tm && tm[3] ? tm[3] : '00';\n return `${year}${month}${day}${hh}${mm}${ss}`;\n }\n\n throw new Error(`Ongeldige datum: \"${d}\"`);\n}\n\nconst items = $input.all();\nconst out = [];\n\nfor (let index = 0; index < items.length; index++) {\n const item = items[index];\n const row = item.json ?? {};\n\n const trackingId = first(row[MAP.id]);\n const status = first(row[MAP.status]);\n const date = first(row[MAP.date]);\n const time = first(row[MAP.time]);\n const note = first(row[MAP.note], '');\n const signedBy = first(row[MAP.signedBy], '');\n const carrier = first(carrierFixed, row[MAP.carrier], '');\n\n if (!trackingId) {\n throw new Error(`Item ${index + 1}: ${MAP.id} ontbreekt`);\n }\n\n if (!status) {\n throw new Error(`Item ${index + 1}: ${MAP.status} ontbreekt`);\n }\n\n if (!date) {\n throw new Error(`Item ${index + 1}: ${MAP.date} ontbreekt`);\n }\n\n const atDateTime = toAT(date, time);\n\n const etaDate = first(row[MAP.etaDate], '');\n const etaTime = first(row[MAP.etaTime], '');\n\n let etaAT = '';\n if (etaDate) {\n etaAT = toAT(etaDate, etaTime);\n }\n\n const noteXml = note\n ? `\\n <note>${xmlEsc(note)}</note>`\n : '';\n\n const carrierXml = carrier\n ? `\\n <carrier>${xmlEsc(carrier)}</carrier>`\n : '';\n\n const etaXml = etaAT\n ? `\\n <eta>${xmlEsc(etaAT)}</eta>`\n : '';\n\n const nameXml = signedBy\n ? `\\n <name>${xmlEsc(signedBy)}</name>`\n : '';\n\n // Bijlage als base64 toevoegen\n let signatureXml = '';\n\n const bin = item.binary?.[BIN_PROP];\n\n if (bin) {\n const buffer = await this.helpers.getBinaryDataBuffer(index, BIN_PROP);\n\n if (buffer && buffer.length > 0) {\n const base64 = buffer.toString('base64');\n\n const fileName = bin.fileName || row.filename || `${trackingId}_cmr.pdf`;\n const mimeType = String(bin.mimeType || '').toLowerCase();\n\n let fileType = 'pdf';\n\n if (mimeType.includes('jpeg') || fileName.toLowerCase().endsWith('.jpg') || fileName.toLowerCase().endsWith('.jpeg')) {\n fileType = 'jpg';\n } else if (mimeType.includes('png') || fileName.toLowerCase().endsWith('.png')) {\n fileType = 'png';\n } else {\n fileType = 'pdf';\n }\n\n signatureXml =\n `\\n <signature type=\"CMR\" filetype=\"${xmlEsc(fileType)}\">${base64}</signature>`;\n }\n }\n\n const xml = `<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<xmlStat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n <dateTime type=\"AT\">${xmlEsc(atDateTime)}</dateTime>\n <codeList>TLN</codeList>\n <consignmentStat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n <id type=\"TRK\" datatype=\"varchar(50)\">${xmlEsc(trackingId)}</id>\n <status>${xmlEsc(status)}</status>\n <dateTime type=\"AT\">${xmlEsc(atDateTime)}</dateTime>${carrierXml}${noteXml}${etaXml}${nameXml}${signatureXml}\n </consignmentStat>\n</xmlStat>`;\n\n const fileNameBase = `${trackingId}_cmr`;\n const fileName = `${fileNameBase}.xml`;\n\n out.push({\n json: {\n ...row,\n trackingId,\n consignmentNumber: trackingId,\n mappedStatus: status,\n atDateTime,\n etaAT,\n signedBy,\n carrier,\n hasAttachment: !!bin,\n attachmentAddedAsBase64: !!signatureXml,\n fileNameBase,\n fileName,\n xml,\n },\n });\n}\n\nreturn out;"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
2064,
|
|
64
|
|
],
|
|
"id": "117d36f5-0f46-4dfc-b3b7-ca0842155b5f",
|
|
"name": "Create XML1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "=https://portal.palletways.com/show_pod.php?fullsize=true&file={{ $json.NoteText }}",
|
|
"options": {
|
|
"response": {
|
|
"response": {
|
|
"responseFormat": "file"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
1680,
|
|
-64
|
|
],
|
|
"id": "682278be-158a-4bf2-ba73-bf0aba55baf4",
|
|
"name": "Download CMR1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const { PDFDocument } = require('pdf-lib');\n\nconst items = $input.all();\nconst BIN_PROP = 'data';\n\nfunction replaceExtWithPdf(name) {\n const fileName = String(name || 'document');\n return /\\.[^.]+$/.test(fileName)\n ? fileName.replace(/\\.[^.]+$/, '') + '.pdf'\n : fileName + '.pdf';\n}\n\nfunction basename(path) {\n return String(path || '').split('/').pop();\n}\n\nfunction getPairedIndex(item, fallbackIndex) {\n if (typeof item.pairedItem?.item === 'number') return item.pairedItem.item;\n if (Array.isArray(item.pairedItem) && typeof item.pairedItem[0]?.item === 'number') return item.pairedItem[0].item;\n return fallbackIndex;\n}\n\nfunction getOriginalJson(item, i) {\n try {\n const originalItems = $items('Bewaar originele data', 0, 0);\n const pairedIndex = getPairedIndex(item, i);\n return originalItems[pairedIndex]?.json ?? {};\n } catch (e) {\n return {};\n }\n}\n\nconst out = [];\n\nfor (let i = 0; i < items.length; i++) {\n const item = items[i];\n\n // Herstel de originele JSON, want HTTP Request kan JSON vervangen/verliezen\n const originalJson = {\n ...getOriginalJson(item, i),\n ...(item.json ?? {}),\n };\n\n const bin = item.binary?.[BIN_PROP];\n\n if (!bin) {\n out.push({\n json: {\n ...originalJson,\n converted: false,\n attachmentError: `Geen binary property '${BIN_PROP}' gevonden`,\n },\n });\n continue;\n }\n\n const originalName =\n bin.fileName ||\n originalJson?.filename ||\n basename(originalJson?.path) ||\n basename(originalJson?.NoteText) ||\n `file_${i + 1}`;\n\n const lowerName = originalName.toLowerCase();\n const mimeType = String(bin.mimeType || '').toLowerCase();\n\n const isPdf =\n mimeType === 'application/pdf' || lowerName.endsWith('.pdf');\n\n const isJpg =\n mimeType === 'image/jpeg' ||\n mimeType === 'image/jpg' ||\n lowerName.endsWith('.jpg') ||\n lowerName.endsWith('.jpeg');\n\n const isPng =\n mimeType === 'image/png' || lowerName.endsWith('.png');\n\n const isTif =\n mimeType === 'image/tiff' ||\n mimeType === 'image/tif' ||\n lowerName.endsWith('.tif') ||\n lowerName.endsWith('.tiff');\n\n // tif overslaan\n if (isTif) {\n out.push({\n json: {\n ...originalJson,\n filename: originalName,\n converted: false,\n attachmentSkipped: true,\n attachmentError: 'TIF/TIFF wordt overgeslagen',\n },\n });\n continue;\n }\n\n // pdf ongewijzigd doorgeven\n if (isPdf) {\n out.push({\n json: {\n ...originalJson,\n filename: originalName,\n converted: false,\n },\n binary: {\n [BIN_PROP]: bin,\n },\n });\n continue;\n }\n\n // alleen jpg/png omzetten\n if (!isJpg && !isPng) {\n out.push({\n json: {\n ...originalJson,\n filename: originalName,\n converted: false,\n attachmentSkipped: true,\n attachmentError: `Bestandstype niet ondersteund: ${mimeType || originalName}`,\n },\n });\n continue;\n }\n\n try {\n const buf = await this.helpers.getBinaryDataBuffer(i, BIN_PROP);\n\n if (!buf || !buf.length) {\n throw new Error('Leeg bestand');\n }\n\n const pdfDoc = await PDFDocument.create();\n\n let image;\n if (isJpg) {\n image = await pdfDoc.embedJpg(buf);\n } else {\n image = await pdfDoc.embedPng(buf);\n }\n\n const size = image.size();\n const width = Number(size.width);\n const height = Number(size.height);\n\n if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {\n throw new Error(`Ongeldige afbeeldingsgrootte: width=${width}, height=${height}`);\n }\n\n const page = pdfDoc.addPage();\n page.setSize(width, height);\n\n page.drawImage(image, {\n x: 0,\n y: 0,\n width,\n height,\n });\n\n const pdfBytes = await pdfDoc.save();\n const outputFileName = replaceExtWithPdf(originalName);\n\n const outBin = await this.helpers.prepareBinaryData(\n Buffer.from(pdfBytes),\n outputFileName,\n 'application/pdf'\n );\n\n out.push({\n json: {\n ...originalJson,\n filename: outputFileName,\n converted: true,\n },\n binary: {\n [BIN_PROP]: outBin,\n },\n });\n } catch (err) {\n out.push({\n json: {\n ...originalJson,\n filename: originalName,\n converted: false,\n attachmentError: err.message || String(err),\n },\n });\n }\n}\n\nreturn out;"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1872,
|
|
-64
|
|
],
|
|
"id": "a6d77e7e-ec90-4723-b70e-b93bfd7c0e25",
|
|
"name": "image to pdf"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// n8n Code node\n// Zet op: Run once for all items\n\nconst MAP = {\n id: 'TrackingID',\n status: 'NoteCodeID', // of 'StatusCode'\n date: 'NoteDate',\n time: 'NoteTime',\n note: 'NoteText',\n etaDate: 'BookInDate',\n etaTime: 'BookInTime',\n signedBy: 'PodSignature',\n carrier: '',\n};\n\nconst BIN_PROP = 'data';\nconst carrierFixed = '1024680';\n\nconst xmlEsc = (s) =>\n String(s ?? '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n\nconst first = (...vals) =>\n vals.find(v => v !== undefined && v !== null && String(v).trim() !== '');\n\nfunction toAT(dateStr, timeStr = '') {\n const d = String(dateStr ?? '').trim();\n const t = String(timeStr ?? '').trim();\n\n let m = d.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n if (m) {\n const [, year, month, day] = m;\n const tm = t.match(/^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/);\n const hh = tm ? tm[1] : '00';\n const mm = tm ? tm[2] : '00';\n const ss = tm && tm[3] ? tm[3] : '00';\n return `${year}${month}${day}${hh}${mm}${ss}`;\n }\n\n m = d.match(/^(\\d{2})\\/(\\d{2})\\/(\\d{4})$/);\n if (m) {\n const [, day, month, year] = m;\n const tm = t.match(/^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/);\n const hh = tm ? tm[1] : '00';\n const mm = tm ? tm[2] : '00';\n const ss = tm && tm[3] ? tm[3] : '00';\n return `${year}${month}${day}${hh}${mm}${ss}`;\n }\n\n throw new Error(`Ongeldige datum: \"${d}\"`);\n}\n\nfunction getFileType(bin, fileName) {\n const mimeType = String(bin?.mimeType || '').toLowerCase();\n const lowerName = String(fileName || '').toLowerCase();\n\n if (mimeType.includes('pdf') || lowerName.endsWith('.pdf')) return 'pdf';\n if (mimeType.includes('jpeg') || lowerName.endsWith('.jpg') || lowerName.endsWith('.jpeg')) return 'jpg';\n if (mimeType.includes('png') || lowerName.endsWith('.png')) return 'png';\n\n // Omdat image to pdf1 alles naar pdf omzet, is pdf hier de veilige fallback.\n return 'pdf';\n}\n\nconst items = $input.all();\nconst out = [];\n\nfor (let index = 0; index < items.length; index++) {\n const item = items[index];\n const row = item.json ?? {};\n\n const trackingId = first(row[MAP.id]);\n const status = first(row[MAP.status]);\n const date = first(row[MAP.date]);\n const time = first(row[MAP.time]);\n const note = first(row[MAP.note], '');\n const signedBy = first(row[MAP.signedBy], '');\n const carrier = first(carrierFixed, row[MAP.carrier], '');\n\n if (!trackingId) {\n throw new Error(\n `Item ${index + 1}: TrackingID ontbreekt. Beschikbare velden: ${Object.keys(row).join(', ')}`\n );\n }\n\n if (!status) {\n throw new Error(\n `Item ${index + 1}: ${MAP.status} ontbreekt. Beschikbare velden: ${Object.keys(row).join(', ')}`\n );\n }\n\n if (!date) {\n throw new Error(\n `Item ${index + 1}: ${MAP.date} ontbreekt. Beschikbare velden: ${Object.keys(row).join(', ')}`\n );\n }\n\n const atDateTime = toAT(date, time);\n\n const etaDate = first(row[MAP.etaDate], '');\n const etaTime = first(row[MAP.etaTime], '');\n\n let etaAT = '';\n if (etaDate) {\n etaAT = toAT(etaDate, etaTime);\n }\n\n const noteXml = note\n ? `\\n <note>${xmlEsc(note)}</note>`\n : '';\n\n const carrierXml = carrier\n ? `\\n <carrier>${xmlEsc(carrier)}</carrier>`\n : '';\n\n const etaXml = etaAT\n ? `\\n <eta>${xmlEsc(etaAT)}</eta>`\n : '';\n\n const nameXml = signedBy\n ? `\\n <name>${xmlEsc(signedBy)}</name>`\n : '';\n\n let signatureXml = '';\n let attachmentAddedAsBase64 = false;\n let attachmentBase64Length = 0;\n\n const bin = item.binary?.[BIN_PROP];\n\n if (bin) {\n const buffer = await this.helpers.getBinaryDataBuffer(index, BIN_PROP);\n\n if (buffer && buffer.length > 0) {\n const base64 = buffer.toString('base64');\n const attachmentFileName = bin.fileName || row.filename || `${trackingId}_cmr.pdf`;\n const fileType = getFileType(bin, attachmentFileName);\n\n signatureXml =\n `\\n <signature type=\"CMR\" filetype=\"${xmlEsc(fileType)}\">${base64}</signature>`;\n\n attachmentAddedAsBase64 = true;\n attachmentBase64Length = base64.length;\n }\n }\n\n const xml = `<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<xmlStat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n <dateTime type=\"AT\">${xmlEsc(atDateTime)}</dateTime>\n <codeList>TLN</codeList>\n <consignmentStat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n <id type=\"TRK\" datatype=\"varchar(50)\">${xmlEsc(trackingId)}</id>\n <status>${xmlEsc(status)}</status>\n <dateTime type=\"AT\">${xmlEsc(atDateTime)}</dateTime>${carrierXml}${noteXml}${etaXml}${nameXml}${signatureXml}\n </consignmentStat>\n</xmlStat>`;\n\n const fileNameBase = `${trackingId}_cmr`;\n const fileName = `${fileNameBase}.xml`;\n\n out.push({\n json: {\n ...row,\n trackingId,\n consignmentNumber: trackingId,\n mappedStatus: status,\n atDateTime,\n etaAT,\n signedBy,\n carrier,\n hasAttachment: !!bin,\n attachmentAddedAsBase64,\n attachmentBase64Length,\n fileNameBase,\n fileName,\n xml,\n },\n });\n}\n\nreturn out;"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
2064,
|
|
-64
|
|
],
|
|
"id": "f6c89c6e-fb4e-4a78-8c7c-0048a6215a79",
|
|
"name": "Create XML"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// n8n Code node\n// Zet op: Run once for all items\n\nconst MAP = {\n id: 'TrackingID',\n status: 'StatusCode', // of 'StatusCode'\n date: 'NoteDate',\n time: 'NoteTime',\n note: 'NoteText',\n etaDate: 'BookInDate',\n etaTime: 'BookInTime',\n signedBy: 'PodSignature', // naam van degene die tekende\n carrier: '', // hier kun je een veldnaam zetten\n};\n\nconst carrierFixed = '1024680'; // vul hier bv '1024680' in om hard te zetten\n\nconst xmlEsc = (s) =>\n String(s ?? '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n\nconst first = (...vals) =>\n vals.find(v => v !== undefined && v !== null && String(v).trim() !== '');\n\nfunction toAT(dateStr, timeStr = '') {\n const d = String(dateStr ?? '').trim();\n const t = String(timeStr ?? '').trim();\n\n let m = d.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n if (m) {\n const [, year, month, day] = m;\n const tm = t.match(/^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/);\n const hh = tm ? tm[1] : '00';\n const mm = tm ? tm[2] : '00';\n const ss = tm && tm[3] ? tm[3] : '00';\n return `${year}${month}${day}${hh}${mm}${ss}`;\n }\n\n m = d.match(/^(\\d{2})\\/(\\d{2})\\/(\\d{4})$/);\n if (m) {\n const [, day, month, year] = m;\n const tm = t.match(/^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/);\n const hh = tm ? tm[1] : '00';\n const mm = tm ? tm[2] : '00';\n const ss = tm && tm[3] ? tm[3] : '00';\n return `${year}${month}${day}${hh}${mm}${ss}`;\n }\n\n throw new Error(`Ongeldige datum: \"${d}\"`);\n}\n\nconst items = $input.all();\n\nreturn items.map((item, index) => {\n const row = item.json ?? {};\n\n const consignmentNumber = first(row[MAP.id]);\n const status = first(row[MAP.status]);\n const date = first(row[MAP.date]);\n const time = first(row[MAP.time]);\n const note = first(row[MAP.note], '');\n const signedBy = first(row[MAP.signedBy], '');\n const carrier = first(carrierFixed, row[MAP.carrier], '');\n\n if (!consignmentNumber) {\n throw new Error(`Item ${index + 1}: ${MAP.id} ontbreekt`);\n }\n\n if (!status) {\n throw new Error(`Item ${index + 1}: ${MAP.status} ontbreekt`);\n }\n\n if (!date) {\n throw new Error(`Item ${index + 1}: ${MAP.date} ontbreekt`);\n }\n\n const atDateTime = toAT(date, time);\n\n const etaDate = first(row[MAP.etaDate], '');\n const etaTime = first(row[MAP.etaTime], '');\n\n let etaAT = '';\n if (etaDate) {\n etaAT = toAT(etaDate, etaTime);\n }\n\n const noteXml = note\n ? `\\n <note>${xmlEsc(note)}</note>`\n : '';\n\n const carrierXml = carrier\n ? `\\n <carrier>${xmlEsc(carrier)}</carrier>`\n : '';\n\n const etaXml = etaAT\n ? `\\n <eta>${xmlEsc(etaAT)}</eta>`\n : '';\n\n const nameXml = signedBy\n ? `\\n <name>${xmlEsc(signedBy)}</name>`\n : '';\n\n const xml = `<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<xmlStat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n <dateTime type=\"AT\">${xmlEsc(atDateTime)}</dateTime>\n <codeList>TLN</codeList>\n <consignmentStat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n <id type=\"TRK\" datatype=\"varchar(50)\">${xmlEsc(consignmentNumber)}</id>\n <status>${xmlEsc(status)}</status>\n <dateTime type=\"AT\">${xmlEsc(atDateTime)}</dateTime>${carrierXml}${noteXml}${etaXml}${nameXml}\n </consignmentStat>\n</xmlStat>`;\n\n return {\n json: {\n ...row,\n consignmentNumber,\n mappedStatus: status,\n atDateTime,\n etaAT,\n signedBy,\n carrier,\n xml,\n }\n };\n});"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1680,
|
|
1040
|
|
],
|
|
"id": "68ad47dc-543f-4e47-bc36-ac10ee80b5e9",
|
|
"name": "Geladen Hub"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// n8n Code node\n// Zet op: Run once for all items\n\nconst MAP = {\n id: 'TrackingID',\n status: 'StatusCode', // of 'StatusCode'\n date: 'NoteDate',\n time: 'NoteTime',\n note: 'NoteText',\n etaDate: 'BookInDate',\n etaTime: 'BookInTime',\n signedBy: 'PodSignature', // naam van degene die tekende\n carrier: '', // hier kun je een veldnaam zetten\n};\n\nconst carrierFixed = '1024680'; // vul hier bv '1024680' in om hard te zetten\n\nconst xmlEsc = (s) =>\n String(s ?? '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n\nconst first = (...vals) =>\n vals.find(v => v !== undefined && v !== null && String(v).trim() !== '');\n\nfunction toAT(dateStr, timeStr = '') {\n const d = String(dateStr ?? '').trim();\n const t = String(timeStr ?? '').trim();\n\n let m = d.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n if (m) {\n const [, year, month, day] = m;\n const tm = t.match(/^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/);\n const hh = tm ? tm[1] : '00';\n const mm = tm ? tm[2] : '00';\n const ss = tm && tm[3] ? tm[3] : '00';\n return `${year}${month}${day}${hh}${mm}${ss}`;\n }\n\n m = d.match(/^(\\d{2})\\/(\\d{2})\\/(\\d{4})$/);\n if (m) {\n const [, day, month, year] = m;\n const tm = t.match(/^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/);\n const hh = tm ? tm[1] : '00';\n const mm = tm ? tm[2] : '00';\n const ss = tm && tm[3] ? tm[3] : '00';\n return `${year}${month}${day}${hh}${mm}${ss}`;\n }\n\n throw new Error(`Ongeldige datum: \"${d}\"`);\n}\n\nconst items = $input.all();\n\nreturn items.map((item, index) => {\n const row = item.json ?? {};\n\n const consignmentNumber = first(row[MAP.id]);\n const status = first(row[MAP.status]);\n const date = first(row[MAP.date]);\n const time = first(row[MAP.time]);\n const note = first(row[MAP.note], '');\n const signedBy = first(row[MAP.signedBy], '');\n const carrier = first(carrierFixed, row[MAP.carrier], '');\n\n if (!consignmentNumber) {\n throw new Error(`Item ${index + 1}: ${MAP.id} ontbreekt`);\n }\n\n if (!status) {\n throw new Error(`Item ${index + 1}: ${MAP.status} ontbreekt`);\n }\n\n if (!date) {\n throw new Error(`Item ${index + 1}: ${MAP.date} ontbreekt`);\n }\n\n const atDateTime = toAT(date, time);\n\n const etaDate = first(row[MAP.etaDate], '');\n const etaTime = first(row[MAP.etaTime], '');\n\n let etaAT = '';\n if (etaDate) {\n etaAT = toAT(etaDate, etaTime);\n }\n\n const noteXml = note\n ? `\\n <note>${xmlEsc(note)}</note>`\n : '';\n\n const carrierXml = carrier\n ? `\\n <carrier>${xmlEsc(carrier)}</carrier>`\n : '';\n\n const etaXml = etaAT\n ? `\\n <eta>${xmlEsc(etaAT)}</eta>`\n : '';\n\n const nameXml = signedBy\n ? `\\n <name>${xmlEsc(signedBy)}</name>`\n : '';\n\n const xml = `<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<xmlStat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n <dateTime type=\"AT\">${xmlEsc(atDateTime)}</dateTime>\n <codeList>TLN</codeList>\n <consignmentStat xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n <id type=\"TRK\" datatype=\"varchar(50)\">${xmlEsc(consignmentNumber)}</id>\n <status>${xmlEsc(status)}</status>\n <dateTime type=\"AT\">${xmlEsc(atDateTime)}</dateTime>${carrierXml}${noteXml}${etaXml}${nameXml}\n </consignmentStat>\n</xmlStat>`;\n\n return {\n json: {\n ...row,\n consignmentNumber,\n mappedStatus: status,\n atDateTime,\n etaAT,\n signedBy,\n carrier,\n xml,\n }\n };\n});"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1680,
|
|
1216
|
|
],
|
|
"id": "8456fbdc-4d33-4751-b529-e89ff7c49bed",
|
|
"name": "Out for delivery"
|
|
}
|
|
],
|
|
"pinData": {},
|
|
"connections": {
|
|
"Schedule Trigger": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Get Last Timestamp from Redis",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Parse XML Response": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Check for notes",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Split Manifests": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Filter Paying depot",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Get Last Timestamp from Redis": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Parse Last Timestamp",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Parse Last Timestamp": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Prepare Timestamp Data",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "Get Palletways Notes",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Prepare Timestamp Data": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Save Timestamp to Redis",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Get Palletways Notes": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Parse XML Response",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Filter Paying depot": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Switch",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Switch": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Download CMR1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Create bookin xml",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Create POC XML",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Afgeleverd",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Create ETA XML",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Delivery depot XML",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Geladen Hub",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Out for delivery",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[]
|
|
]
|
|
},
|
|
"Download CMR": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "image to pdf1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Check for notes": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Split Manifests",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Create bookin xml": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "FTP2",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Create POC XML": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "FTP2",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Afgeleverd": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "FTP2",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Create ETA XML": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "FTP2",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Delivery depot XML": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "FTP2",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"image to pdf1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Create XML1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Create XML1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "FTP2",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Download CMR1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "image to pdf",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"image to pdf": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Create XML",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Create XML": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "FTP2",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Geladen Hub": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "FTP2",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Out for delivery": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "FTP2",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"active": true,
|
|
"settings": {
|
|
"executionOrder": "v1",
|
|
"binaryMode": "separate",
|
|
"timezone": "Europe/London",
|
|
"callerPolicy": "workflowsFromSameOwner",
|
|
"availableInMCP": false,
|
|
"timeSavedMode": "fixed",
|
|
"errorWorkflow": "1Ps5lukDf2sgcGL7"
|
|
},
|
|
"versionId": "3d92e4ff-a291-43d8-b65d-eb7c37bb0c54",
|
|
"meta": {
|
|
"templateCredsSetupCompleted": true,
|
|
"instanceId": "bef8d409866a58c0777dfe7cca1b9c2400fd051c056d361501393ab423006b5f"
|
|
},
|
|
"nodeGroups": [],
|
|
"id": "CyVjh2cxoyAsO1kN",
|
|
"tags": [
|
|
{
|
|
"updatedAt": "2025-11-11T04:59:39.416Z",
|
|
"createdAt": "2025-11-11T04:59:39.416Z",
|
|
"id": "ClOZrAjJKoGLFvaO",
|
|
"name": "Palletways"
|
|
}
|
|
]
|
|
} |