{ "name": "Gheeraert workflow 4 — Transpas status XML export", "nodes": [ { "parameters": { "operation": "executeQuery", "query": "SELECT\n id,\n order_log_id,\n gheeraert_order_id,\n purchase_order_number,\n status_id,\n tracking_date,\n tracking_comments,\n tracking_url,\n barcode,\n user_name,\n transpas_export_status\nFROM public.ghe_status_log\nWHERE COALESCE(transpas_export_status, 'pending') = 'pending'\nORDER BY tracking_date, id\nLIMIT 100;", "options": {} }, "type": "n8n-nodes-base.postgres", "typeVersion": 2.6, "position": [ -1552, -192 ], "id": "a00a8bbb-bf11-4292-9520-86aea2ae2dc1", "name": "Get pending ghe_status_log", "credentials": { "postgres": { "id": "pxziuCOuNpsUFKrm", "name": "transpas_n8n_gheeraert" } } }, { "parameters": { "jsCode": "// n8n Code node: Gheeraert status -> Transpas status XML\n// Mode: Run once for each item\n\nconst AMP_FIX_RE = /&(?!amp;|lt;|gt;|quot;|apos;|#\\d+;|#x[0-9A-Fa-f]+;)/g;\n\nfunction xmlEsc(value) {\n return String(value ?? '')\n .replace(AMP_FIX_RE, '&')\n .replace(//g, '>')\n .replace(/\\\"/g, '"')\n .replace(/'/g, ''');\n}\n\nfunction formatATDateTime(input) {\n if (!input) return '';\n const d = new Date(input);\n if (isNaN(d.getTime())) return '';\n const pad = (n) => String(n).padStart(2, '0');\n return (\n d.getFullYear() +\n pad(d.getMonth() + 1) +\n pad(d.getDate()) +\n pad(d.getHours()) +\n pad(d.getMinutes()) +\n pad(d.getSeconds())\n );\n}\n\nfunction safeFilePart(value) {\n return String(value || '')\n .replace(/[^a-zA-Z0-9._-]+/g, '_')\n .replace(/^_+|_+$/g, '')\n .slice(0, 80) || 'status';\n}\n\nfunction nowStamp() {\n const d = new Date();\n const p = (n) => String(n).padStart(2, '0');\n return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}_${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}_${String(d.getMilliseconds()).padStart(3, '0')}`;\n}\n\nfunction mapStatus(statusId) {\n const sid = Number(statusId);\n switch (sid) {\n case 0: return { export: true, transpasStatus: '0', label: 'Cancelled' };\n case 5: return { export: false, transpasStatus: null, label: 'New' };\n case 10: return { export: true, transpasStatus: null, label: 'Confirmed' };\n case 20: return { export: false, transpasStatus: null, label: 'Accepted' };\n case 21: return { export: false, transpasStatus: null, label: 'Depot_Assigned' };\n case 22: return { export: false, transpasStatus: null, label: 'Planned' };\n case 23: return { export: true, transpasStatus: '23', label: 'Collected' };\n case 24: return { export: true, transpasStatus: '24', label: 'Arrived_At_Depot' };\n case 25: return { export: false, transpasStatus: null, label: 'Storage_At_Depot' };\n case 26: return { export: true, transpasStatus: '26', label: 'Shipped_From_Depot' };\n case 27: return { export: true, transpasStatus: '27', label: 'Incomplete' };\n case 28: return { export: true, transpasStatus: '28', label: 'Manco' };\n case 30: return { export: true, transpasStatus: '30', label: 'Delivered' };\n case 35: return { export: true, transpasStatus: '35', label: 'POD_Online' };\n case 40: return { export: false, transpasStatus: null, label: 'Invoiced' };\n case 50: return { export: false, transpasStatus: null, label: 'Closed' };\n case 100: return { export: false, transpasStatus: null, label: 'Custom_100' };\n default: return { export: false, transpasStatus: null, label: `Unknown_${sid}` };\n }\n}\n\nconst j = $json;\nconst mapped = mapStatus(j.status_id);\nconst atDateTime = formatATDateTime(j.tracking_date);\nconst consignmentId = String(j.purchase_order_number || '').trim();\n\nlet noteParts = [mapped.label, j.tracking_comments || ''];\nif (j.tracking_url) noteParts.push(`POD/URL: ${j.tracking_url}`);\nif (j.barcode) noteParts.push(`Barcode: ${j.barcode}`);\nconst note = noteParts.filter(Boolean).join(' - ');\n\nlet xml = '';\nlet fileName = null;\n\nif (mapped.export && consignmentId && atDateTime) {\n xml = [\n '',\n '',\n ` ${xmlEsc(atDateTime)}`,\n ' TLN',\n ' ',\n ` ${xmlEsc(consignmentId)}`,\n ` ${xmlEsc(mapped.transpasStatus)}`,\n ` ${xmlEsc(note)}`,\n ' ',\n '',\n ].join('\\n');\n\n fileName = `GHE_TLN_${safeFilePart(consignmentId)}_${safeFilePart(mapped.transpasStatus)}_${nowStamp()}.xml`;\n}\n\nreturn {\n json: {\n ...j,\n mapped_label: mapped.label,\n mapped_transpas_status: mapped.transpasStatus,\n should_export_to_transpas: mapped.export,\n transpas_xml: xml || null,\n transpas_xml_file: fileName,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -1328, -192 ], "id": "ec13ec89-6e87-4ea2-9ac1-a55bae2daef8", "name": "Build Transpas XML" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "2aa00fa4-f1bc-490f-ac20-45359523ea8f", "leftValue": "={{ $json.should_export_to_transpas }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -1104, -192 ], "id": "1c38efca-a152-4568-a73d-5e4b0710dea9", "name": "Should export to Transpas?" }, { "parameters": { "jsCode": "const xml = $json.transpas_xml;\nconst fileName = $json.transpas_xml_file || 'status.xml';\n\nconst binaryData = await this.helpers.prepareBinaryData(\n Buffer.from(xml, 'utf8'),\n fileName,\n 'application/xml'\n);\n\nreturn {\n json: {\n ...$json,\n },\n binary: {\n data: binaryData,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -880, -288 ], "id": "a384cc7c-6eed-4fdc-b1d2-0a3e0d0bbaa3", "name": "Create XML Binary" }, { "parameters": { "protocol": "sftp", "operation": "upload", "path": "=/prod/to_tp/tln/{{ $json.transpas_xml_file }}", "options": {} }, "type": "n8n-nodes-base.ftp", "typeVersion": 1, "position": [ -656, -288 ], "id": "b997c3af-a1ff-426a-b1c2-b442aa013a61", "name": "SFTP Upload", "credentials": { "sftp": { "id": "uKyzg5cSXQqXOuHI", "name": "SFTP n8n" } } }, { "parameters": { "operation": "executeQuery", "query": "UPDATE public.ghe_status_log\nSET\n transpas_xml_file = $1::varchar,\n transpas_xml_created_at = CURRENT_TIMESTAMP,\n transpas_export_status = 'exported',\n transpas_export_error = NULL\nWHERE id = $2::bigint;", "options": { "queryReplacement": "={{ [$json.transpas_xml_file, $json.id] }}" } }, "type": "n8n-nodes-base.postgres", "typeVersion": 2.6, "position": [ -432, -288 ], "id": "f0980ea4-86e1-40fe-b616-f6a294606aa1", "name": "Mark exported", "credentials": { "postgres": { "id": "pxziuCOuNpsUFKrm", "name": "transpas_n8n_gheeraert" } } }, { "parameters": { "operation": "executeQuery", "query": "WITH input AS (\n SELECT $1::jsonb AS j\n)\nUPDATE public.ghe_status_log\nSET\n transpas_export_status = 'ignored',\n transpas_export_error = (j->>'error_text')::text\nFROM input\nWHERE public.ghe_status_log.id = (j->>'id')::bigint;", "options": { "queryReplacement": "={{ [JSON.stringify({ id: $json.id, error_text: 'Status niet geëxporteerd volgens mapping: ' + ($json.mapped_label || 'onbekend') })] }}" } }, "type": "n8n-nodes-base.postgres", "typeVersion": 2.6, "position": [ -896, -96 ], "id": "051233d8-8852-4cde-b5ec-4c01d5c7fc80", "name": "Mark ignored", "credentials": { "postgres": { "id": "pxziuCOuNpsUFKrm", "name": "transpas_n8n_gheeraert" } } }, { "parameters": { "rule": { "interval": [ { "field": "cronExpression", "expression": "*/20 6-22 * * 1-5" } ] } }, "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1.3, "position": [ -1760, -192 ], "id": "705a4240-1aa6-44f8-834d-03dc81cc0ca6", "name": "Schedule Trigger" } ], "pinData": {}, "connections": { "Get pending ghe_status_log": { "main": [ [ { "node": "Build Transpas XML", "type": "main", "index": 0 } ] ] }, "Build Transpas XML": { "main": [ [ { "node": "Should export to Transpas?", "type": "main", "index": 0 } ] ] }, "Should export to Transpas?": { "main": [ [ { "node": "Create XML Binary", "type": "main", "index": 0 } ], [ { "node": "Mark ignored", "type": "main", "index": 0 } ] ] }, "Create XML Binary": { "main": [ [ { "node": "SFTP Upload", "type": "main", "index": 0 } ] ] }, "SFTP Upload": { "main": [ [ { "node": "Mark exported", "type": "main", "index": 0 } ] ] }, "Schedule Trigger": { "main": [ [ { "node": "Get pending ghe_status_log", "type": "main", "index": 0 } ] ] } }, "active": false, "settings": { "executionOrder": "v1", "binaryMode": "separate" }, "versionId": "7006b721-596e-4654-96dd-c6d71a1e10e3", "meta": { "instanceId": "bef8d409866a58c0777dfe7cca1b9c2400fd051c056d361501393ab423006b5f" }, "nodeGroups": [], "id": "tgpdWats31Z0Idf6", "tags": [] }