Files
mc_cars_gmbh_infraestructure/n8n/workflows/03-manual-email-send.json
T
Lago f46ba8cadc feat(i18n): add VAT labels and email sent messages in German and English
style(admin): increase max-width of admin page and adjust table styles

fix(n8n): enhance workflow import and publishing process

fix(workflows): update SQL queries for fetching and updating sales orders

feat(migrations): normalize rental types and enhance email guard for individuell rentals

feat(migrations): add RPC for updating deposit in sales orders

fix(migrations): ensure individuell orders persist net/vat components and backfill existing records

test: update last run status to failed
2026-05-17 22:35:11 +02:00

263 lines
12 KiB
JSON

{
"name": "Manual Email Send",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "manual-email-send",
"responseMode": "responseNode",
"options": {}
},
"id": "webhook-trigger",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [250, 300],
"webhookId": "manual-email-send"
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT so.id, 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\nFROM public.customers c\nJOIN public.sales_orders so ON so.customer_id = c.id\nWHERE so.id = '{{ $json.body?.sales_order_id || $json.query?.sales_order_id || $json.sales_order_id }}'::uuid",
"options": {}
},
"id": "fetch-order-data",
"name": "Fetch Order Data",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [470, 300],
"credentials": {
"postgres": {
"id": "__POSTGRES_CREDENTIAL_ID__",
"name": "Postgres account"
}
}
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const item = $json;\n\nconst formatEur = (value) => {\n const n = Number(value || 0);\n return new Intl.NumberFormat(\"de-AT\", { style: \"currency\", currency: \"EUR\" }).format(n);\n};\n\nconst formatDate = (value) => {\n if (!value) return \"-\";\n const d = new Date(value);\n if (Number.isNaN(d.getTime())) return String(value);\n return new Intl.DateTimeFormat(\"de-AT\", { day: \"2-digit\", month: \"2-digit\", year: \"numeric\" }).format(d);\n};\n\nconst toNum = (value) => {\n const n = Number(value);\n return Number.isFinite(n) ? n : 0;\n};\n\nconst round2 = (value) => Math.round(value * 100) / 100;\n\nconst orderNumber = item.order_number || \"\";\nconst dateFrom = formatDate(item.date_from);\nconst dateTo = formatDate(item.date_to);\nconst rentalRange = `${dateFrom} bis ${dateTo}`;\n\nconst totalEurNum = toNum(item.total_eur);\nlet subtotalEurNum = toNum(item.subtotal_eur);\nlet vatEurNum = toNum(item.vat_eur);\n\nif (totalEurNum > 0 && subtotalEurNum === 0 && vatEurNum === 0) {\n subtotalEurNum = round2(totalEurNum / 1.2);\n vatEurNum = round2(totalEurNum - subtotalEurNum);\n}\n\nconst depositEur = formatEur(item.deposit_eur);\nconst rentalEur = formatEur(totalEurNum);\nconst subtotalEur = formatEur(subtotalEurNum);\nconst vatEur = formatEur(vatEurNum);\n\nconst safeName = item.name || \"Kundin/Kunde\";\nconst vehicle = item.vehicle_label || \"-\";\n\nconst subject = `MC Cars \u2013 Buchung best\u00e4tigt (${orderNumber}) \u2013 Zahlungsinformationen`;\n\nconst paypalButton = (url, alt) => `\n <a href=\"${url}\" style=\"display:inline-block;text-decoration:none;\" target=\"_blank\" rel=\"noopener noreferrer\">\n <img src=\"https://www.paypalobjects.com/webstatic/en_US/i/buttons/checkout-logo-large.png\" alt=\"${alt}\" style=\"display:block;border:0;max-width:290px;width:100%;height:auto;\">\n </a>\n`;\n\nconst html = `\n<div style=\"margin:0;padding:24px;background:#f3f1ed;font-family:Arial,sans-serif;color:#151515;\">\n <table role=\"presentation\" style=\"width:100%;max-width:680px;margin:0 auto;border-collapse:collapse;background:#ffffff;border-radius:14px;overflow:hidden;\">\n <tr>\n <td style=\"background:#101010;padding:24px 28px;\">\n <div style=\"font-size:13px;letter-spacing:0.18em;text-transform:uppercase;color:#caa06a;\">MC Cars</div>\n <h1 style=\"margin:10px 0 0 0;font-size:24px;line-height:1.2;color:#ffffff;\">Ihre Miete wurde freigegeben</h1>\n </td>\n </tr>\n <tr>\n <td style=\"padding:28px;\">\n <p style=\"margin:0 0 14px 0;font-size:16px;\">Guten Tag <strong>${safeName}</strong>,</p>\n <p style=\"margin:0 0 18px 0;font-size:15px;line-height:1.6;\">\n Ihre Buchung wurde auf Basis der von Ihnen bereitgestellten Informationen gepr\u00fcft und freigegeben.\n Nachfolgend finden Sie die Zahlungsanweisungen f\u00fcr Kaution und Mietbetrag.\n </p>\n\n <table role=\"presentation\" style=\"width:100%;border-collapse:collapse;background:#f8f8f8;border:1px solid #e8e8e8;border-radius:10px;overflow:hidden;margin:0 0 22px 0;\">\n <tr><td style=\"padding:12px 14px;border-bottom:1px solid #e8e8e8;width:44%;color:#666;\">Bestellnummer</td><td style=\"padding:12px 14px;border-bottom:1px solid #e8e8e8;\"><strong>${orderNumber}</strong></td></tr>\n <tr><td style=\"padding:12px 14px;border-bottom:1px solid #e8e8e8;width:44%;color:#666;\">Fahrzeug</td><td style=\"padding:12px 14px;border-bottom:1px solid #e8e8e8;\">${vehicle}</td></tr>\n <tr><td style=\"padding:12px 14px;border-bottom:1px solid #e8e8e8;width:44%;color:#666;\">Mietzeitraum</td><td style=\"padding:12px 14px;border-bottom:1px solid #e8e8e8;\">${rentalRange}</td></tr>\n <tr><td style=\"padding:12px 14px;border-bottom:1px solid #e8e8e8;width:44%;color:#666;\">Zwischensumme</td><td style=\"padding:12px 14px;border-bottom:1px solid #e8e8e8;\">${subtotalEur}</td></tr>\n <tr><td style=\"padding:12px 14px;border-bottom:1px solid #e8e8e8;width:44%;color:#666;\">MwSt.</td><td style=\"padding:12px 14px;border-bottom:1px solid #e8e8e8;\">${vatEur}</td></tr>\n <tr><td style=\"padding:12px 14px;border-bottom:1px solid #e8e8e8;width:44%;color:#666;\">Mietbetrag</td><td style=\"padding:12px 14px;border-bottom:1px solid #e8e8e8;\"><strong>${rentalEur}</strong></td></tr>\n <tr><td style=\"padding:12px 14px;width:44%;color:#666;\">Kaution</td><td style=\"padding:12px 14px;\"><strong>${depositEur}</strong></td></tr>\n </table>\n\n <h2 style=\"margin:0 0 10px 0;font-size:18px;color:#101010;\">1) Kaution bezahlen</h2>\n <p style=\"margin:0 0 12px 0;font-size:14px;line-height:1.6;\">Sie k\u00f6nnen die Kaution in H\u00f6he von <strong>${depositEur}</strong> per PayPal bezahlen:</p>\n ${paypalButton('__PAYPAL_KAUTION_LINK__', 'PayPal Kaution bezahlen')}\n <p style=\"margin:12px 0 0 0;font-size:14px;line-height:1.6;\">Alternativ ist Barzahlung m\u00f6glich. In diesem Fall antworten Sie bitte so schnell wie m\u00f6glich auf diese E-Mail, damit wir die \u00dcbergabe best\u00e4tigen k\u00f6nnen.</p>\n\n <h2 style=\"margin:22px 0 10px 0;font-size:18px;color:#101010;\">2) Mietbetrag bezahlen</h2>\n <p style=\"margin:0 0 12px 0;font-size:14px;line-height:1.6;\">Den Mietbetrag von <strong>${rentalEur}</strong> k\u00f6nnen Sie \u00fcber folgenden PayPal-Button bezahlen:</p>\n ${paypalButton('__PAYPAL_MIETE_LINK__', 'PayPal Mietbetrag bezahlen')}\n\n <p style=\"margin:20px 0 6px 0;font-size:14px;line-height:1.6;\">Falls Sie Fragen haben, antworten Sie einfach auf diese E-Mail.</p>\n <p style=\"margin:0;font-size:14px;line-height:1.6;\">Freundliche Gr\u00fc\u00dfe<br><strong>MC Cars Team</strong></p>\n </td>\n </tr>\n </table>\n</div>`;\n\nreturn {\n toEmail: item.email,\n subject,\n html,\n sales_order_id: item.id,\n};\n"
},
"id": "build-email",
"name": "Build Email",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [690, 300]
},
{
"parameters": {
"fromEmail": "office@mc-cars.at",
"toEmail": "={{ $json.toEmail }}",
"subject": "={{ $json.subject }}",
"html": "={{ $json.html }}",
"options": {
"appendAttribution": false
},
"continueOnFail": true
},
"id": "send-email",
"name": "Send Email",
"type": "n8n-nodes-base.emailSend",
"typeVersion": 2.1,
"position": [910, 300],
"credentials": {
"smtp": {
"id": "__SMTP_CREDENTIAL_ID__",
"name": "SMTP account"
}
}
},
{
"parameters": {
"operation": "executeQuery",
"query": "UPDATE public.sales_orders SET email_sent = 1, updated_at = now() WHERE id = '{{ $('Build Email').item.json.sales_order_id }}'::uuid",
"options": {}
},
"id": "update-email-1",
"name": "Update email_sent = 1",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [1260, 200],
"credentials": {
"postgres": {
"id": "__POSTGRES_CREDENTIAL_ID__",
"name": "Postgres account"
}
}
},
{
"parameters": {
"operation": "executeQuery",
"query": "UPDATE public.sales_orders SET email_sent = 2, updated_at = now() WHERE id = '{{ $('Build Email').item.json.sales_order_id }}'::uuid",
"options": {}
},
"id": "update-email-2",
"name": "Update email_sent = 2",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [1260, 400],
"credentials": {
"postgres": {
"id": "__POSTGRES_CREDENTIAL_ID__",
"name": "Postgres account"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "cond-error",
"leftValue": "={{ $json.error }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notExists"
}
}
],
"combinator": "and"
}
},
"id": "check-error",
"name": "IF",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [1130, 300]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={ \"ok\": true }",
"options": {}
},
"id": "respond-ok",
"name": "Respond OK",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [1480, 200]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={ \"ok\": false, \"error\": \"email_send_failed\" }",
"options": {
"responseCode": 500
}
},
"id": "respond-err",
"name": "Respond Error",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [1480, 400]
}
],
"pinData": {},
"connections": {
"Webhook": {
"main": [
[
{
"node": "Fetch Order Data",
"type": "main",
"index": 0
}
]
]
},
"Fetch Order Data": {
"main": [
[
{
"node": "Build Email",
"type": "main",
"index": 0
}
]
]
},
"Build Email": {
"main": [
[
{
"node": "Send Email",
"type": "main",
"index": 0
}
]
]
},
"Send Email": {
"main": [
[
{
"node": "IF",
"type": "main",
"index": 0
}
]
]
},
"IF": {
"main": [
[
{
"node": "Update email_sent = 1",
"type": "main",
"index": 0
}
],
[
{
"node": "Update email_sent = 2",
"type": "main",
"index": 0
}
]
]
},
"Update email_sent = 1": {
"main": [
[
{
"node": "Respond OK",
"type": "main",
"index": 0
}
]
]
},
"Update email_sent = 2": {
"main": [
[
{
"node": "Respond Error",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {
"executionOrder": "v1"
},
"versionId": "d3c4e5f6-1234-5678-90ab-cdef12345680",
"meta": {
"templateCredsSetupCompleted": true
},
"id": "M3nU4lW0rkFl0w03",
"tags": [
{
"name": "mc-cars"
}
]
}