{ "name": "Lead Qualified → Mietvertrag PDF", "nodes": [ { "parameters": { "triggerOnNotify": true, "channel": "lead_qualified", "additionalFields": {} }, "id": "pg-trigger-mv", "name": "Postgres Trigger", "type": "n8n-nodes-base.postgresTrigger", "typeVersion": 1, "position": [250, 300], "credentials": { "postgres": { "id": "1", "name": "MC Cars Postgres" } } }, { "parameters": { "operation": "executeQuery", "query": "SELECT value FROM public.site_settings WHERE key = 'mietvertrag_template_path'", "additionalFields": {} }, "id": "check-template", "name": "Check Template Exists", "type": "n8n-nodes-base.postgres", "typeVersion": 2.5, "position": [470, 300], "credentials": { "postgres": { "id": "1", "name": "MC Cars Postgres" } } }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" }, "conditions": [ { "id": "cond1", "leftValue": "={{ $json.value }}", "rightValue": "", "operator": { "type": "string", "operation": "notEmpty" } } ], "combinator": "and" }, "options": {} }, "id": "if-template-exists", "name": "Template Exists?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [670, 300] }, { "parameters": { "operation": "executeQuery", "query": "SELECT c.name, c.email, c.phone,\n so.order_number, so.total_eur, so.deposit_eur,\n so.date_from, so.date_to, so.vehicle_label,\n so.daily_subtotal, so.weekend_subtotal,\n so.subtotal_eur, so.vat_eur,\n so.total_days, so.weekday_count, so.weekend_day_count,\n to_char(so.date_from, 'DD.MM.YYYY') as date_from_de,\n to_char(so.date_to, 'DD.MM.YYYY') as date_to_de,\n to_char(now(), 'DD.MM.YYYY') as today_de\nFROM public.customers c\nJOIN public.sales_orders so ON so.customer_id = c.id\nWHERE so.id = '{{ $('Postgres Trigger').item.json.sales_order_id }}'::uuid", "additionalFields": {} }, "id": "fetch-full-data", "name": "Fetch Full Order+Customer", "type": "n8n-nodes-base.postgres", "typeVersion": 2.5, "position": [890, 200], "credentials": { "postgres": { "id": "1", "name": "MC Cars Postgres" } } }, { "parameters": { "url": "=http://kong:8000/storage/v1/object/document-templates/{{ $('Check Template Exists').item.json.value }}", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "apikey", "value": "={{ $env.SERVICE_ROLE_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU' }}" }, { "name": "Authorization", "value": "=Bearer {{ $env.SERVICE_ROLE_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU' }}" } ] }, "options": { "response": { "response": { "responseFormat": "file" } } } }, "id": "download-template", "name": "Download DOCX Template", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [1110, 200] }, { "parameters": { "jsCode": "// Fill DOCX template placeholders using simple text replacement.\n// The DOCX is a ZIP containing XML. We replace {{placeholder}} markers\n// in the document.xml with actual values from the order data.\n\nconst JSZip = require('jszip');\n\nconst binaryData = await this.helpers.getBinaryDataBuffer(0, 'data');\nconst orderData = $('Fetch Full Order+Customer').first().json;\n\n// Placeholders map\nconst placeholders = {\n '{{KUNDE_NAME}}': orderData.name || '',\n '{{KUNDE_EMAIL}}': orderData.email || '',\n '{{KUNDE_TELEFON}}': orderData.phone || '',\n '{{BESTELLNUMMER}}': orderData.order_number || '',\n '{{FAHRZEUG}}': orderData.vehicle_label || '',\n '{{DATUM_VON}}': orderData.date_from_de || '',\n '{{DATUM_BIS}}': orderData.date_to_de || '',\n '{{TAGE_GESAMT}}': String(orderData.total_days || 0),\n '{{WOCHENTAGE}}': String(orderData.weekday_count || 0),\n '{{WOCHENENDTAGE}}': String(orderData.weekend_day_count || 0),\n '{{NETTO}}': String(orderData.subtotal_eur || 0),\n '{{MWST}}': String(orderData.vat_eur || 0),\n '{{GESAMT}}': String(orderData.total_eur || 0),\n '{{KAUTION}}': String(orderData.deposit_eur || 0),\n '{{TAGESSATZ}}': String(orderData.daily_subtotal || 0),\n '{{WOCHENENDZUSCHLAG}}': String(orderData.weekend_subtotal || 0),\n '{{DATUM_HEUTE}}': orderData.today_de || '',\n};\n\nconst zip = await JSZip.loadAsync(binaryData);\n\n// Process all XML files in the docx\nconst xmlFiles = Object.keys(zip.files).filter(f => f.endsWith('.xml'));\n\nfor (const xmlFile of xmlFiles) {\n let content = await zip.file(xmlFile).async('string');\n for (const [placeholder, value] of Object.entries(placeholders)) {\n // Handle split placeholders in XML (Word splits text into runs)\n // Simple approach: replace in the raw XML\n const escaped = placeholder.replace(/[{}]/g, c => `\\\\${c}`);\n content = content.split(placeholder).join(value);\n }\n zip.file(xmlFile, content);\n}\n\nconst filledDocx = await zip.generateAsync({ type: 'nodebuffer' });\n\nreturn [{\n json: { filename: `Mietvertrag_${orderData.order_number}.docx` },\n binary: {\n data: await this.helpers.prepareBinaryData(filledDocx, `Mietvertrag_${orderData.order_number}.docx`, 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')\n }\n}];" }, "id": "fill-template", "name": "Fill DOCX Template", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [1330, 200] }, { "parameters": { "url": "http://gotenberg:3000/forms/libreoffice/convert", "method": "POST", "sendBody": true, "contentType": "multipart-form-data", "bodyParameters": { "parameters": [ { "parameterType": "formBinaryData", "name": "files", "inputDataFieldName": "data" } ] }, "options": { "response": { "response": { "responseFormat": "file" } } } }, "id": "convert-to-pdf", "name": "Convert to PDF (Gotenberg)", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [1550, 200] }, { "parameters": { "jsCode": "// Rename the binary output to have the correct PDF filename\nconst orderData = $('Fetch Full Order+Customer').first().json;\nconst binaryData = await this.helpers.getBinaryDataBuffer(0, 'data');\n\nreturn [{\n json: { filename: `Mietvertrag_${orderData.order_number}.pdf`, email: orderData.email, name: orderData.name, order_number: orderData.order_number },\n binary: {\n data: await this.helpers.prepareBinaryData(binaryData, `Mietvertrag_${orderData.order_number}.pdf`, 'application/pdf')\n }\n}];" }, "id": "prepare-pdf", "name": "Prepare PDF Attachment", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [1770, 200] }, { "parameters": { "fromEmail": "info@mc-cars.at", "toEmail": "={{ $json.email }}", "subject": "MC Cars – Ihr Mietvertrag {{ $json.order_number }}", "emailType": "html", "html": "
Sehr geehrte/r {{ $json.name }},
\nanbei finden Sie Ihren Mietvertrag für die Bestellung {{ $json.order_number }}.
\nBitte prüfen Sie die Angaben und bringen Sie den unterschriebenen Vertrag zur Fahrzeugübergabe mit.
\nBei Fragen stehen wir Ihnen jederzeit zur Verfügung.
\nMit freundlichen Grüßen,
MC Cars GmbH
info@mc-cars.at
mc-cars.at