diff --git a/README.md b/README.md index 44934ad..dcce379 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,15 @@ Self-hosted Supabase stack + bilingual (DE/EN) public website + lead-management | `post-init` | `postgres:15-alpine` | Idempotent bootstrap: seed admin + migrations | | `kong` | `kong:2.8.1` | Single API gateway at `:55521` | | `studio` | `supabase/studio` | Supabase dashboard (`:55530`) | -| `web` | `nginx:1.27-alpine` | Public site + admin panel (`:55580`) | +| `web` | `nginx:1.27-alpine` | Public website (`:55580`) | +| `web-admin` | `nginx:1.27-alpine` | Admin web entrypoint (`:55581`) | +| `n8n` | `n8nio/n8n:latest` | Automation UI/API (`:55590`) | +| `gotenberg` | `gotenberg/gotenberg:8` | DOCX/PDF conversion (internal only) | ## Requirements - Docker Engine with Compose v2 (or Portainer with Stacks) -- Free ports: `55521`, `55530`, `55532`, `55543`, `55580` +- Free ports: `55521`, `55530`, `55532`, `55543`, `55580`, `55581`, `55590` ## Run @@ -89,9 +92,11 @@ rm -rf /mnt/user/appdata/mc-cars/data/db # FULL DB wipe (re-runs fir | Purpose | URL | | ------------------------------- | --------------------------------- | | Public website | http://\:55580 | -| Admin panel | http://\:55580/admin.html | +| Admin web (dedicated nginx) | http://\:55581 | +| Admin page | http://\:55581/admin.html | | Supabase Studio | http://\:55530 | | API gateway (Kong) | http://\:55521 | +| n8n | http://\:55590 | | Postgres | `:55532` | > Admin access is deliberately **not** linked from the public site. Bookmark it. diff --git a/n8n/workflows/01-qualification-payment-email.json b/n8n/workflows/01-qualification-payment-email.json index e37a687..4dfa51b 100644 --- a/n8n/workflows/01-qualification-payment-email.json +++ b/n8n/workflows/01-qualification-payment-email.json @@ -1,5 +1,5 @@ { - "name": "Lead Qualified → Payment Email", + "name": "Lead Qualified -> Payment Email", "nodes": [ { "parameters": { @@ -11,7 +11,10 @@ "name": "Postgres Trigger", "type": "n8n-nodes-base.postgresTrigger", "typeVersion": 1, - "position": [250, 300], + "position": [ + 250, + 300 + ], "credentials": { "postgres": { "id": "1", @@ -29,7 +32,10 @@ "name": "Fetch Order Data", "type": "n8n-nodes-base.postgres", "typeVersion": 2.5, - "position": [470, 300], + "position": [ + 470, + 300 + ], "credentials": { "postgres": { "id": "1", @@ -39,18 +45,36 @@ }, { "parameters": { - "fromEmail": "info@mc-cars.at", - "toEmail": "={{ $json.email }}", - "subject": "MC Cars – Ihre Buchung {{ $json.order_number }} – Zahlungsanweisungen", + "mode": "runOnceForEachItem", + "language": "javaScript", + "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 orderNumber = item.order_number || \"\";\nconst dateFrom = formatDate(item.date_from);\nconst dateTo = formatDate(item.date_to);\nconst rentalRange = `${dateFrom} bis ${dateTo}`;\nconst paymentLink = `https://www.mc-cars.at/zahlung/${encodeURIComponent(orderNumber)}`;\n\nconst depositEur = formatEur(item.deposit_eur);\nconst rentalEur = formatEur(item.total_eur);\nconst subtotalEur = formatEur(item.subtotal_eur);\nconst vatEur = formatEur(item.vat_eur);\n\nconst safeName = item.name || \"Kundin/Kunde\";\nconst vehicle = item.vehicle_label || \"-\";\n\nconst subject = `MC Cars - Buchung bestaetigt (${orderNumber}) - Zahlungsinfos`;\n\nconst html = `\n
\n \n \n \n \n \n \n \n
\n
MC Cars
\n

Ihre Miete wurde freigegeben

\n
\n

Guten Tag ${safeName},

\n

\n Ihre Buchung wurde auf Basis der von Ihnen bereitgestellten Informationen geprueft und freigegeben.\n Nachfolgend finden Sie die Zahlungsanweisungen fuer Kaution und Mietbetrag.\n

\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Bestellnummer${orderNumber}
Fahrzeug${vehicle}
Mietzeitraum${rentalRange}
Zwischensumme${subtotalEur}
MwSt.${vatEur}
Mietbetrag${rentalEur}
Kaution${depositEur}
\n\n

1) Kaution per Ueberweisung

\n

Bitte ueberweisen Sie die Kaution in Hoehe von ${depositEur} mit folgendem Verwendungszweck: Kaution ${orderNumber}.

\n\n

2) Mietbetrag online bezahlen

\n

Den Mietbetrag von ${rentalEur} koennen Sie direkt ueber folgenden Link bezahlen:

\n\n

\n Zur Zahlung (${orderNumber})\n

\n\n

Falls Sie Fragen haben, antworten Sie einfach auf diese E-Mail.

\n

Freundliche Gruesse
MC Cars Team

\n
\n
`;\n\nreturn {\n toEmail: item.email,\n subject,\n html,\n};" + }, + "id": "build-payment-email", + "name": "Build Payment Email", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 690, + 300 + ] + }, + { + "parameters": { + "fromEmail": "office@mc-cars.at", + "toEmail": "={{ $json.toEmail }}", + "subject": "={{ $json.subject }}", "emailType": "html", - "html": "
\n
\n

MC Cars

\n
\n
\n

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

\n

vielen Dank für Ihre Buchung! Hier sind Ihre Buchungsdetails und Zahlungsanweisungen:

\n \n \n \n \n \n \n \n \n \n \n
Bestellnummer:{{ $json.order_number }}
Fahrzeug:{{ $json.vehicle_label }}
Zeitraum:{{ $json.date_from }} – {{ $json.date_to }}
Tage gesamt:{{ $json.total_days }}
Netto:€{{ $json.subtotal_eur }}
MwSt (19%):€{{ $json.vat_eur }}
Gesamtbetrag:€{{ $json.total_eur }}
Kaution:€{{ $json.deposit_eur }}
\n\n

Zahlungsanweisungen

\n \n

1. Kaution (€{{ $json.deposit_eur }})

\n

Bitte überweisen Sie die Kaution auf folgendes Konto:

\n
\n

Empfänger: MC Cars GmbH

\n

IBAN: AT00 0000 0000 0000 0000

\n

BIC: BKAUATWW

\n

Verwendungszweck: Kaution {{ $json.order_number }}

\n
\n\n

2. Mietbetrag (€{{ $json.total_eur }})

\n

Den Mietbetrag können Sie bequem online bezahlen:

\n \n

Oder überweisen Sie auf dasselbe Konto mit Verwendungszweck: Miete {{ $json.order_number }}

\n\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
", + "html": "={{ $json.html }}", "options": {} }, "id": "send-email", "name": "Send Payment Email", "type": "n8n-nodes-base.emailSend", "typeVersion": 2.1, - "position": [690, 300], + "position": [ + 910, + 300 + ], "credentials": { "smtp": { "id": "2", @@ -72,6 +96,17 @@ ] }, "Fetch Order Data": { + "main": [ + [ + { + "node": "Build Payment Email", + "type": "main", + "index": 0 + } + ] + ] + }, + "Build Payment Email": { "main": [ [ { diff --git a/n8n/workflows/README.md b/n8n/workflows/README.md index 7439b2a..3f6b42d 100644 --- a/n8n/workflows/README.md +++ b/n8n/workflows/README.md @@ -40,20 +40,26 @@ This folder contains exportable n8n workflow definitions for the MC Cars qualifi ### 2. Create SMTP credential in n8n - **Name:** `MC Cars SMTP` -- **Host:** your SMTP server (e.g. `smtp.mailgun.org`, `mail.mc-cars.at`) -- **Port:** `587` (TLS) or `465` (SSL) -- **User:** your SMTP username -- **Password:** your SMTP password -- **From:** `info@mc-cars.at` +- **Host:** `heracles.mxrouting.net` +- **Port:** `587` (STARTTLS) or `465` (SSL/TLS) +- **User:** `office@mc-cars.at` +- **Password:** use the mailbox password provided out-of-band (do not commit secrets to git) +- **From:** `office@mc-cars.at` -### 3. Import workflows +### 3. Mailbox reference (for future incoming-email workflows) +- **IMAP host:** `heracles.mxrouting.net` (port `993`, SSL/TLS) +- **POP3 host:** `heracles.mxrouting.net` (port `995`, SSL/TLS) +- **Username:** `office@mc-cars.at` +- **Password:** same mailbox password as SMTP + +### 4. Import workflows 1. Open n8n at http://localhost:55590 2. Go to **Workflows** → **Import from file** 3. Import `01-qualification-payment-email.json` 4. Import `02-mietvertrag-pdf-email.json` 5. Open each workflow → assign the credentials created above → **Activate** -### 4. Upload Mietvertrag template (optional) +### 5. Upload Mietvertrag template (optional) 1. Open Admin panel → **Einstellungen** tab 2. Upload a DOCX file in the "Mietvertrag-Vorlage" section 3. The template should contain these placeholders: