Files
n8n-workflows/Gheeraert/Gheereart Workflow 1 - orders export.json

220 lines
26 KiB
JSON

{
"name": "Gheereart Workflow 1 - orders export",
"nodes": [
{
"parameters": {
"jsCode": "// n8n Code node: Transpas TPE XML -> Gheeraert XML\n// Mode: Run Once for All Items\n//\n// Verwacht:\n// - TPE XML als binary property \"data\"\n// OF\n// - XML tekst in json.xml / json.data / json.body\n//\n// Output:\n// - 1 Gheeraert XML-bestand per <shipment>\n// - binary property: data\n\n// ---------- CONFIG ----------\nconst CONFIG = {\n INPUT_BINARY_PROPERTY: 'data',\n OUTPUT_BINARY_PROPERTY: 'data',\n\n FILE_PREFIX: 'PORTAL',\n\n BILL_TO: {\n name: 'De Wit Transport & Logistics',\n street: '',\n pc: '',\n city: '',\n country: 'NL',\n phone: '',\n email: '',\n comment: '',\n vat: 'NL007697168B01',\n },\n\n TRANSPORT_TYPE: 'SHIPMENT',\n\n DEFAULT_GOOD_CODE: 'PALLDM',\n GOOD_CODE_MAP: {\n 'EUROPALLET': 'EUR',\n 'EURO PALLET': 'EUR',\n 'EUR': 'EUR',\n\n 'BLOKPALLET': 'BLOK',\n 'BLOK PALLET': 'BLOK',\n 'BLOK': 'BLOK',\n\n 'PALLETS': 'PALLDM',\n 'PALLET': 'PALLDM',\n 'VRACHT': 'PALLDM',\n 'COLLI': 'COLLI',\n\n 'CC-TAG6': 'CC',\n 'CC TAG6': 'CC',\n },\n\n FIXED_HOUR_WHEN_EXACT_TIME: false,\n};\n\n// ---------- XML HELPERS ----------\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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\nfunction xmlDecode(value) {\n return String(value ?? '')\n .replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16)))\n .replace(/&#(\\d+);/g, (_, d) => String.fromCodePoint(parseInt(d, 10)))\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&apos;/g, \"'\")\n .replace(/&amp;/g, '&');\n}\n\nfunction clean(value) {\n return xmlDecode(value)\n .replace(/\\r\\n/g, '\\n')\n .replace(/\\r/g, '\\n')\n .split('\\n')\n .map(s => s.trim())\n .filter(Boolean)\n .join('\\n')\n .trim();\n}\n\nfunction firstBlock(xmlText, tag) {\n const re = new RegExp(`<${tag}\\\\b[^>]*>([\\\\s\\\\S]*?)<\\\\/${tag}>`, 'i');\n const m = String(xmlText || '').match(re);\n return m ? m[1] : '';\n}\n\nfunction allBlocks(xmlText, tag) {\n const re = new RegExp(`<${tag}\\\\b[^>]*>([\\\\s\\\\S]*?)<\\\\/${tag}>`, 'gi');\n const out = [];\n let m;\n while ((m = re.exec(String(xmlText || ''))) !== null) {\n out.push(m[1]);\n }\n return out;\n}\n\nfunction tagText(xmlText, tag) {\n const re = new RegExp(`<${tag}\\\\b[^>]*>([\\\\s\\\\S]*?)<\\\\/${tag}>|<${tag}\\\\b[^>]*/>`, 'i');\n const m = String(xmlText || '').match(re);\n if (!m) return '';\n return clean(m[1] || '');\n}\n\nfunction allTagTexts(xmlText, tag) {\n const re = new RegExp(`<${tag}\\\\b[^>]*>([\\\\s\\\\S]*?)<\\\\/${tag}>`, 'gi');\n const out = [];\n let m;\n while ((m = re.exec(String(xmlText || ''))) !== null) {\n const value = clean(m[1] || '');\n if (value) out.push(value);\n }\n return out;\n}\n\nfunction isTrueFlag(value) {\n const v = String(value ?? '').trim().toLowerCase();\n return ['true', '1', 'yes', 'ja', 'y'].includes(v);\n}\n\nfunction boolText(value) {\n return isTrueFlag(value) ? 'true' : 'false';\n}\n\nfunction isBeneluxCountry(country) {\n const c = String(country || '').trim().toUpperCase();\n return ['NL', 'BE', 'LU'].includes(c);\n}\n\nfunction formatDate(value) {\n const v = String(value ?? '').trim();\n if (!v) return '';\n\n const iso = v.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (iso) return `${iso[3]}/${iso[2]}/${iso[1]}`;\n\n const dmy = v.match(/^(\\d{2})\\/(\\d{2})\\/(\\d{4})$/);\n if (dmy) return v;\n\n return v;\n}\n\nfunction formatTime(value) {\n const v = String(value ?? '').trim();\n if (!v) return '';\n\n const m = v.match(/^(\\d{1,2}):(\\d{2})/);\n if (!m) return '';\n\n return `${m[1].padStart(2, '0')}:${m[2]}`;\n}\n\nfunction minutes(value) {\n const t = formatTime(value);\n if (!t) return null;\n\n const [h, m] = t.split(':').map(Number);\n return h * 60 + m;\n}\n\nfunction numberText(value, fallback = '') {\n const v = String(value ?? '').trim().replace(',', '.');\n if (!v) return fallback;\n\n const n = Number(v);\n return Number.isFinite(n) ? String(n) : fallback;\n}\n\nfunction integerText(value, fallback = '') {\n const v = String(value ?? '').trim().replace(',', '.');\n if (!v) return fallback;\n\n const n = Number(v);\n return Number.isFinite(n) ? String(Math.round(n)) : fallback;\n}\n\nfunction safeFilePart(value) {\n return String(value || '')\n .replace(/[^a-zA-Z0-9._-]+/g, '_')\n .replace(/^_+|_+$/g, '')\n .slice(0, 80) || 'shipment';\n}\n\nfunction nowStamp() {\n const d = new Date();\n const p = n => String(n).padStart(2, '0');\n\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 uniqueSuffix(index) {\n return `${String(index + 1).padStart(3, '0')}_${Math.random().toString(36).slice(2, 8)}`;\n}\n\nfunction xmlEl(name, value, level = 0) {\n const indent = '\\t'.repeat(level);\n return `${indent}<${name}>${xmlEsc(value)}</${name}>`;\n}\n\nfunction decodeXmlBuffer(buffer) {\n const head = buffer.toString('ascii', 0, Math.min(buffer.length, 300));\n const encMatch = head.match(/encoding=[\"']([^\"']+)[\"']/i);\n const encoding = encMatch ? encMatch[1].toLowerCase() : 'utf-8';\n\n if (encoding.includes('windows-1252') || encoding.includes('cp1252')) {\n try {\n return new TextDecoder('windows-1252').decode(buffer).replace(/^\\uFEFF/, '');\n } catch (e) {\n return buffer.toString('latin1').replace(/^\\uFEFF/, '');\n }\n }\n\n if (encoding.includes('iso-8859-1') || encoding.includes('latin1')) {\n return buffer.toString('latin1').replace(/^\\uFEFF/, '');\n }\n\n return buffer.toString('utf8').replace(/^\\uFEFF/, '');\n}\n\n// ---------- TPE HELPERS ----------\nfunction addr(block) {\n const commentParts = [\n tagText(block, 'address2'),\n tagText(block, 'driverinfo'),\n tagText(block, 'remarks'),\n tagText(block, 'comment'),\n ].filter(Boolean);\n\n return {\n name: tagText(block, 'name'),\n street: tagText(block, 'address1') || tagText(block, 'street'),\n pc: tagText(block, 'zipcode') || tagText(block, 'postcode') || tagText(block, 'pc'),\n city: tagText(block, 'city_id') || tagText(block, 'cityname') || tagText(block, 'city'),\n country: tagText(block, 'country_id') || tagText(block, 'country'),\n phone: tagText(block, 'phone') || tagText(block, 'mobile'),\n email: tagText(block, 'email'),\n comment: commentParts.join('\\n'),\n contact: tagText(block, 'contact'),\n };\n}\n\nfunction timeWindowComment(block) {\n const start = formatTime(tagText(block, 'time'));\n const end = formatTime(tagText(block, 'timetill'));\n\n if (start && end && start !== end) return `Tijdvenster: ${start} - ${end}`;\n if (start) return `Tijd: ${start}`;\n return '';\n}\n\nfunction detectRequirements(block, includeRembours = false, options = {}) {\n const driverInfo = [\n tagText(block, 'driverinfo'),\n tagText(block, 'remarks'),\n tagText(block, 'comment'),\n tagText(block, 'address2'),\n options.extraText || '',\n ].join(' ').toLowerCase();\n\n const start = minutes(tagText(block, 'time'));\n const end = minutes(tagText(block, 'timetill'));\n\n const startTxt = formatTime(tagText(block, 'time'));\n const endTxt = formatTime(tagText(block, 'timetill'));\n\n const fixedHr =\n CONFIG.FIXED_HOUR_WHEN_EXACT_TIME &&\n startTxt &&\n (!endTxt || startTxt === endTxt);\n\n const before10 = !fixedHr && end !== null && end <= 10 * 60;\n const before12 = !fixedHr && !before10 && end !== null && end <= 12 * 60;\n const after12 = !fixedHr && start !== null && start >= 12 * 60;\n\n const call = /\\b(call|bellen|bel|telefon|phone|contact|aanmelden)\\b/i.test(driverInfo);\n\n const bookIn =\n options.forceBookIn ||\n /\\b(book\\s*in|bookin|appointment|afspraak|aanmelden|melden|rdv|rendez[- ]?vous|prendre\\s+rdv)\\b/i.test(driverInfo);\n\n const side = /\\b(side|zijkant|tautliner|schuifzeil|curtain|curtainsider)\\b/i.test(driverInfo);\n\n // Let op:\n // tailgate/laadklep wordt NIET naar ForkLift gemapt.\n // ForkLift blijft alleen true als het expliciet uit tekst blijkt of geforceerd wordt.\n const forkLift =\n options.forceForkLift ||\n /\\b(fork\\s*lift|forklift|heftruck|kooiaap|meeneemheftruck)\\b/i.test(driverInfo);\n\n return {\n FixedHr: fixedHr ? 'true' : 'false',\n Before10: before10 ? 'true' : 'false',\n Before12: before12 ? 'true' : 'false',\n After12: after12 ? 'true' : 'false',\n Rembours: includeRembours ? 'false' : undefined,\n Call: call ? 'true' : 'false',\n BookIn: bookIn ? 'true' : 'false',\n Side: side ? 'true' : 'false',\n ForkLift: forkLift ? 'true' : 'false',\n };\n}\n\nfunction getGoodCode(unitId) {\n const key = String(unitId || '').trim().toUpperCase();\n return CONFIG.GOOD_CODE_MAP[key] || CONFIG.DEFAULT_GOOD_CODE;\n}\n\n// ---------- GHEERAERT XML BUILDERS ----------\nfunction buildAddressXml(address, level) {\n return [\n `${'\\t'.repeat(level)}<Address>`,\n xmlEl('Name', address.name, level + 1),\n xmlEl('Street', address.street, level + 1),\n xmlEl('PC', address.pc, level + 1),\n xmlEl('City', address.city, level + 1),\n xmlEl('Country', address.country, level + 1),\n xmlEl('Phone', address.phone, level + 1),\n xmlEl('Email', address.email, level + 1),\n xmlEl('Comment', address.comment, level + 1),\n `${'\\t'.repeat(level)}</Address>`,\n ].join('\\n');\n}\n\nfunction buildRequirementsXml(req, level) {\n const lines = [`${'\\t'.repeat(level)}<Requirements>`];\n\n for (const key of [\n 'FixedHr',\n 'Before10',\n 'Before12',\n 'After12',\n 'Rembours',\n 'Call',\n 'BookIn',\n 'Side',\n 'ForkLift',\n ]) {\n if (req[key] !== undefined) {\n lines.push(xmlEl(key, req[key], level + 1));\n }\n }\n\n lines.push(`${'\\t'.repeat(level)}</Requirements>`);\n return lines.join('\\n');\n}\n\nfunction buildStopXml(tagName, block, fallbackAddrBlock, includeRembours, level, options = {}) {\n const sourceBlock = block || fallbackAddrBlock || '';\n const address = addr(sourceBlock);\n const req = detectRequirements(sourceBlock, includeRembours, options);\n\n const fixedHr = req.FixedHr === 'true';\n const hour = fixedHr ? formatTime(tagText(sourceBlock, 'time')) : '';\n\n return [\n `${'\\t'.repeat(level)}<${tagName}>`,\n buildAddressXml(address, level + 1),\n xmlEl('Date', formatDate(tagText(sourceBlock, 'date')), level + 1),\n xmlEl('Hour', hour, level + 1),\n xmlEl('Reference', tagText(sourceBlock, 'reference'), level + 1),\n buildRequirementsXml(req, level + 1),\n `${'\\t'.repeat(level)}</${tagName}>`,\n ].join('\\n');\n}\n\nfunction buildAdrXml(sourceBlock, level) {\n const adrBlocks = [\n ...allBlocks(sourceBlock, 'ADR'),\n ...allBlocks(sourceBlock, 'adr'),\n ];\n\n return adrBlocks.map(adr => [\n `${'\\t'.repeat(level)}<ADR>`,\n xmlEl('UN', tagText(adr, 'UN') || tagText(adr, 'un') || tagText(adr, 'unnumber'), level + 1),\n xmlEl('Description', tagText(adr, 'Description') || tagText(adr, 'description'), level + 1),\n xmlEl('TechName', tagText(adr, 'TechName') || tagText(adr, 'techname'), level + 1),\n xmlEl('Class', tagText(adr, 'Class') || tagText(adr, 'class'), level + 1),\n xmlEl('PackingGroup', tagText(adr, 'PackingGroup') || tagText(adr, 'packinggroup'), level + 1),\n xmlEl('TransportCategory', tagText(adr, 'TransportCategory') || tagText(adr, 'transportcategory'), level + 1),\n xmlEl('TunnelRestrictionCode', tagText(adr, 'TunnelRestrictionCode') || tagText(adr, 'tunnelrestrictioncode'), level + 1),\n xmlEl('Weight', numberText(tagText(adr, 'Weight') || tagText(adr, 'weight')), level + 1),\n xmlEl('EnvDanger', boolText(tagText(adr, 'EnvDanger') || tagText(adr, 'envdanger')), level + 1),\n `${'\\t'.repeat(level)}</ADR>`,\n ].join('\\n')).join('\\n');\n}\n\nfunction buildLoadXml(gl, cargoBlock, level) {\n const unit = tagText(gl, 'unit_id') || tagText(cargoBlock, 'unit_id');\n\n const product =\n tagText(gl, 'productdescription') ||\n tagText(gl, 'product_id') ||\n tagText(cargoBlock, 'productdescription') ||\n tagText(cargoBlock, 'product_id');\n\n const number = integerText(\n tagText(gl, 'unitamount') || tagText(cargoBlock, 'unitamount'),\n '1'\n );\n\n let barcodes = allTagTexts(gl, 'barcode');\n\n // In sommige TPE exports staat de barcode als reference op goodsline.\n if (!barcodes.length) {\n const glReference = tagText(gl, 'reference');\n if (glReference) barcodes.push(glReference);\n }\n\n // In andere TPE exports staat barcode op cargo-niveau.\n if (!barcodes.length) {\n barcodes = allTagTexts(cargoBlock, 'barcode');\n }\n\n const lines = [\n `${'\\t'.repeat(level)}<Load>`,\n xmlEl('Number', number, level + 1),\n xmlEl('GoodCode', getGoodCode(unit), level + 1),\n xmlEl('L', integerText(tagText(gl, 'length') || tagText(cargoBlock, 'length'), '0'), level + 1),\n xmlEl('B', integerText(tagText(gl, 'width') || tagText(cargoBlock, 'width'), '0'), level + 1),\n xmlEl('H', integerText(tagText(gl, 'height') || tagText(cargoBlock, 'height'), '0'), level + 1),\n xmlEl('LDM', numberText(tagText(gl, 'loadingmeter') || tagText(cargoBlock, 'loadingmeter'), '0'), level + 1),\n xmlEl('Weight', integerText(tagText(gl, 'weight') || tagText(cargoBlock, 'weight'), '0'), level + 1),\n xmlEl('Comment', product, level + 1),\n ];\n\n for (const barcode of barcodes) {\n lines.push(xmlEl('Barcode', barcode, level + 1));\n }\n\n const adrXml = buildAdrXml(gl, level + 1);\n if (adrXml) {\n lines.push(adrXml);\n }\n\n lines.push(`${'\\t'.repeat(level)}</Load>`);\n return lines.join('\\n');\n}\n\nfunction getTripReference(rootXml) {\n const rootWithoutShipments = String(rootXml || '').replace(/<shipments\\b[^>]*>[\\s\\S]*?<\\/shipments>/i, '');\n return tagText(rootWithoutShipments, 'reference');\n}\n\nfunction buildOrderXml(shipment, rootXml, level) {\n const shipmentEdiRef = tagText(shipment, 'edireference');\n const shipmentRef = tagText(shipment, 'reference');\n const bookingReference = tagText(shipment, 'bookingreference');\n const clientReference = tagText(shipment, 'clientreference');\n const tripRef = getTripReference(rootXml);\n\n const hasAppointment = isTrueFlag(tagText(shipment, 'appointment'));\n const hasTailgate = isTrueFlag(tagText(shipment, 'tailgate'));\n\n const senderBlock = firstBlock(shipment, 'sender');\n const receiverBlock = firstBlock(shipment, 'receiver');\n\n const pickupBlock = firstBlock(shipment, 'pickupaddress') || senderBlock;\n const deliveryBlock = firstBlock(shipment, 'deliveryaddress') || receiverBlock;\n\n const deliveryCountry =\n tagText(deliveryBlock, 'country_id') ||\n tagText(deliveryBlock, 'country') ||\n tagText(receiverBlock, 'country_id') ||\n tagText(receiverBlock, 'country');\n\n // Gheeraert: Benelux standaard laadklep.\n // Buiten Benelux moet laadklep in commentaar.\n // Als land onbekend is, zetten we laadklep voor de zekerheid wél in commentaar.\n const mentionTailgateInComment = hasTailgate && !isBeneluxCountry(deliveryCountry);\n\n const cargoBlock = firstBlock(shipment, 'cargo');\n\n let goodsLines = allBlocks(cargoBlock, 'goodsline');\n\n if (!goodsLines.length && cargoBlock) {\n goodsLines = [cargoBlock];\n }\n\n const pickupComment = [\n timeWindowComment(pickupBlock),\n tagText(pickupBlock, 'driverinfo'),\n tagText(pickupBlock, 'remarks'),\n ].filter(Boolean).join('\\n');\n\n const deliveryComment = [\n timeWindowComment(deliveryBlock),\n tagText(deliveryBlock, 'driverinfo'),\n tagText(deliveryBlock, 'remarks'),\n ].filter(Boolean).join('\\n');\n\n const orderComment = [\n tagText(shipment, 'remarks'),\n tagText(shipment, 'remark'),\n bookingReference ? `Bookingreference: ${bookingReference}` : '',\n clientReference ? `Clientreference: ${clientReference}` : '',\n hasAppointment ? 'Afspraak maken vereist' : '',\n mentionTailgateInComment ? 'Laadklep vereist' : '',\n pickupComment ? `Laden: ${pickupComment}` : '',\n deliveryComment ? `Lossen: ${deliveryComment}` : '',\n ].filter(Boolean).join('\\n');\n\n const billTo = CONFIG.BILL_TO;\n\n const lines = [\n `${'\\t'.repeat(level)}<Order>`,\n\n xmlEl('PurchaseOrderNumber', shipmentRef || shipmentEdiRef || tripRef, level + 1),\n xmlEl('ExtOrderNumber', shipmentEdiRef || shipmentRef, level + 1),\n xmlEl('ExtDelNoteNumber', tagText(deliveryBlock, 'reference') || bookingReference || shipmentRef, level + 1),\n\n buildStopXml(\n 'ShipFrom',\n pickupBlock,\n senderBlock,\n false,\n level + 1,\n {\n forceBookIn: false,\n forceForkLift: false,\n }\n ),\n\n buildStopXml(\n 'ShipTo',\n deliveryBlock,\n receiverBlock,\n true,\n level + 1,\n {\n // appointment uit TPE betekent afspraak maken bij levering\n forceBookIn: hasAppointment,\n\n // tailgate/laadklep NIET naar ForkLift sturen\n forceForkLift: false,\n\n extraText: hasAppointment ? 'appointment afspraak rdv' : '',\n }\n ),\n\n `${'\\t'.repeat(level + 1)}<BillTo>`,\n buildAddressXml({\n name: billTo.name,\n street: billTo.street,\n pc: billTo.pc,\n city: billTo.city,\n country: billTo.country,\n phone: billTo.phone,\n email: billTo.email,\n comment: billTo.comment,\n }, level + 2),\n xmlEl('VAT', billTo.vat || tagText(rootXml, 'customer_id'), level + 2),\n `${'\\t'.repeat(level + 1)}</BillTo>`,\n\n xmlEl('Comment', orderComment, level + 1),\n xmlEl('Type', CONFIG.TRANSPORT_TYPE, level + 1),\n ];\n\n for (const gl of goodsLines) {\n lines.push(buildLoadXml(gl, cargoBlock, level + 1));\n }\n\n lines.push(`${'\\t'.repeat(level)}</Order>`);\n\n return {\n xml: lines.join('\\n'),\n meta: {\n shipmentRef,\n shipmentEdiRef,\n tripRef,\n bookingReference,\n clientReference,\n deliveryCountry,\n hasAppointment,\n hasTailgate,\n mentionTailgateInComment,\n loadCount: goodsLines.length,\n },\n };\n}\n\nfunction buildGheeraertXmlForShipment(shipment, rootXml) {\n const order = buildOrderXml(shipment, rootXml, 1);\n\n const xmlOut = [\n '<?xml version=\"1.0\" encoding=\"utf-8\"?>',\n '<Orders>',\n order.xml,\n '</Orders>',\n ].join('\\n');\n\n return {\n xml: xmlOut,\n meta: order.meta,\n };\n}\n\n// ---------- INPUT READER ----------\nasync function getXmlFromItem(item, index) {\n const binProp = CONFIG.INPUT_BINARY_PROPERTY;\n\n if (item.binary?.[binProp]) {\n const buffer = await this.helpers.getBinaryDataBuffer(index, binProp);\n return decodeXmlBuffer(buffer);\n }\n\n const xml =\n item.json?.xml ??\n item.json?.data ??\n item.json?.body ??\n item.json?.content ??\n '';\n\n if (!xml) {\n throw new Error(`Item ${index}: geen XML gevonden. Verwacht binary.${binProp} of json.xml/json.data/json.body.`);\n }\n\n return String(xml).replace(/^\\uFEFF/, '');\n}\n\n// ---------- MAIN ----------\nconst items = $input.all();\nconst output = [];\n\nfor (let inputIndex = 0; inputIndex < items.length; inputIndex++) {\n const sourceXml = await getXmlFromItem.call(this, items[inputIndex], inputIndex);\n\n const shipments = allBlocks(sourceXml, 'shipment');\n\n if (!shipments.length) {\n throw new Error(`Item ${inputIndex}: geen <shipment> blokken gevonden in de TPE XML.`);\n }\n\n for (let shipmentIndex = 0; shipmentIndex < shipments.length; shipmentIndex++) {\n const shipment = shipments[shipmentIndex];\n\n const { xml: gheeraertXml, meta } = buildGheeraertXmlForShipment(shipment, sourceXml);\n\n const baseRef = safeFilePart(meta.shipmentRef || meta.shipmentEdiRef || meta.tripRef);\n const fileName = `${CONFIG.FILE_PREFIX}_${baseRef}_${nowStamp()}_${uniqueSuffix(shipmentIndex)}.xml`;\n\n const binaryData = await this.helpers.prepareBinaryData(\n Buffer.from(gheeraertXml, 'utf8'),\n fileName,\n 'application/xml'\n );\n\n output.push({\n json: {\n fileName,\n shipmentReference: meta.shipmentRef,\n shipmentEdiReference: meta.shipmentEdiRef,\n tripReference: meta.tripRef,\n bookingReference: meta.bookingReference,\n clientReference: meta.clientReference,\n deliveryCountry: meta.deliveryCountry,\n appointment: meta.hasAppointment,\n tailgate: meta.hasTailgate,\n tailgateMentionedInComment: meta.mentionTailgateInComment,\n loadCount: meta.loadCount,\n sourceInputIndex: inputIndex,\n sourceShipmentIndex: shipmentIndex,\n },\n binary: {\n [CONFIG.OUTPUT_BINARY_PROPERTY]: binaryData,\n },\n });\n }\n}\n\nreturn output;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-384,
-128
],
"id": "3e4ddb57-9298-4a1b-b486-ccb8a595d979",
"name": "Code in JavaScript"
},
{
"parameters": {
"path": "={{ $json.path }}",
"options": {}
},
"type": "n8n-nodes-base.ftp",
"typeVersion": 1,
"position": [
-848,
-128
],
"id": "05f19f00-8d96-469a-ad3d-d67e4d44a69e",
"name": "Download file",
"credentials": {
"ftp": {
"id": "oLAZ4OgmkOopMHAq",
"name": "FTP De Wit Transport"
}
}
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "*/15 6-22 * * 1-5"
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-1280,
-128
],
"id": "d651ca8c-2559-4824-af10-84782d60d8a3",
"name": "Schedule Trigger"
},
{
"parameters": {
"operation": "list",
"path": "/prod/FromTP/tpe_export/",
"options": {}
},
"type": "n8n-nodes-base.ftp",
"typeVersion": 1,
"position": [
-1056,
-128
],
"id": "e26a6649-67a5-4e2b-949a-fec7ef8b5ad3",
"name": "List files",
"credentials": {
"ftp": {
"id": "oLAZ4OgmkOopMHAq",
"name": "FTP De Wit Transport"
}
}
},
{
"parameters": {
"operation": "xml",
"options": {}
},
"type": "n8n-nodes-base.extractFromFile",
"typeVersion": 1,
"position": [
-608,
-128
],
"id": "7dda404f-ba0f-4d7e-84f5-45fba7d9a9dd",
"name": "Extract from File"
},
{
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO public.ghe_order_log (\n purchase_order_number,\n transpas_legnr,\n transpas_reference,\n file_name,\n file_uploaded_at,\n file_upload_status,\n next_status_poll_at\n)\nVALUES (\n $1::varchar,\n $2::varchar,\n $3::varchar,\n $4::varchar,\n CURRENT_TIMESTAMP,\n 'sent',\n CURRENT_TIMESTAMP + INTERVAL '10 minutes'\n)\nON CONFLICT (purchase_order_number)\nDO UPDATE SET\n transpas_legnr = EXCLUDED.transpas_legnr,\n transpas_reference = EXCLUDED.transpas_reference,\n file_name = EXCLUDED.file_name,\n file_uploaded_at = EXCLUDED.file_uploaded_at,\n file_upload_status = EXCLUDED.file_upload_status,\n file_upload_error = NULL,\n next_status_poll_at = EXCLUDED.next_status_poll_at;",
"options": {
"queryReplacement": "={{ $json.shipmentReference }}, {{ $json.shipmentEdiReference }}, {{ $json.tripReference }}, {{ $json.fileName }}"
}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
32,
-128
],
"id": "c4b0ec24-abc1-4916-84a2-a3aa3334f085",
"name": "Execute a SQL query",
"credentials": {
"postgres": {
"id": "pxziuCOuNpsUFKrm",
"name": "transpas_n8n_gheeraert"
}
}
},
{
"parameters": {
"protocol": "sftp",
"operation": "upload",
"path": "=/IN/{{ $json.fileName }}",
"options": {}
},
"type": "n8n-nodes-base.ftp",
"typeVersion": 1,
"position": [
-176,
-128
],
"id": "91d2ac28-5258-468a-b14c-b5a7bbd44815",
"name": "FTP",
"credentials": {
"sftp": {
"id": "dDY78xgQa87mHl4J",
"name": "FTP Gheeraert prod"
}
}
}
],
"pinData": {},
"connections": {
"Download file": {
"main": [
[
{
"node": "Extract from File",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "List files",
"type": "main",
"index": 0
}
]
]
},
"List files": {
"main": [
[
{
"node": "Download file",
"type": "main",
"index": 0
}
]
]
},
"Extract from File": {
"main": [
[
{
"node": "Code in JavaScript",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript": {
"main": [
[
{
"node": "FTP",
"type": "main",
"index": 0
}
]
]
},
"FTP": {
"main": [
[
{
"node": "Execute a SQL query",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate"
},
"versionId": "97d8fd4d-3d5a-4492-ac09-66b1da5b35f7",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "bef8d409866a58c0777dfe7cca1b9c2400fd051c056d361501393ab423006b5f"
},
"nodeGroups": [],
"id": "0E0ESANYP27dlue7",
"tags": []
}