diff --git a/Flower_direct/Lidl Frankrijk - Opdracht naar Postgres en Transpas.json b/Flower_direct/Lidl Frankrijk - Opdracht naar Postgres en Transpas.json
index 5896cc0..e765da7 100644
--- a/Flower_direct/Lidl Frankrijk - Opdracht naar Postgres en Transpas.json
+++ b/Flower_direct/Lidl Frankrijk - Opdracht naar Postgres en Transpas.json
@@ -1,5 +1,5 @@
{
- "name": "Lidl Frankrijk - Opdracht naar Postgres en Transpas -",
+ "name": "Lidl Frankrijk - Opdracht naar Postgres en Transpas",
"nodes": [
{
"parameters": {
@@ -9,9 +9,9 @@
"typeVersion": 2,
"position": [
1136,
- 416
+ 576
],
- "id": "2d78e121-cdb5-48b6-a98b-0aeec510cbb0",
+ "id": "798a0f29-9cfd-4e9e-951b-ef9ab1252c2e",
"name": "Selecteer Excel-bijlagen",
"notesInFlow": true,
"onError": "continueErrorOutput",
@@ -24,17 +24,17 @@
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
- 2352,
- 368
+ 2032,
+ 576
],
- "id": "6d4504e4-7b7b-4369-827c-2cee9951b0cf",
+ "id": "d28f2932-ef00-494c-b882-8797e5823abf",
"name": "Normaliseer en valideer orders",
"onError": "continueErrorOutput"
},
{
"parameters": {
"operation": "executeQuery",
- "query": "WITH input AS MATERIALIZED (\n SELECT $1::jsonb AS j\n),\nsrc AS MATERIALIZED (\n SELECT\n j,\n j->'business_payload' AS p,\n md5((j->'business_payload')::text) AS payload_hash\n FROM input\n),\nprevious AS MATERIALIZED (\n SELECT o.*\n FROM public.lidl_fr_orders o\n JOIN src s ON s.p->>'transport_number' = o.transport_number\n FOR UPDATE\n),\nupserted AS (\n INSERT INTO public.lidl_fr_orders AS existing (\n transport_number, current_status, is_cancelled, revision, current_payload_hash,\n pickup_date, pickup_time, pickup_time_till, pickup_name, pickup_address1,\n pickup_zipcode, pickup_city, pickup_phone, pickup_country,\n delivery_date, delivery_date_till, delivery_time, delivery_time_till,\n delivery_name, delivery_address1, delivery_zipcode, delivery_city, delivery_country,\n transport_code, colli, loose_boxes, transport_units, product_group,\n load_carrier_count, load_carrier_type, temperature_group,\n min_temperature, max_temperature, reference_text,\n external_document_number, external_document_branch, forwarder, gross_weight,\n source_file_name, source_message_id, source_subject, source_from,\n source_received_at, raw_row\n )\n SELECT\n p->>'transport_number',\n p->>'status',\n COALESCE((p->>'is_cancelled')::boolean, false),\n 1,\n payload_hash,\n NULLIF(p->>'pickup_date', '')::date,\n NULLIF(p->>'pickup_time', '')::time,\n NULLIF(p->>'pickup_time_till', '')::time,\n p->>'pickup_name',\n p->>'pickup_address1',\n p->>'pickup_zipcode',\n p->>'pickup_city',\n p->>'pickup_phone',\n p->>'pickup_country',\n NULLIF(p->>'delivery_date', '')::date,\n NULLIF(p->>'delivery_date_till', '')::date,\n NULLIF(p->>'delivery_time', '')::time,\n NULLIF(p->>'delivery_time_till', '')::time,\n p->>'delivery_name',\n p->>'delivery_address1',\n p->>'delivery_zipcode',\n p->>'delivery_city',\n p->>'delivery_country',\n p->>'transport_code',\n NULLIF(p->>'colli', '')::numeric,\n NULLIF(p->>'loose_boxes', '')::numeric,\n NULLIF(p->>'transport_units', '')::numeric,\n p->>'product_group',\n NULLIF(p->>'load_carrier_count', '')::numeric,\n p->>'load_carrier_type',\n p->>'temperature_group',\n NULLIF(p->>'min_temperature', '')::numeric,\n NULLIF(p->>'max_temperature', '')::numeric,\n p->>'reference_text',\n p->>'external_document_number',\n p->>'external_document_branch',\n p->>'forwarder',\n NULLIF(p->>'gross_weight', '')::numeric,\n j->>'source_file_name',\n j->>'source_message_id',\n j->>'source_subject',\n j->>'source_from',\n NULLIF(j->>'source_received_at', '')::timestamptz,\n COALESCE(j->'raw_row', '{}'::jsonb)\n FROM src\n ON CONFLICT (transport_number) DO UPDATE SET\n current_status = EXCLUDED.current_status,\n is_cancelled = EXCLUDED.is_cancelled,\n revision = CASE\n WHEN existing.current_payload_hash IS DISTINCT FROM EXCLUDED.current_payload_hash\n THEN existing.revision + 1\n ELSE existing.revision\n END,\n current_payload_hash = EXCLUDED.current_payload_hash,\n pickup_date = EXCLUDED.pickup_date,\n pickup_time = EXCLUDED.pickup_time,\n pickup_time_till = EXCLUDED.pickup_time_till,\n pickup_name = EXCLUDED.pickup_name,\n pickup_address1 = EXCLUDED.pickup_address1,\n pickup_zipcode = EXCLUDED.pickup_zipcode,\n pickup_city = EXCLUDED.pickup_city,\n pickup_phone = EXCLUDED.pickup_phone,\n pickup_country = EXCLUDED.pickup_country,\n delivery_date = EXCLUDED.delivery_date,\n delivery_date_till = EXCLUDED.delivery_date_till,\n delivery_time = EXCLUDED.delivery_time,\n delivery_time_till = EXCLUDED.delivery_time_till,\n delivery_name = EXCLUDED.delivery_name,\n delivery_address1 = EXCLUDED.delivery_address1,\n delivery_zipcode = EXCLUDED.delivery_zipcode,\n delivery_city = EXCLUDED.delivery_city,\n delivery_country = EXCLUDED.delivery_country,\n transport_code = EXCLUDED.transport_code,\n colli = EXCLUDED.colli,\n loose_boxes = EXCLUDED.loose_boxes,\n transport_units = EXCLUDED.transport_units,\n product_group = EXCLUDED.product_group,\n load_carrier_count = EXCLUDED.load_carrier_count,\n load_carrier_type = EXCLUDED.load_carrier_type,\n temperature_group = EXCLUDED.temperature_group,\n min_temperature = EXCLUDED.min_temperature,\n max_temperature = EXCLUDED.max_temperature,\n reference_text = EXCLUDED.reference_text,\n external_document_number = EXCLUDED.external_document_number,\n external_document_branch = EXCLUDED.external_document_branch,\n forwarder = EXCLUDED.forwarder,\n gross_weight = EXCLUDED.gross_weight,\n source_file_name = EXCLUDED.source_file_name,\n source_message_id = EXCLUDED.source_message_id,\n source_subject = EXCLUDED.source_subject,\n source_from = EXCLUDED.source_from,\n source_received_at = EXCLUDED.source_received_at,\n raw_row = EXCLUDED.raw_row,\n last_seen_at = now(),\n status_changed_at = CASE\n WHEN existing.current_status IS DISTINCT FROM EXCLUDED.current_status\n OR existing.is_cancelled IS DISTINCT FROM EXCLUDED.is_cancelled\n THEN now()\n ELSE existing.status_changed_at\n END,\n reactivated_at = CASE\n WHEN existing.is_cancelled = true AND EXCLUDED.is_cancelled = false\n THEN now()\n ELSE existing.reactivated_at\n END,\n last_error = CASE\n WHEN existing.current_payload_hash IS DISTINCT FROM EXCLUDED.current_payload_hash\n THEN NULL\n ELSE existing.last_error\n END\n RETURNING *\n),\nclassified AS MATERIALIZED (\n SELECT\n u.*,\n CASE\n WHEN p.id IS NULL THEN 'created'\n WHEN p.is_cancelled = false AND u.is_cancelled = true THEN 'cancelled'\n WHEN p.is_cancelled = true AND u.is_cancelled = false THEN 'reactivated'\n WHEN p.current_payload_hash IS DISTINCT FROM u.current_payload_hash THEN 'updated'\n ELSE 'unchanged'\n END AS event_type,\n (p.id IS NULL OR p.current_payload_hash IS DISTINCT FROM u.current_payload_hash) AS state_changed\n FROM upserted u\n LEFT JOIN previous p ON p.id = u.id\n),\nevent_insert AS (\n INSERT INTO public.lidl_fr_order_events (\n order_id, transport_number, revision, event_type, status,\n is_cancelled, payload_hash, source_file_name, source_message_id, row_data\n )\n SELECT\n c.id, c.transport_number, c.revision, c.event_type, c.current_status,\n c.is_cancelled, c.current_payload_hash, c.source_file_name,\n c.source_message_id, c.raw_row\n FROM classified c\n WHERE c.state_changed\n ON CONFLICT (transport_number, revision) DO NOTHING\n RETURNING id\n),\nlatest_document AS MATERIALIZED (\n SELECT d.*\n FROM public.lidl_fr_documents d\n JOIN classified c ON c.transport_number = d.transport_number\n WHERE d.document_kind = 'PAKBON'\n AND d.active = true\n AND d.storage_status = 'stored'\n AND d.file_path IS NOT NULL\n ORDER BY d.received_at DESC, d.id DESC\n LIMIT 1\n)\nSELECT\n c.id AS order_id,\n c.transport_number,\n c.current_status,\n c.is_cancelled,\n c.revision,\n c.current_payload_hash AS payload_hash,\n c.event_type,\n (c.revision > COALESCE(c.last_transpas_export_revision, 0)) AS should_export,\n CASE\n WHEN c.is_cancelled THEN 'DELETE'\n WHEN c.has_active_export = false THEN 'NEW'\n ELSE 'UPDATE'\n END AS transpas_action,\n\n c.pickup_date,\n to_char(c.pickup_time, 'HH24:MI') AS pickup_time,\n to_char(c.pickup_time_till, 'HH24:MI') AS pickup_time_till,\n c.pickup_name,\n c.pickup_address1,\n c.pickup_zipcode,\n c.pickup_city,\n c.pickup_phone,\n c.pickup_country,\n\n c.delivery_date,\n c.delivery_date_till,\n to_char(c.delivery_time, 'HH24:MI') AS delivery_time,\n to_char(c.delivery_time_till, 'HH24:MI') AS delivery_time_till,\n c.delivery_name,\n c.delivery_address1,\n c.delivery_zipcode,\n c.delivery_city,\n c.delivery_country,\n\n c.transport_code,\n c.colli,\n c.loose_boxes,\n c.transport_units,\n c.product_group,\n c.load_carrier_count,\n c.load_carrier_type,\n c.temperature_group,\n c.min_temperature,\n c.max_temperature,\n c.reference_text,\n c.external_document_number,\n c.external_document_branch,\n c.forwarder,\n c.gross_weight,\n c.source_file_name,\n\n d.id AS pakbon_id,\n d.file_name AS pakbon_file_name,\n d.stored_file_name AS pakbon_stored_file_name,\n d.file_path AS pakbon_file_path,\n d.file_type AS pakbon_file_type,\n d.mime_type AS pakbon_mime_type,\n d.document_hash AS pakbon_document_hash\nFROM classified c\nLEFT JOIN latest_document d ON true;",
+ "query": "WITH input AS MATERIALIZED (\n SELECT $1::jsonb AS j\n),\nsrc AS MATERIALIZED (\n SELECT\n j,\n j->'business_payload' AS p,\n md5((j->'business_payload')::text) AS payload_hash\n FROM input\n),\nprevious AS MATERIALIZED (\n SELECT o.*\n FROM public.lidl_fr_orders o\n JOIN src s ON s.p->>'transport_number' = o.transport_number\n FOR UPDATE\n),\nupserted AS (\n INSERT INTO public.lidl_fr_orders AS existing (\n transport_number, current_status, is_cancelled, revision, current_payload_hash,\n pickup_date, pickup_time, pickup_time_till, pickup_name, pickup_address1,\n pickup_zipcode, pickup_city, pickup_phone, pickup_country,\n delivery_date, delivery_date_till, delivery_time, delivery_time_till,\n delivery_name, delivery_address1, delivery_zipcode, delivery_city, delivery_country,\n transport_code, colli, loose_boxes, transport_units, product_group,\n load_carrier_count, load_carrier_type, temperature_group,\n min_temperature, max_temperature, reference_text,\n external_document_number, external_document_branch, forwarder, gross_weight,\n source_file_name, source_message_id, source_subject, source_from,\n source_received_at, raw_row\n )\n SELECT\n p->>'transport_number',\n p->>'status',\n COALESCE((p->>'is_cancelled')::boolean, false),\n 1,\n payload_hash,\n NULLIF(p->>'pickup_date', '')::date,\n NULLIF(p->>'pickup_time', '')::time,\n NULLIF(p->>'pickup_time_till', '')::time,\n p->>'pickup_name',\n p->>'pickup_address1',\n p->>'pickup_zipcode',\n p->>'pickup_city',\n p->>'pickup_phone',\n p->>'pickup_country',\n NULLIF(p->>'delivery_date', '')::date,\n NULLIF(p->>'delivery_date_till', '')::date,\n NULLIF(p->>'delivery_time', '')::time,\n NULLIF(p->>'delivery_time_till', '')::time,\n p->>'delivery_name',\n p->>'delivery_address1',\n p->>'delivery_zipcode',\n p->>'delivery_city',\n p->>'delivery_country',\n p->>'transport_code',\n NULLIF(p->>'colli', '')::numeric,\n NULLIF(p->>'loose_boxes', '')::numeric,\n NULLIF(p->>'transport_units', '')::numeric,\n p->>'product_group',\n NULLIF(p->>'load_carrier_count', '')::numeric,\n p->>'load_carrier_type',\n p->>'temperature_group',\n NULLIF(p->>'min_temperature', '')::numeric,\n NULLIF(p->>'max_temperature', '')::numeric,\n p->>'reference_text',\n p->>'external_document_number',\n p->>'external_document_branch',\n p->>'forwarder',\n NULLIF(p->>'gross_weight', '')::numeric,\n j->>'source_file_name',\n j->>'source_message_id',\n j->>'source_subject',\n j->>'source_from',\n NULLIF(j->>'source_received_at', '')::timestamptz,\n COALESCE(j->'raw_row', '{}'::jsonb)\n FROM src\n ON CONFLICT (transport_number) DO UPDATE SET\n current_status = EXCLUDED.current_status,\n is_cancelled = EXCLUDED.is_cancelled,\n revision = CASE\n WHEN existing.current_payload_hash IS DISTINCT FROM EXCLUDED.current_payload_hash\n THEN existing.revision + 1\n ELSE existing.revision\n END,\n current_payload_hash = EXCLUDED.current_payload_hash,\n pickup_date = EXCLUDED.pickup_date,\n pickup_time = EXCLUDED.pickup_time,\n pickup_time_till = EXCLUDED.pickup_time_till,\n pickup_name = EXCLUDED.pickup_name,\n pickup_address1 = EXCLUDED.pickup_address1,\n pickup_zipcode = EXCLUDED.pickup_zipcode,\n pickup_city = EXCLUDED.pickup_city,\n pickup_phone = EXCLUDED.pickup_phone,\n pickup_country = EXCLUDED.pickup_country,\n delivery_date = EXCLUDED.delivery_date,\n delivery_date_till = EXCLUDED.delivery_date_till,\n delivery_time = EXCLUDED.delivery_time,\n delivery_time_till = EXCLUDED.delivery_time_till,\n delivery_name = EXCLUDED.delivery_name,\n delivery_address1 = EXCLUDED.delivery_address1,\n delivery_zipcode = EXCLUDED.delivery_zipcode,\n delivery_city = EXCLUDED.delivery_city,\n delivery_country = EXCLUDED.delivery_country,\n transport_code = EXCLUDED.transport_code,\n colli = EXCLUDED.colli,\n loose_boxes = EXCLUDED.loose_boxes,\n transport_units = EXCLUDED.transport_units,\n product_group = EXCLUDED.product_group,\n load_carrier_count = EXCLUDED.load_carrier_count,\n load_carrier_type = EXCLUDED.load_carrier_type,\n temperature_group = EXCLUDED.temperature_group,\n min_temperature = EXCLUDED.min_temperature,\n max_temperature = EXCLUDED.max_temperature,\n reference_text = EXCLUDED.reference_text,\n external_document_number = EXCLUDED.external_document_number,\n external_document_branch = EXCLUDED.external_document_branch,\n forwarder = EXCLUDED.forwarder,\n gross_weight = EXCLUDED.gross_weight,\n source_file_name = EXCLUDED.source_file_name,\n source_message_id = EXCLUDED.source_message_id,\n source_subject = EXCLUDED.source_subject,\n source_from = EXCLUDED.source_from,\n source_received_at = EXCLUDED.source_received_at,\n raw_row = EXCLUDED.raw_row,\n last_seen_at = now(),\n status_changed_at = CASE\n WHEN existing.current_status IS DISTINCT FROM EXCLUDED.current_status\n OR existing.is_cancelled IS DISTINCT FROM EXCLUDED.is_cancelled\n THEN now()\n ELSE existing.status_changed_at\n END,\n reactivated_at = CASE\n WHEN existing.is_cancelled = true AND EXCLUDED.is_cancelled = false\n THEN now()\n ELSE existing.reactivated_at\n END,\n last_error = CASE\n WHEN existing.current_payload_hash IS DISTINCT FROM EXCLUDED.current_payload_hash\n THEN NULL\n ELSE existing.last_error\n END\n RETURNING *\n),\nclassified AS MATERIALIZED (\n SELECT\n u.*,\n CASE\n WHEN p.id IS NULL THEN 'created'\n WHEN p.is_cancelled = false AND u.is_cancelled = true THEN 'cancelled'\n WHEN p.is_cancelled = true AND u.is_cancelled = false THEN 'reactivated'\n WHEN p.current_payload_hash IS DISTINCT FROM u.current_payload_hash THEN 'updated'\n ELSE 'unchanged'\n END AS event_type,\n (p.id IS NULL OR p.current_payload_hash IS DISTINCT FROM u.current_payload_hash) AS state_changed\n FROM upserted u\n LEFT JOIN previous p ON p.id = u.id\n),\nevent_insert AS (\n INSERT INTO public.lidl_fr_order_events (\n order_id, transport_number, revision, event_type, status,\n is_cancelled, payload_hash, source_file_name, source_message_id, row_data\n )\n SELECT\n c.id, c.transport_number, c.revision, c.event_type, c.current_status,\n c.is_cancelled, c.current_payload_hash, c.source_file_name,\n c.source_message_id, c.raw_row\n FROM classified c\n WHERE c.state_changed\n ON CONFLICT (transport_number, revision) DO NOTHING\n RETURNING id\n)\nSELECT\n c.id AS order_id,\n c.transport_number,\n c.current_status,\n c.is_cancelled,\n c.revision,\n c.current_payload_hash AS payload_hash,\n c.event_type,\n (c.revision > COALESCE(c.last_transpas_export_revision, 0)) AS should_export,\n CASE\n WHEN c.is_cancelled THEN 'DELETE'\n WHEN c.has_active_export = false THEN 'NEW'\n ELSE 'UPDATE'\n END AS transpas_action,\n\n c.pickup_date,\n to_char(c.pickup_time, 'HH24:MI') AS pickup_time,\n to_char(c.pickup_time_till, 'HH24:MI') AS pickup_time_till,\n c.pickup_name,\n c.pickup_address1,\n c.pickup_zipcode,\n c.pickup_city,\n c.pickup_phone,\n c.pickup_country,\n\n c.delivery_date,\n c.delivery_date_till,\n to_char(c.delivery_time, 'HH24:MI') AS delivery_time,\n to_char(c.delivery_time_till, 'HH24:MI') AS delivery_time_till,\n c.delivery_name,\n c.delivery_address1,\n c.delivery_zipcode,\n c.delivery_city,\n c.delivery_country,\n\n c.transport_code,\n c.colli,\n c.loose_boxes,\n c.transport_units,\n c.product_group,\n c.load_carrier_count,\n c.load_carrier_type,\n c.temperature_group,\n c.min_temperature,\n c.max_temperature,\n c.reference_text,\n c.external_document_number,\n c.external_document_branch,\n c.forwarder,\n c.gross_weight,\n c.source_file_name,\n\n d.id AS pakbon_id,\n d.file_name AS pakbon_file_name,\n d.stored_file_name AS pakbon_stored_file_name,\n d.file_path AS pakbon_file_path,\n d.file_type AS pakbon_file_type,\n d.mime_type AS pakbon_mime_type,\n d.document_hash AS pakbon_document_hash\nFROM classified c\nLEFT JOIN LATERAL (\n SELECT doc.*\n FROM public.lidl_fr_documents doc\n WHERE doc.transport_number = c.transport_number\n AND doc.document_kind = 'PAKBON'\n AND doc.active = true\n AND doc.storage_status = 'stored'\n AND doc.file_path IS NOT NULL\n ORDER BY doc.received_at DESC, doc.id DESC\n LIMIT 1\n) d ON true;",
"options": {
"queryReplacement": "={{ [JSON.stringify($json)] }}"
}
@@ -42,18 +42,20 @@
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
- 2672,
- 352
+ 2256,
+ 576
],
- "id": "8ef1a7e3-819c-46c5-b04c-cc1db01de2d7",
+ "id": "65f00226-090b-45a2-a401-493fd409474c",
"name": "Upsert order status en zoek pakbon",
+ "notesInFlow": true,
"credentials": {
"postgres": {
"id": "m4DQjg3b1iDYGlBp",
"name": "Postgres Flower direct"
}
},
- "onError": "continueErrorOutput"
+ "onError": "continueErrorOutput",
+ "notes": "Documentselectie is per transportnummer gecorreleerd met LEFT JOIN LATERAL. Hierdoor kan een document van een andere order niet meer als nieuwste document voor alle orders worden hergebruikt."
},
{
"parameters": {
@@ -83,27 +85,28 @@
"type": "n8n-nodes-base.filter",
"typeVersion": 2.2,
"position": [
- 2960,
- 320
+ 2704,
+ 240
],
- "id": "fa659a1a-13b4-4897-b2e6-e637da769216",
- "name": "Alleen nieuwe of gewijzigde revisies",
- "disabled": true
+ "id": "fc63eb5b-448a-4d40-a6c4-9b21a516c261",
+ "name": "Alleen nieuwe of gewijzigde revisies"
},
{
"parameters": {
"mode": "runOnceForEachItem",
- "jsCode": "// Per item uitvoeren.\n// De ordergegevens komen rechtstreeks uit PostgreSQL of via de gekoppelde item-link\n// wanneer het pakbonbestand eerst van de NAS is gelezen.\n\nconst CFG = {\n customerId: '60971',\n departmentId: '',\n shipmentKindId: '',\n ediproviderId: '',\n companyId: '1',\n unitMatchmode: '1',\n documentTypeId: 'PAKBON',\n documentTypeMatchmode: '1',\n ftpFolder: '/test/ToTP/TPE',\n\n deliveryDriverInfo: [\n 'Transport flowers at +4°C to +6°C – Transport plants at +15°C –',\n 'When transporting a mixed load of flowers and plants, set the temperature to +12°C.',\n 'Exchange the pallets at the unloading location using PAKi eVoucher number 048849 04-CAMBRAI',\n ].join(' '),\n};\n\nconst linkedOrder = $('Upsert order status en zoek pakbon').item.json;\nconst j = $json?.order_id ? $json : linkedOrder;\nconst inputItem = $input.item;\n\nfunction xmlEsc(value) {\n return String(value ?? '')\n .replace(\n /&(?!amp;|lt;|gt;|quot;|apos;|#\\d+;|#x[0-9A-Fa-f]+;)/g,\n '&',\n )\n .replace(//g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\nfunction tag(name, value, attrs = '') {\n if (\n value === undefined ||\n value === null ||\n String(value).trim() === ''\n ) {\n return '';\n }\n\n const attrText = attrs ? ` ${attrs}` : '';\n return `<${name}${attrText}>${xmlEsc(value)}${name}>`;\n}\n\nfunction emptyTag(name, attrs = '') {\n const attrText = attrs ? ` ${attrs}` : '';\n return `<${name}${attrText} />`;\n}\n\nfunction safeFilePart(value) {\n return String(value ?? '')\n .replace(/[^a-zA-Z0-9._-]+/g, '_')\n .replace(/^_+|_+$/g, '')\n .slice(0, 100) || 'order';\n}\n\nfunction block(name, lines, indent = ' ') {\n const present = lines.filter(Boolean);\n\n if (!present.length) {\n return '';\n }\n\n return [\n `${indent}<${name}>`,\n ...present.map(line => `${indent} ${line}`),\n `${indent}${name}>`,\n ].join('\\n');\n}\n\n/**\n * Zet een PostgreSQL-/ISO-datum om naar de lokale kalenderdatum\n * in Nederland.\n *\n * Voorbeeld:\n * 2026-08-02T22:00:00.000Z -> 2026-08-03\n */\nfunction formatDate(value) {\n if (value === undefined || value === null || value === '') {\n return null;\n }\n\n const text = String(value).trim();\n\n // Een datum zonder tijd kan direct worden gebruikt.\n const dateOnly = text.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n\n if (dateOnly) {\n return `${dateOnly[1]}-${dateOnly[2]}-${dateOnly[3]}`;\n }\n\n const date = new Date(text);\n\n if (Number.isNaN(date.getTime())) {\n throw new Error(`Ongeldige datum ontvangen: ${text}`);\n }\n\n const parts = new Intl.DateTimeFormat('en-CA', {\n timeZone: 'Europe/Amsterdam',\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n }).formatToParts(date);\n\n const result = Object.fromEntries(\n parts\n .filter(part => part.type !== 'literal')\n .map(part => [part.type, part.value]),\n );\n\n return `${result.year}-${result.month}-${result.day}`;\n}\n\nconst pickupDate = formatDate(j.pickup_date);\nconst deliveryDate = formatDate(j.delivery_date);\nconst deliveryDateTill = formatDate(\n j.delivery_date_till || j.delivery_date,\n);\n\nlet pakbonBase64 = '';\n\nif (!j.is_cancelled && inputItem.binary?.pakbon) {\n const buffer = await this.helpers.getBinaryDataBuffer(0, 'pakbon');\n pakbonBase64 = buffer.toString('base64');\n}\n\nconst rootOptional = [\n tag('ediprovider_id', CFG.ediproviderId),\n tag('company_id', CFG.companyId),\n].filter(Boolean);\n\nconst bookingOptional = [\n tag('department_id', CFG.departmentId),\n].filter(Boolean);\n\nconst shipmentOptional = [\n tag(\n 'shipmentkind_id',\n CFG.shipmentKindId,\n 'matchmode=\"1\"',\n ),\n].filter(Boolean);\n\nconst pickup = block(\n 'pickupaddress',\n [\n emptyTag('address_id', 'matchmode=\"11\"'),\n tag('date', pickupDate),\n tag('datetill', pickupDate),\n tag('time', j.pickup_time),\n tag('timetill', j.pickup_time_till),\n tag('name', j.pickup_name),\n tag('address1', j.pickup_address1),\n tag('zipcode', j.pickup_zipcode),\n tag('city_id', j.pickup_city, 'matchmode=\"4\"'),\n tag('country_id', j.pickup_country, 'matchmode=\"2\"'),\n tag('phone', j.pickup_phone),\n\n // Transportnummer is de laadreferentie.\n tag('reference', j.transport_number),\n\n // De naam/code van het afleveradres komt als chauffeursinformatie\n // bij het laadadres.\n tag('driverinfo', j.delivery_name),\n ],\n ' ',\n);\n\nconst delivery = block(\n 'deliveryaddress',\n [\n emptyTag('address_id', 'matchmode=\"11\"'),\n tag('date', deliveryDate),\n tag('datetill', deliveryDateTill),\n tag('time', j.delivery_time),\n tag('timetill', j.delivery_time_till),\n tag('name', j.delivery_name),\n tag('address1', j.delivery_address1),\n tag('zipcode', j.delivery_zipcode),\n tag('city_id', j.delivery_city, 'matchmode=\"4\"'),\n tag('country_id', j.delivery_country, 'matchmode=\"2\"'),\n\n // Transportnummer is ook de losreferentie.\n tag('reference', j.transport_number),\n\n // Vaste Lidl-instructie bij het losadres.\n tag('driverinfo', CFG.deliveryDriverInfo),\n ],\n ' ',\n);\n\nconst sender = block(\n 'sender',\n [\n tag('name', j.pickup_name),\n tag('address1', j.pickup_address1),\n tag('zipcode', j.pickup_zipcode),\n tag('city_id', j.pickup_city, 'matchmode=\"4\"'),\n tag('country_id', j.pickup_country, 'matchmode=\"2\"'),\n tag('phone', j.pickup_phone),\n ],\n ' ',\n);\n\nconst receiver = block(\n 'receiver',\n [\n tag('name', j.delivery_name),\n tag('address1', j.delivery_address1),\n tag('zipcode', j.delivery_zipcode),\n tag('city_id', j.delivery_city, 'matchmode=\"4\"'),\n tag('country_id', j.delivery_country, 'matchmode=\"2\"'),\n ],\n ' ',\n);\n\nconst cargo = block(\n 'cargo',\n [\n tag('colli', j.colli),\n tag(\n 'unitamount',\n j.load_carrier_count ?? j.transport_units,\n ),\n tag(\n 'unit_id',\n j.load_carrier_type,\n `matchmode=\"${CFG.unitMatchmode}\"`,\n ),\n tag('product_id', j.product_group, 'matchmode=\"1\"'),\n tag('productdescription', j.product_group),\n tag('weight', j.gross_weight),\n tag('mintemperature', j.min_temperature),\n tag('maxtemperature', j.max_temperature),\n ],\n ' ',\n);\n\nconst includePakbon =\n !j.is_cancelled &&\n Boolean(pakbonBase64) &&\n Boolean(j.pakbon_file_name);\n\nlet documents = '';\n\nif (includePakbon) {\n documents = [\n ' ',\n ' ',\n ` ${tag(\n 'documenttype_id',\n CFG.documentTypeId,\n `matchmode=\"${CFG.documentTypeMatchmode}\"`,\n )}`,\n ` ${tag('filename', j.pakbon_file_name)}`,\n ` ${tag('filedata', pakbonBase64)}`,\n ` ${tag('reference', j.transport_number)}`,\n ` ${tag('concerns', 'Pakbon')}`,\n ' ',\n ' ',\n ].join('\\n');\n}\n\nconst xmlLines = [\n '',\n '',\n ' ',\n\n ...rootOptional.map(value => ` ${value}`),\n\n ` ${tag(\n 'ediproviderstatus_id',\n j.transpas_action,\n 'matchmode=\"1\"',\n )}`,\n\n ` ${tag('edireference', j.transport_number)}`,\n ` ${tag('reference', j.transport_number)}`,\n ` ${tag(\n 'customer_id',\n CFG.customerId,\n 'matchmode=\"1\"',\n )}`,\n\n ...bookingOptional.map(value => ` ${value}`),\n\n ' ',\n ' ',\n\n ` ${tag(\n 'plangroup_id',\n '17',\n 'matchmode=\"0\"',\n )}`,\n\n ` ${tag(\n 'edireference',\n j.transport_number,\n )}`,\n\n ` ${tag(\n 'reference',\n j.transport_number,\n )}`,\n\n ` ${tag(\n 'cancellation',\n j.is_cancelled ? 'true' : 'false',\n )}`,\n\n ...shipmentOptional.map(value => ` ${value}`),\n\n sender,\n receiver,\n pickup,\n delivery,\n cargo,\n documents,\n\n ' ',\n ' ',\n ' ',\n '',\n].filter(Boolean);\n\nconst xml = xmlLines.join('\\n');\n\nconst stamp = $now\n .setZone('Europe/Amsterdam')\n .toFormat('yyyyLLdd_HHmmss_SSS');\n\nconst xmlFileName =\n `${stamp}_` +\n `${safeFilePart(j.transport_number)}_` +\n `R${j.revision}_` +\n `${j.transpas_action}.xml`;\n\nconst ftpFolder = String(CFG.ftpFolder || '').replace(/\\/+$/, '');\n\nreturn {\n json: {\n order_id: j.order_id,\n transport_number: j.transport_number,\n revision: j.revision,\n payload_hash: j.payload_hash,\n transpas_action: j.transpas_action,\n is_cancelled: j.is_cancelled,\n\n pickup_date: pickupDate,\n delivery_date: deliveryDate,\n delivery_date_till: deliveryDateTill,\n\n pakbon_id: j.pakbon_id ?? null,\n pakbon_file_path: j.pakbon_file_path ?? null,\n document_included: includePakbon,\n\n xml_file_name: xmlFileName,\n ftp_path: `${ftpFolder}/${xmlFileName}`,\n xml,\n },\n};"
+ "jsCode": "// Per item uitvoeren.\n// Deze node ontvangt binnen de loop exact één order.\n// Bij een order met pakbon is de order-JSON door de Merge-node gecombineerd\n// met binary.pakbon. Bij een order zonder pakbon komt de order direct binnen.\n\nconst CFG = {\n customerId: '60971',\n departmentId: '',\n shipmentKindId: '',\n ediproviderId: '',\n companyId: '1',\n unitMatchmode: '1',\n documentTypeId: 'PAKBON',\n documentTypeMatchmode: '1',\n ftpFolder: '/test/ToTP/TPE',\n\n deliveryDriverInfo: [\n 'Transport flowers at +4°C to +6°C – Transport plants at +15°C –',\n 'When transporting a mixed load of flowers and plants, set the temperature to +12°C.',\n 'Exchange the pallets at the unloading location using PAKi eVoucher number 048849 04-CAMBRAI',\n ].join(' '),\n};\n\nconst inputItem = $input.item;\nconst j = inputItem.json ?? {};\n\nif (!j.order_id) {\n throw new Error('Order-ID ontbreekt in de huidige loop-iteratie.');\n}\n\nif (!j.transport_number) {\n throw new Error(`Transportnummer ontbreekt voor order ${j.order_id}.`);\n}\n\nif (\n j.pakbon_id &&\n !j.is_cancelled &&\n !String(j.pakbon_file_path ?? '').trim()\n) {\n throw new Error(\n `Pakbonpad ontbreekt voor transportnummer ${j.transport_number}.`,\n );\n}\n\nfunction xmlEsc(value) {\n return String(value ?? '')\n .replace(\n /&(?!amp;|lt;|gt;|quot;|apos;|#\\d+;|#x[0-9A-Fa-f]+;)/g,\n '&',\n )\n .replace(//g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\nfunction tag(name, value, attrs = '') {\n if (\n value === undefined ||\n value === null ||\n String(value).trim() === ''\n ) {\n return '';\n }\n\n const attrText = attrs ? ` ${attrs}` : '';\n return `<${name}${attrText}>${xmlEsc(value)}${name}>`;\n}\n\nfunction emptyTag(name, attrs = '') {\n const attrText = attrs ? ` ${attrs}` : '';\n return `<${name}${attrText} />`;\n}\n\nfunction safeFilePart(value) {\n return String(value ?? '')\n .replace(/[^a-zA-Z0-9._-]+/g, '_')\n .replace(/^_+|_+$/g, '')\n .slice(0, 100) || 'order';\n}\n\nfunction block(name, lines, indent = ' ') {\n const present = lines.filter(Boolean);\n\n if (!present.length) {\n return '';\n }\n\n return [\n `${indent}<${name}>`,\n ...present.map(line => `${indent} ${line}`),\n `${indent}${name}>`,\n ].join('\\n');\n}\n\n/**\n * Zet een PostgreSQL-/ISO-datum om naar de lokale kalenderdatum\n * in Nederland.\n *\n * Voorbeeld:\n * 2026-08-02T22:00:00.000Z -> 2026-08-03\n */\nfunction formatDate(value) {\n if (value === undefined || value === null || value === '') {\n return null;\n }\n\n const text = String(value).trim();\n\n // Een datum zonder tijd kan direct worden gebruikt.\n const dateOnly = text.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n\n if (dateOnly) {\n return `${dateOnly[1]}-${dateOnly[2]}-${dateOnly[3]}`;\n }\n\n const date = new Date(text);\n\n if (Number.isNaN(date.getTime())) {\n throw new Error(`Ongeldige datum ontvangen: ${text}`);\n }\n\n const parts = new Intl.DateTimeFormat('en-CA', {\n timeZone: 'Europe/Amsterdam',\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n }).formatToParts(date);\n\n const result = Object.fromEntries(\n parts\n .filter(part => part.type !== 'literal')\n .map(part => [part.type, part.value]),\n );\n\n return `${result.year}-${result.month}-${result.day}`;\n}\n\nconst pickupDate = formatDate(j.pickup_date);\nconst deliveryDate = formatDate(j.delivery_date);\nconst deliveryDateTill = formatDate(\n j.delivery_date_till || j.delivery_date,\n);\n\nlet pakbonBase64 = '';\n\nif (!j.is_cancelled && inputItem.binary?.pakbon) {\n const buffer = await this.helpers.getBinaryDataBuffer(0, 'pakbon');\n pakbonBase64 = buffer.toString('base64');\n}\n\nconst rootOptional = [\n tag('ediprovider_id', CFG.ediproviderId),\n tag('company_id', CFG.companyId),\n].filter(Boolean);\n\nconst bookingOptional = [\n tag('department_id', CFG.departmentId),\n].filter(Boolean);\n\nconst shipmentOptional = [\n tag(\n 'shipmentkind_id',\n CFG.shipmentKindId,\n 'matchmode=\"1\"',\n ),\n].filter(Boolean);\n\nconst pickup = block(\n 'pickupaddress',\n [\n emptyTag('address_id', 'matchmode=\"11\"'),\n tag('date', pickupDate),\n tag('datetill', pickupDate),\n tag('time', j.pickup_time),\n tag('timetill', j.pickup_time_till),\n tag('name', j.pickup_name),\n tag('address1', j.pickup_address1),\n tag('zipcode', j.pickup_zipcode),\n tag('city_id', j.pickup_city, 'matchmode=\"4\"'),\n tag('country_id', j.pickup_country, 'matchmode=\"2\"'),\n tag('phone', j.pickup_phone),\n\n // Transportnummer is de laadreferentie.\n tag('reference', j.transport_number),\n\n // De naam/code van het afleveradres komt als chauffeursinformatie\n // bij het laadadres.\n tag('driverinfo', j.delivery_name),\n ],\n ' ',\n);\n\nconst delivery = block(\n 'deliveryaddress',\n [\n emptyTag('address_id', 'matchmode=\"11\"'),\n tag('date', deliveryDate),\n tag('datetill', deliveryDateTill),\n tag('time', j.delivery_time),\n tag('timetill', j.delivery_time_till),\n tag('name', j.delivery_name),\n tag('address1', j.delivery_address1),\n tag('zipcode', j.delivery_zipcode),\n tag('city_id', j.delivery_city, 'matchmode=\"4\"'),\n tag('country_id', j.delivery_country, 'matchmode=\"2\"'),\n\n // Transportnummer is ook de losreferentie.\n tag('reference', j.transport_number),\n\n // Vaste Lidl-instructie bij het losadres.\n tag('driverinfo', CFG.deliveryDriverInfo),\n ],\n ' ',\n);\n\nconst sender = block(\n 'sender',\n [\n tag('name', j.pickup_name),\n tag('address1', j.pickup_address1),\n tag('zipcode', j.pickup_zipcode),\n tag('city_id', j.pickup_city, 'matchmode=\"4\"'),\n tag('country_id', j.pickup_country, 'matchmode=\"2\"'),\n tag('phone', j.pickup_phone),\n ],\n ' ',\n);\n\nconst receiver = block(\n 'receiver',\n [\n tag('name', j.delivery_name),\n tag('address1', j.delivery_address1),\n tag('zipcode', j.delivery_zipcode),\n tag('city_id', j.delivery_city, 'matchmode=\"4\"'),\n tag('country_id', j.delivery_country, 'matchmode=\"2\"'),\n ],\n ' ',\n);\n\nconst cargo = block(\n 'cargo',\n [\n tag('colli', j.colli),\n tag(\n 'unitamount',\n j.load_carrier_count ?? j.transport_units,\n ),\n tag(\n 'unit_id',\n j.load_carrier_type,\n `matchmode=\"${CFG.unitMatchmode}\"`,\n ),\n tag('product_id', j.product_group, 'matchmode=\"1\"'),\n tag('productdescription', j.product_group),\n tag('weight', j.gross_weight),\n tag('mintemperature', j.min_temperature),\n tag('maxtemperature', j.max_temperature),\n ],\n ' ',\n);\n\nconst includePakbon =\n !j.is_cancelled &&\n Boolean(pakbonBase64) &&\n Boolean(j.pakbon_file_name);\n\nlet documents = '';\n\nif (includePakbon) {\n documents = [\n ' ',\n ' ',\n ` ${tag(\n 'documenttype_id',\n CFG.documentTypeId,\n `matchmode=\"${CFG.documentTypeMatchmode}\"`,\n )}`,\n ` ${tag('filename', j.pakbon_file_name)}`,\n ` ${tag('filedata', pakbonBase64)}`,\n ` ${tag('reference', j.transport_number)}`,\n ` ${tag('concerns', 'Pakbon')}`,\n ' ',\n ' ',\n ].join('\\n');\n}\n\nconst xmlLines = [\n '',\n '',\n ' ',\n\n ...rootOptional.map(value => ` ${value}`),\n\n ` ${tag(\n 'ediproviderstatus_id',\n j.transpas_action,\n 'matchmode=\"1\"',\n )}`,\n\n ` ${tag('edireference', j.transport_number)}`,\n ` ${tag('reference', j.transport_number)}`,\n ` ${tag(\n 'customer_id',\n CFG.customerId,\n 'matchmode=\"1\"',\n )}`,\n\n ...bookingOptional.map(value => ` ${value}`),\n\n ' ',\n ' ',\n\n ` ${tag(\n 'plangroup_id',\n '17',\n 'matchmode=\"0\"',\n )}`,\n\n ` ${tag(\n 'edireference',\n j.transport_number,\n )}`,\n\n ` ${tag(\n 'reference',\n j.transport_number,\n )}`,\n\n ` ${tag(\n 'cancellation',\n j.is_cancelled ? 'true' : 'false',\n )}`,\n\n ...shipmentOptional.map(value => ` ${value}`),\n\n sender,\n receiver,\n pickup,\n delivery,\n cargo,\n documents,\n\n ' ',\n ' ',\n ' ',\n '',\n].filter(Boolean);\n\nconst xml = xmlLines.join('\\n');\n\nconst stamp = $now\n .setZone('Europe/Amsterdam')\n .toFormat('yyyyLLdd_HHmmss_SSS');\n\nconst xmlFileName =\n `${stamp}_` +\n `${safeFilePart(j.transport_number)}_` +\n `R${j.revision}_` +\n `${j.transpas_action}.xml`;\n\nconst ftpFolder = String(CFG.ftpFolder || '').replace(/\\/+$/, '');\n\nreturn {\n json: {\n order_id: j.order_id,\n transport_number: j.transport_number,\n revision: j.revision,\n payload_hash: j.payload_hash,\n transpas_action: j.transpas_action,\n is_cancelled: j.is_cancelled,\n\n pickup_date: pickupDate,\n delivery_date: deliveryDate,\n delivery_date_till: deliveryDateTill,\n\n pakbon_id: j.pakbon_id ?? null,\n pakbon_file_path: j.pakbon_file_path ?? null,\n document_included: includePakbon,\n\n xml_file_name: xmlFileName,\n ftp_path: `${ftpFolder}/${xmlFileName}`,\n xml,\n },\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
- 3632,
- 320
+ 3600,
+ 240
],
- "id": "13565bdb-edfc-4501-b165-741a6c86ddd6",
+ "id": "35b1334e-b9fc-47d0-8e0d-d0b15006ac43",
"name": "Bouw Transpas TPE XML",
- "onError": "continueErrorOutput"
+ "notesInFlow": true,
+ "onError": "continueErrorOutput",
+ "notes": "Ontvangt per iteratie één order. Bij een pakbon bevat hetzelfde item zowel de juiste order-JSON als binary.pakbon."
},
{
"parameters": {
@@ -116,10 +119,10 @@
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
- 3872,
- 304
+ 3824,
+ 240
],
- "id": "0ce5c72a-9b85-477d-a1cf-f8814821411d",
+ "id": "9fe032cb-b9c3-47c1-ba49-50b8f74e7e76",
"name": "Log exportpoging",
"credentials": {
"postgres": {
@@ -140,10 +143,10 @@
"type": "n8n-nodes-base.ftp",
"typeVersion": 1,
"position": [
- 4176,
- 288
+ 4048,
+ 240
],
- "id": "cddb120b-27ae-4e65-91f5-63e9a78e045d",
+ "id": "6c961ced-bbed-4366-b056-25f798d608da",
"name": "Upload order naar Transpas FTP",
"credentials": {
"ftp": {
@@ -164,10 +167,10 @@
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
- 4576,
- 176
+ 4272,
+ 240
],
- "id": "74d1eabc-f4c6-4bf3-9e54-393da0f7a5d0",
+ "id": "67539e73-d05a-4ed6-8a95-1f9362c30f63",
"name": "Markeer export geslaagd",
"credentials": {
"postgres": {
@@ -185,10 +188,10 @@
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
- 4464,
- 352
+ 4272,
+ 672
],
- "id": "99f0884c-a60c-4b8d-b142-72176bf12618",
+ "id": "36e474cd-f4aa-4e10-bce1-803f8bb3cf23",
"name": "Maak FTP foutmelding",
"onError": "continueErrorOutput"
},
@@ -203,10 +206,10 @@
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
- 4752,
- 336
+ 4496,
+ 672
],
- "id": "ed112513-d2d0-4f4c-ad41-adee052c4bff",
+ "id": "7ac9974c-3c70-47c8-a5f1-89b5de72cf1a",
"name": "Markeer exportfout",
"credentials": {
"postgres": {
@@ -244,10 +247,10 @@
"type": "n8n-nodes-base.filter",
"typeVersion": 2.2,
"position": [
- 3184,
- 224
+ 2928,
+ 144
],
- "id": "4d787260-1d2c-47d6-b2ac-022636fecd5a",
+ "id": "a2edfac1-53f7-48db-909f-b1ff0eee2ea8",
"name": "Met opgeslagen pakbon"
},
{
@@ -278,10 +281,10 @@
"type": "n8n-nodes-base.filter",
"typeVersion": 2.2,
"position": [
- 3312,
- 400
+ 3376,
+ 432
],
- "id": "ebc1c06d-376d-4268-ba8b-a4014de05a94",
+ "id": "7e99ea48-ec49-45ad-b118-ac09f750a2f8",
"name": "Zonder pakbonbestand"
},
{
@@ -296,10 +299,10 @@
"type": "n8n-nodes-base.readWriteFile",
"typeVersion": 1.1,
"position": [
- 3408,
- 224
+ 3184,
+ -32
],
- "id": "b38a126c-6526-4122-909f-e783d9f582af",
+ "id": "bbcf61d6-f82c-463e-970f-f01eb7ee6612",
"name": "Lees pakbon van NAS",
"onError": "continueErrorOutput"
},
@@ -330,10 +333,10 @@
"type": "n8n-nodes-base.filter",
"typeVersion": 2.2,
"position": [
- 1408,
- 400
+ 1360,
+ 576
],
- "id": "5d1963ea-8bd8-44e8-974b-3e4d96f190f7",
+ "id": "d723f334-765a-46fd-8832-9b7a9fb3c45f",
"name": "Bestand is CSV"
},
{
@@ -343,10 +346,10 @@
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
- 1632,
- 400
+ 1584,
+ 576
],
- "id": "f8b5ca41-0887-4ded-a493-49a29a783dba",
+ "id": "7d11ffdd-b21f-4e02-b52f-9b3833ac43fc",
"name": "Detecteer CSV scheidingsteken",
"notesInFlow": true,
"onError": "continueErrorOutput",
@@ -366,10 +369,10 @@
"type": "n8n-nodes-base.extractFromFile",
"typeVersion": 1.1,
"position": [
- 1904,
- 384
+ 1808,
+ 576
],
- "id": "ca5eeac7-94ec-4299-b785-152f774d6dda",
+ "id": "26c9ac30-a1c9-483d-b9cf-9df73dbd7f3c",
"name": "Lees CSV op kolompositie",
"notesInFlow": true,
"onError": "continueErrorOutput",
@@ -403,10 +406,10 @@
"type": "n8n-nodes-base.filter",
"typeVersion": 2.2,
"position": [
- 2960,
- 512
+ 2704,
+ 432
],
- "id": "84799366-a4ce-4edd-a5b9-67e3233eae48",
+ "id": "4d3d3c22-20fe-4704-ba01-8ca02895a694",
"name": "Alleen ongewijzigde revisies",
"notesInFlow": true,
"notes": "Ook een mail zonder nieuwe Transpas-export is correct verwerkt en krijgt daarom de groene categorie."
@@ -425,9 +428,9 @@
"typeVersion": 1.2,
"position": [
16,
- 416
+ 576
],
- "id": "685995b6-e0ed-4949-bbd8-d06eddd2f996",
+ "id": "73618086-2fda-4f64-8f61-6b8bd103b559",
"name": "Controleer FTP elke 5 minuten"
},
{
@@ -448,9 +451,9 @@
"typeVersion": 3.4,
"position": [
240,
- 416
+ 576
],
- "id": "0267471f-1841-49c5-8fdc-c62f28df5de8",
+ "id": "ba1e6552-09b5-4892-a405-f4ab64ec8add",
"name": "FTP bronmap instellen",
"notesInFlow": true,
"notes": "Vul hier de FTP-map in waarin Lidl de XLSX-, XLS- of CSV-bestanden plaatst."
@@ -465,9 +468,9 @@
"typeVersion": 1,
"position": [
464,
- 416
+ 576
],
- "id": "178fe9f0-1c61-4d19-b94b-18c784338601",
+ "id": "16229f15-ce52-42e8-b458-76aff2d5646c",
"name": "Toon bestanden in FTP-map",
"credentials": {
"ftp": {
@@ -484,9 +487,9 @@
"typeVersion": 2,
"position": [
688,
- 416
+ 576
],
- "id": "342f1aa0-48cb-4635-8744-467be0439c1d",
+ "id": "f08457e0-77bf-44a3-8325-960526ce4c7e",
"name": "Selecteer FTP-bronbestanden",
"notesInFlow": true,
"notes": "Selecteert maximaal één CSV/XLS/XLSX per uitvoering. Zo kan één bronbestand met meerdere zendingregels na afloop maar één keer worden verplaatst."
@@ -500,9 +503,9 @@
"typeVersion": 1,
"position": [
912,
- 416
+ 576
],
- "id": "007781e2-68d5-4e3e-b991-86fc501d2f25",
+ "id": "fb43da1c-4b18-4a6f-9087-a761df10ed72",
"name": "Download bestand van FTP",
"credentials": {
"ftp": {
@@ -513,7 +516,7 @@
},
{
"parameters": {
- "content": "## FTP-bron\n\n1. De workflow leest `.xlsx`, `.xls` en `.csv`.\n2. Per workflow-run wordt bewust **één bronbestand** verwerkt.\n3. Het bestand mag meerdere zendingregels bevatten.\n4. Na succesvolle verwerking wordt het bronbestand **één keer** verplaatst naar `verwerkt`.\n5. Een volgend bestand wordt bij de volgende geplande uitvoering opgepakt.",
+ "content": "## Verwerking per order\n\n1. Per uitvoering wordt één CSV/XLS/XLSX opgehaald.\n2. De orders uit het bestand worden met batchgrootte **1** verwerkt.\n3. Per order wordt apart gecontroleerd of een pakbon aanwezig is.\n4. Bij een pakbon worden order-JSON en binary bestand binnen dezelfde iteratie samengevoegd.\n5. Pas als de loop volledig klaar is, wordt het bronbestand één keer naar `verwerkt` verplaatst.\n6. Bij een exportfout wordt niet naar de loop teruggekeerd en blijft het bronbestand staan.",
"height": 260,
"width": 500
},
@@ -523,7 +526,7 @@
0,
0
],
- "id": "99f22c78-9c6f-4853-bb9a-8578f5e32c52",
+ "id": "150cb6c4-801b-4240-b008-4e2c874cd728",
"name": "FTP bron uitleg"
},
{
@@ -531,15 +534,17 @@
"operation": "rename",
"oldPath": "={{ $('Selecteer Excel-bijlagen').item.json.source_ftp_path }}",
"newPath": "={{ $('FTP bronmap instellen').first().json.ftp_source_folder + '/verwerkt/' + $('Selecteer Excel-bijlagen').item.json.source_file_name }}",
- "options": {}
+ "options": {
+ "createDirectories": false
+ }
},
"type": "n8n-nodes-base.ftp",
"typeVersion": 1.1,
"position": [
- 4784,
- 80
+ 2704,
+ 624
],
- "id": "3984f2f8-7ca4-4e5a-abe2-025818327ef9",
+ "id": "4ce36db8-27c5-47de-a94e-d985bb22d376",
"name": "Verplaats bronbestand naar verwerkt",
"executeOnce": true,
"notesInFlow": true,
@@ -549,7 +554,39 @@
"name": "FTP De Wit Transport"
}
},
- "notes": "Voert de rename maar één keer uit. De bronselectie verwerkt maximaal één bestand per workflow-run."
+ "notes": "Wordt alleen vanaf de 'done'-uitgang van de orderloop uitgevoerd. Het CSV-bestand wordt dus pas na alle succesvol verwerkte/ongwijzigde orders één keer verplaatst."
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "type": "n8n-nodes-base.splitInBatches",
+ "typeVersion": 3,
+ "position": [
+ 2480,
+ 576
+ ],
+ "id": "b24f676a-73c6-49ec-9b5a-4f47706d041c",
+ "name": "Verwerk orders één voor één",
+ "notesInFlow": true,
+ "notes": "Verwerkt steeds precies één order. Pas nadat deze order volledig klaar is, wordt de volgende order uit het CSV-bestand gestart."
+ },
+ {
+ "parameters": {
+ "mode": "combine",
+ "combineBy": "combineByPosition",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.merge",
+ "typeVersion": 3.2,
+ "position": [
+ 3376,
+ 144
+ ],
+ "id": "64f5e12b-2d12-41fa-ba03-86056c187fe3",
+ "name": "Combineer order met gelezen pakbon",
+ "notesInFlow": true,
+ "notes": "Combineert binnen de loop de JSON van de huidige order met het binary pakbonbestand. Omdat de loop batchgrootte 1 is, kunnen order en document niet met een andere order mengen."
}
],
"pinData": {},
@@ -613,12 +650,7 @@
"main": [
[
{
- "node": "Alleen nieuwe of gewijzigde revisies",
- "type": "main",
- "index": 0
- },
- {
- "node": "Alleen ongewijzigde revisies",
+ "node": "Verwerk orders één voor één",
"type": "main",
"index": 0
}
@@ -644,6 +676,11 @@
"Met opgeslagen pakbon": {
"main": [
[
+ {
+ "node": "Combineer order met gelezen pakbon",
+ "type": "main",
+ "index": 0
+ },
{
"node": "Lees pakbon van NAS",
"type": "main",
@@ -656,9 +693,9 @@
"main": [
[
{
- "node": "Bouw Transpas TPE XML",
+ "node": "Combineer order met gelezen pakbon",
"type": "main",
- "index": 0
+ "index": 1
}
]
]
@@ -781,6 +818,17 @@
]
},
"Markeer export geslaagd": {
+ "main": [
+ [
+ {
+ "node": "Verwerk orders één voor één",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Verwerk orders één voor één": {
"main": [
[
{
@@ -788,6 +836,40 @@
"type": "main",
"index": 0
}
+ ],
+ [
+ {
+ "node": "Alleen nieuwe of gewijzigde revisies",
+ "type": "main",
+ "index": 0
+ },
+ {
+ "node": "Alleen ongewijzigde revisies",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Combineer order met gelezen pakbon": {
+ "main": [
+ [
+ {
+ "node": "Bouw Transpas TPE XML",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Alleen ongewijzigde revisies": {
+ "main": [
+ [
+ {
+ "node": "Verwerk orders één voor één",
+ "type": "main",
+ "index": 0
+ }
]
]
}
@@ -798,11 +880,11 @@
"binaryMode": "separate",
"availableInMCP": false
},
- "versionId": "4f45ad38-9fb7-4099-be89-17417dcdf73a",
+ "versionId": "98e992f4-4db0-441d-87a8-e42df73acb21",
"meta": {
"instanceId": "bef8d409866a58c0777dfe7cca1b9c2400fd051c056d361501393ab423006b5f"
},
"nodeGroups": [],
- "id": "LmKzYJsq8CIGXvYJ",
+ "id": "bA4pz9VBLu3t8tX0",
"tags": []
}
\ No newline at end of file