From 3a1163d5a735e56de66d35ce93a351fb52c3fc26 Mon Sep 17 00:00:00 2001 From: adminjeroen Date: Thu, 2 Jul 2026 21:57:58 +0200 Subject: [PATCH] Upload files to "palletways" voeg zendingen toe die wij moeten uitleveren aan de database --- palletways/Palletways BI - Leveringen.json | 289 +++++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 palletways/Palletways BI - Leveringen.json diff --git a/palletways/Palletways BI - Leveringen.json b/palletways/Palletways BI - Leveringen.json new file mode 100644 index 0000000..f5a34c5 --- /dev/null +++ b/palletways/Palletways BI - Leveringen.json @@ -0,0 +1,289 @@ +{ + "name": "Palletways BI - Leveringen", + "nodes": [ + { + "parameters": { + "jsCode": "// Get today's date\nconst today = new Date();\n\n\nconst lastWorkday = new Date(today);\n\n// Format as YYYY-MM-DD\nconst yyyy = lastWorkday.getFullYear();\nconst mm = String(lastWorkday.getMonth() + 1).padStart(2, '0');\nconst dd = String(lastWorkday.getDate()).padStart(2, '0');\nconst formattedDate = `${yyyy}-${mm}-${dd}`;\n\nreturn [\n {\n json: {\n lastWorkday: formattedDate\n }\n }\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 480, + -144 + ], + "id": "08c5aa91-9a51-49f7-ab05-c103ab5c53f4", + "name": "Last workday1" + }, + { + "parameters": { + "jsCode": "const xmlData = items[0]?.json?.data;\nconst listDate = (() => {\n try { return $items('Last workday1')?.[0]?.json?.lastWorkday || null; } catch (e) { return null; }\n})();\n\nif (typeof xmlData !== 'string') {\n throw new Error('Expected an XML string in $input.first().json.data');\n}\n\nconst out = [];\nconst seen = new Set();\nconst regex = /([\\s\\S]*?)<\\/TrackingID>/gi;\nlet match;\n\nwhile ((match = regex.exec(xmlData)) !== null) {\n const trackingid = String(match[1] || '').trim().toUpperCase();\n if (!trackingid || seen.has(trackingid)) continue;\n seen.add(trackingid);\n\n out.push({\n json: {\n trackingid,\n source_name: 'PW_DELIVERY_LIST',\n delivery_list_date: listDate,\n },\n });\n}\n\nreturn out;" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 960, + -144 + ], + "id": "b6a0bffa-2e63-40d0-b91f-d9e3c330546b", + "name": "Extract TrackingIDs" + }, + { + "parameters": { + "url": "=https://api.palletways.com/getconsignment/{{ $json.trackingid }}?apikey=SXJaM7PLjZDqfsnXSDP8Y9wDY6crxJySBg705MQEPus%3D", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1440, + -144 + ], + "id": "094aa988-beee-448f-9aad-d14e69c55ee7", + "name": "Get Consignment" + }, + { + "parameters": { + "options": {} + }, + "type": "n8n-nodes-base.splitInBatches", + "typeVersion": 3, + "position": [ + 1200, + -144 + ], + "id": "0df72e64-9034-4643-b984-4f0ee5396ada", + "name": "Loop Over Items" + }, + { + "parameters": { + "amount": 0.5 + }, + "type": "n8n-nodes-base.wait", + "typeVersion": 1.1, + "position": [ + 2160, + -144 + ], + "id": "537e024d-1ec6-43cc-92fe-22a7347f3306", + "name": "Wait", + "webhookId": "b17f739d-d020-4810-b669-e01b2df86c08" + }, + { + "parameters": { + "rule": { + "interval": [ + { + "field": "cronExpression", + "expression": "0 21 * * 1-5" + } + ] + } + }, + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.2, + "position": [ + 240, + -144 + ], + "id": "ec8c08dc-9dcb-424c-b194-f99a5e43c3d3", + "name": "Schedule Trigger" + }, + { + "parameters": { + "url": "=https://portal.palletways.com/api/getObman/{{ $json.lastWorkday }}?apikey=&output=xml", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "apikey", + "value": "SXJaM7PLjZDqfsnXSDP8Y9wDY6crxJySBg705MQEPus%3D" + }, + { + "name": "date", + "value": "={{ $json[\"lastWorkday\"] }}" + }, + { + "name": "output", + "value": "xml" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 720, + -144 + ], + "id": "29d07f3e-bb03-404e-9de0-96c77d15f472", + "name": "Get Obman delivery list" + }, + { + "parameters": { + "jsCode": "const OUR_DEPOT = 464;\nconst out = [];\n\nconst asArray = (v) => {\n if (v === undefined || v === null || v === '') return [];\n return Array.isArray(v) ? v : [v];\n};\n\nconst text = (v) => {\n if (v === undefined || v === null) return null;\n const s = String(v).trim();\n return s === '' ? null : s;\n};\n\nconst num = (v) => {\n const s = text(v);\n if (s === null) return null;\n const n = Number(String(s).replace(',', '.'));\n return Number.isFinite(n) ? n : null;\n};\n\nconst intVal = (v) => {\n const n = num(v);\n return n === null ? null : Math.trunc(n);\n};\n\nconst yesNoBool = (v) => {\n const s = String(v ?? '').trim().toLowerCase();\n if (['yes', 'true', '1', 'ja', 'y', 'on'].includes(s)) return true;\n if (['no', 'false', '0', 'nee', 'n', 'off'].includes(s)) return false;\n return null;\n};\n\nconst normalizeCountry = (v) => {\n const s = text(v);\n if (!s) return null;\n const u = s.toUpperCase();\n return u === 'UK' ? 'GB' : u;\n};\n\nconst decodeXml = (s) => String(s ?? '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\");\n\nconst tag = (xml, name) => {\n if (!xml) return null;\n const re = new RegExp(`<${name}(?:\\\\s[^>]*)?>([\\\\s\\\\S]*?)<\\\\/${name}>`, 'i');\n const m = String(xml).match(re);\n return m ? text(decodeXml(m[1])) : null;\n};\n\nconst blocks = (xml, name) => {\n if (!xml) return [];\n const re = new RegExp(`<${name}(?:\\\\s[^>]*)?>([\\\\s\\\\S]*?)<\\\\/${name}>`, 'gi');\n const result = [];\n let m;\n while ((m = re.exec(String(xml))) !== null) {\n result.push(m[1]);\n }\n return result;\n};\n\nconst firstBlock = (xml, name) => blocks(xml, name)[0] || '';\n\nconst safeNodeItem = (name) => {\n try {\n return $(name).item.json || {};\n } catch (e) {\n try {\n return $items(name)?.[0]?.json || {};\n } catch (e2) {\n return {};\n }\n }\n};\n\nconst makeDateTime = (dateValue, timeValue) => {\n const d = text(dateValue);\n const t = text(timeValue);\n if (!d) return null;\n\n // Already datetime\n if (d.includes('T') || /^\\d{4}-\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}/.test(d)) {\n return d.replace(' ', 'T');\n }\n\n return `${d}T${t || '00:00:00'}`;\n};\n\nconst dateOnly = (v) => {\n const s = text(v);\n if (!s) return null;\n return s.substring(0, 10);\n};\n\nconst statusFlags = (statusValue, statusDateTime) => {\n const status = text(statusValue);\n const s = String(status || '').toLowerCase();\n\n const isDelivered =\n s.includes('delivered') ||\n s.includes('afgeleverd') ||\n s.includes('pod');\n\n const isCollected =\n s.includes('collected') ||\n s.includes('picked up') ||\n s.includes('pickup complete') ||\n s.includes('collection complete') ||\n s.includes('opgehaald') ||\n s.includes('afgehaald');\n\n const isDeleted =\n s.includes('deleted') ||\n s.includes('removed') ||\n s.includes('cancelled') ||\n s.includes('canceled') ||\n s.includes('verwijderd') ||\n s.includes('geannuleerd');\n\n return {\n current_status: status,\n current_status_at: status ? statusDateTime : null,\n is_delivered: isDelivered,\n delivered_at: isDelivered ? statusDateTime : null,\n is_collected: isCollected,\n collected_at: isCollected ? statusDateTime : null,\n is_deleted: isDeleted,\n deleted_at: isDeleted ? statusDateTime : null,\n status_source: status ? 'PALLETWAYS_API' : null,\n };\n};\n\nfunction addressFromXml(consignmentXml, type) {\n const addressBlocks = blocks(consignmentXml, 'Address');\n const wanted = addressBlocks.find(a => String(tag(a, 'Type') || '').toLowerCase() === type.toLowerCase()) || '';\n return {\n contact: tag(wanted, 'ContactName'),\n company: tag(wanted, 'CompanyName'),\n addr1: tag(wanted, 'Addr1'),\n addr2: tag(wanted, 'Addr2'),\n addr3: tag(wanted, 'Addr3'),\n addr4: tag(wanted, 'Addr4'),\n town: tag(wanted, 'Town'),\n county: tag(wanted, 'County'),\n country: normalizeCountry(tag(wanted, 'Country')),\n postcode: tag(wanted, 'PostCode'),\n };\n}\n\nfunction getAddressFromJson(consignment, type) {\n const addresses = asArray(consignment.Address);\n return addresses.find(a => String(a.Type || '').toLowerCase() === type.toLowerCase()) || {};\n}\n\nfunction getServiceFromXml(consignmentXml, type) {\n const serviceBlocks = blocks(consignmentXml, 'Service');\n return serviceBlocks.find(s => String(tag(s, 'Type') || '').toLowerCase() === type.toLowerCase()) || '';\n}\n\nfunction getServiceFromJson(consignment, type) {\n const services = asArray(consignment.Service);\n return services.find(s => String(s?.Type || '').toLowerCase() === type.toLowerCase()) || {};\n}\n\nfunction countPalletCodesXml(consignmentXml) {\n return blocks(consignmentXml, 'Pallet')\n .map(p => text(decodeXml(p)))\n .filter(Boolean).length;\n}\n\nfunction countPalletCodesJson(palletValue) {\n return asArray(palletValue).map(p => text(p)).filter(Boolean).length;\n}\n\nfunction choosePrimaryService(parts, collectionDepot, deliveryDepot) {\n const collectionCode = text(parts.collection_service_code);\n const deliveryCode = text(parts.delivery_service_code);\n\n if (collectionDepot === OUR_DEPOT && collectionCode) {\n return {\n service_type: 'Collection',\n service_code: collectionCode,\n service_surcharge: text(parts.collection_service_surcharge),\n };\n }\n\n if (deliveryDepot === OUR_DEPOT && deliveryCode) {\n return {\n service_type: 'Delivery',\n service_code: deliveryCode,\n service_surcharge: text(parts.delivery_service_surcharge),\n };\n }\n\n // fallback voor zendingen die wij alleen aanmelden\n if (collectionCode) {\n return {\n service_type: 'Collection',\n service_code: collectionCode,\n service_surcharge: text(parts.collection_service_surcharge),\n };\n }\n\n if (deliveryCode) {\n return {\n service_type: 'Delivery',\n service_code: deliveryCode,\n service_surcharge: text(parts.delivery_service_surcharge),\n };\n }\n\n return {\n service_type: text(parts.service_type),\n service_code: text(parts.service_code),\n service_surcharge: text(parts.service_surcharge),\n };\n}\n\nfunction pushRowFromParts(parts) {\n const payingDepot = intVal(parts.paying_depot);\n const collectionDepot = intVal(parts.collection_depot);\n const deliveryDepot = intVal(parts.delivery_depot);\n const trackingId = text(parts.tracking_id);\n if (!trackingId) return;\n\n const primaryService = choosePrimaryService(parts, collectionDepot, deliveryDepot);\n\n const isOurPayingDepot = Boolean(parts.is_our_paying_depot) || payingDepot === OUR_DEPOT;\n const isCollectionByUs = Boolean(parts.is_collection_by_us) || collectionDepot === OUR_DEPOT;\n const isDeliveryByUs = Boolean(parts.is_delivery_by_us) || deliveryDepot === OUR_DEPOT;\n const isManifested = Boolean(parts.is_manifested) || isOurPayingDepot || text(parts.manifested_at) !== null;\n\n const statusInfo = statusFlags(parts.current_status, parts.current_status_at);\n\n out.push({\n json: {\n tracking_id: trackingId,\n\n consignment_number: text(parts.consignment_number),\n reference: text(parts.reference),\n collection_reference: text(parts.collection_reference),\n\n manifest_date: text(parts.manifest_date),\n manifest_time: text(parts.manifest_time),\n\n paying_depot: payingDepot,\n collection_depot: collectionDepot,\n delivery_depot: deliveryDepot,\n\n is_our_paying_depot: isOurPayingDepot,\n is_collection_by_us: isCollectionByUs,\n is_delivery_by_us: isDeliveryByUs,\n is_manifested: isManifested,\n\n account_name: text(parts.account_name),\n account_code: text(parts.account_code),\n\n consignment_type: text(parts.consignment_type),\n classification: text(parts.classification),\n\n service_type: primaryService.service_type,\n service_code: primaryService.service_code,\n service_surcharge: primaryService.service_surcharge,\n\n collection_service_code: text(parts.collection_service_code),\n collection_service_surcharge: text(parts.collection_service_surcharge),\n delivery_service_code: text(parts.delivery_service_code),\n delivery_service_surcharge: text(parts.delivery_service_surcharge),\n\n due_date: text(parts.due_date),\n due_time: text(parts.due_time),\n\n planned_collection_date: dateOnly(parts.planned_collection_date),\n planned_delivery_date: dateOnly(parts.planned_delivery_date),\n delivery_list_date: dateOnly(parts.delivery_list_date),\n collection_list_date: dateOnly(parts.collection_list_date),\n manifested_at: text(parts.manifested_at),\n\n lifts: num(parts.lifts),\n weight_kg: num(parts.weight_kg),\n\n bill_unit_type: text(parts.bill_unit_type),\n bill_unit_amount: num(parts.bill_unit_amount),\n\n pallet_count: intVal(parts.pallet_count),\n\n handball: yesNoBool(parts.handball),\n tail_lift: yesNoBool(parts.tail_lift),\n adr_goods: yesNoBool(parts.adr_goods),\n limited_quantity_goods: yesNoBool(parts.limited_quantity_goods),\n booked_in: yesNoBool(parts.booked_in),\n book_in_request: yesNoBool(parts.book_in_request),\n\n collection_company: text(parts.collection_company),\n collection_contact: text(parts.collection_contact),\n collection_addr1: text(parts.collection_addr1),\n collection_addr2: text(parts.collection_addr2),\n collection_addr3: text(parts.collection_addr3),\n collection_addr4: text(parts.collection_addr4),\n collection_town: text(parts.collection_town),\n collection_county: text(parts.collection_county),\n collection_country: normalizeCountry(parts.collection_country),\n collection_postcode: text(parts.collection_postcode),\n\n delivery_company: text(parts.delivery_company),\n delivery_contact: text(parts.delivery_contact),\n delivery_addr1: text(parts.delivery_addr1),\n delivery_addr2: text(parts.delivery_addr2),\n delivery_addr3: text(parts.delivery_addr3),\n delivery_addr4: text(parts.delivery_addr4),\n delivery_town: text(parts.delivery_town),\n delivery_county: text(parts.delivery_county),\n delivery_country: normalizeCountry(parts.delivery_country),\n delivery_postcode: text(parts.delivery_postcode),\n\n hub_sequence: intVal(parts.hub_sequence),\n hub_name: text(parts.hub_name),\n\n current_status: statusInfo.current_status,\n current_status_at: statusInfo.current_status_at,\n is_delivered: statusInfo.is_delivered,\n delivered_at: statusInfo.delivered_at,\n is_deleted: statusInfo.is_deleted,\n deleted_at: statusInfo.deleted_at,\n is_collected: statusInfo.is_collected,\n collected_at: statusInfo.collected_at,\n status_source: statusInfo.status_source,\n\n source_first_seen: text(parts.source_name),\n source_last_seen: text(parts.source_name),\n last_api_seen_at: new Date().toISOString(),\n\n raw_json: parts.raw_json ?? {},\n },\n });\n}\n\nfor (const item of items) {\n const root = item.json || {};\n\n const ctxTracking = safeNodeItem('Extract TrackingIDs');\n const ctxCollections = safeNodeItem('Extract Collections or No Work');\n const context = Object.keys(ctxTracking).length ? ctxTracking : (Object.keys(ctxCollections).length ? ctxCollections : {});\n\n const lastWorkday = (() => {\n try { return $items('Last workday1')?.[0]?.json?.lastWorkday || null; } catch (e) { return null; }\n })();\n\n const xml = typeof root.data === 'string'\n ? root.data\n : typeof root.body === 'string'\n ? root.body\n : typeof root === 'string'\n ? root\n : null;\n\n // Route 1: HTTP Request returned XML as json.data/body\n if (xml && xml.includes('