{ "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": "
\n
\n

MC Cars

\n
\n
\n

Sehr geehrte/r {{ $json.name }},

\n

anbei finden Sie Ihren Mietvertrag für die Bestellung {{ $json.order_number }}.

\n

Bitte prüfen Sie die Angaben und bringen Sie den unterschriebenen Vertrag zur Fahrzeugübergabe mit.

\n
\n

Bei Fragen stehen wir Ihnen jederzeit zur Verfügung.

\n

Mit freundlichen Grüßen,
MC Cars GmbH
info@mc-cars.at
mc-cars.at

\n
\n
", "options": { "attachments": "data" } }, "id": "send-mietvertrag", "name": "Send Mietvertrag Email", "type": "n8n-nodes-base.emailSend", "typeVersion": 2.1, "position": [1990, 200], "credentials": { "smtp": { "id": "2", "name": "MC Cars SMTP" } } } ], "connections": { "Postgres Trigger": { "main": [ [ { "node": "Check Template Exists", "type": "main", "index": 0 } ] ] }, "Check Template Exists": { "main": [ [ { "node": "Template Exists?", "type": "main", "index": 0 } ] ] }, "Template Exists?": { "main": [ [ { "node": "Fetch Full Order+Customer", "type": "main", "index": 0 } ], [] ] }, "Fetch Full Order+Customer": { "main": [ [ { "node": "Download DOCX Template", "type": "main", "index": 0 } ] ] }, "Download DOCX Template": { "main": [ [ { "node": "Fill DOCX Template", "type": "main", "index": 0 } ] ] }, "Fill DOCX Template": { "main": [ [ { "node": "Convert to PDF (Gotenberg)", "type": "main", "index": 0 } ] ] }, "Convert to PDF (Gotenberg)": { "main": [ [ { "node": "Prepare PDF Attachment", "type": "main", "index": 0 } ] ] }, "Prepare PDF Attachment": { "main": [ [ { "node": "Send Mietvertrag Email", "type": "main", "index": 0 } ] ] } }, "settings": { "executionOrder": "v1" }, "staticData": null, "tags": [ { "name": "mc-cars" } ], "triggerCount": 1 }