From d5a304ebe954759826f99ad3022e14f887556472 Mon Sep 17 00:00:00 2001 From: rogalik27 Date: Thu, 25 Jun 2026 09:33:53 +0200 Subject: [PATCH] n8n ingest swap: lead-intake & onboarding write to the DB Both flows now POST into the smb-crm DB API instead of appending to Sheets, so Postgres stays the source of truth and the DB->Sheets mirror keeps the Sheet current. Telegram notifications unchanged. - lead-intake: Webhook -> Build lead row -> POST /api/leads -> Telegram (dropped the direct Sheets append + the redundant activity-log nodes; the DB service logs + mirrors activity itself). - onboarding: Webhook -> Compute -> POST /api/clients (DB assigns C-id) -> Build project w/ returned id -> POST /api/projects -> Telegram. DB computes renewal_date; go-live = start + 14d. - Token injected at deploy time; repo keeps the __CRM_TOKEN__ placeholder. Verified end-to-end on the live webhooks: lead and client+project land in the DB and mirror to Sheets; test data cleaned; DB/Sheets consistent. Co-Authored-By: Claude Opus 4.8 --- TODO.md | 33 ++-- n8n/lead-intake.json | 180 ++++------------------ n8n/onboarding.json | 350 ++++++------------------------------------- 3 files changed, 94 insertions(+), 469 deletions(-) diff --git a/TODO.md b/TODO.md index f6bd3bc..9b96041 100644 --- a/TODO.md +++ b/TODO.md @@ -20,19 +20,24 @@ Implications to work through: replace with something hosting-agnostic (e.g. `hosting = google | selfhosted`, `plan = …`). - [ ] Onboarding form + workflow currently ask for Tier A/B — realign once the model is settled. -## Onboard dashboard → manage Leads & Clients (CRUD) -The operator onboarding page (https://onboard.mivanchenko.de) currently only **creates** records. -Extend it into a small back-office where I can **read, edit, delete and add** both Leads and -Clients directly: -- [ ] **Read:** pull and list the current `Leads` and `Clients` rows (via n8n webhook → Sheets - `values.get`, or Sheets API) in a clean table on the onboard page. -- [ ] **Add:** new Lead / new Client manually (Clients add already exists via the onboarding form; - add a lighter "new lead" form too). -- [ ] **Edit:** update a row in place (e.g. change a lead's status, fix a client's fee/renewal). -- [ ] **Delete:** cleanly remove a row (Sheets `deleteDimension`), so test/junk rows don't pile up - — replaces the current scratchpad `del_rows.py`/`del_lead.py` manual cleanup. -- [ ] Stays behind the existing Caddy basic-auth (operator-only; these are billable/CRM records). -- [ ] Needs new n8n webhooks (list/update/delete) keyed by `lead_id` / `client_id`. +## Onboard dashboard → manage Leads & Clients (CRUD) — DONE 2026-06-25 +Built as a DB-first back-office (architecture pivot: **Postgres is the source of truth**, Sheets +is a one-way mirror — see `backoffice/` and the `smb-db-source-of-truth` memory). Live at +**onboard.mivanchenko.de/crm** behind the existing basic-auth. +- [x] **Read:** Leads & Clients tables, served by `smb-crm` straight from Postgres. +- [x] **Add:** "+ Neu" modal per entity; auto-IDs (C-#### / L-epoch), renewal auto-compute. +- [x] **Edit:** per-row ✎ modal, token-protected PATCH. +- [x] **Delete:** per-row 🗑 (token-protected), audit-logged — replaces the scratchpad cleanup. +- [x] Behind Caddy basic-auth; mutations gated by `CRM_API_TOKEN`; all changes audit-logged. +- [x] One-way **DB→Sheets mirror** keeps the Sheet overview current after every change. +- [x] **n8n ingest swap:** `lead-intake` & `onboarding` now POST into the DB API (not Sheets); + Telegram notify unchanged. `renewal-reminder` still reads Sheets (fine — Sheets mirrors DB). + +Follow-ups (not yet done): +- [ ] Extend CRUD UI beyond Leads/Clients (projects, bookings, invoices, activity) if useful. +- [ ] Daily backup of the `smb-db` Postgres volume. +- [ ] Make the DB→Sheets overwrite atomic (currently clear-then-write; brief mid-write window a + reader could catch the cleared sheet — harmless for a human overview). ## Other - [ ] Harden n8n auth: compose still has deprecated `N8N_BASIC_AUTH_*` with password `password` @@ -49,3 +54,5 @@ Clients directly: - [x] 2026-06-25 — Pizzeria demo with online ordering added. - [x] 2026-06-25 — Barbershop "Google Kalender" dot-overlap styling fixed. - [x] 2026-06-25 — All three demo pages neutralised (stack-agnostic, non-stale dates). +- [x] 2026-06-25 — DB-first back office: Postgres source of truth + Sheets mirror + CRUD + n8n + ingest swap (lead-intake/onboarding → DB API). Live at onboard.mivanchenko.de/crm. diff --git a/n8n/lead-intake.json b/n8n/lead-intake.json index f246bf9..5134c1d 100644 --- a/n8n/lead-intake.json +++ b/n8n/lead-intake.json @@ -1,5 +1,5 @@ { - "name": "SMB \u00b7 Lead Intake", + "name": "SMB · Lead Intake", "nodes": [ { "parameters": { @@ -14,63 +14,39 @@ "name": "Webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 2, - "position": [ - 240, - 300 - ], + "position": [240, 300], "webhookId": "bb5f87d2-4647-4f38-9d6b-82a863158937" }, { "parameters": { - "jsCode": "const i=$input.first().json; const b=i.body||i;\nconst now=new Date().toISOString().replace('T',' ').slice(0,16);\nconst s=v=>{v=(v==null?'':String(v)); return /^[=+\\-@\\t\\r]/.test(v)?\"'\"+v:v;};\nreturn [{ json:{ lead_id:'L-'+Date.now(), received_at:now, client_id:s(b.client_id),\n source:s(b.source), name:s(b.name), contact:s(b.contact||b.email||b.phone),\n service_interest:s(b.service_interest||b.service), message:s(b.message), status:'new',\n notified:'TRUE' }}];" + "jsCode": "const i=$input.first().json; const b=i.body||i;\nconst now=new Date().toISOString().replace('T',' ').slice(0,16);\nconst s=v=>{v=(v==null?'':String(v)); return /^[=+\\-@\\t\\r]/.test(v)?\"'\"+v:v;};\nreturn [{ json:{ lead_id:'L-'+Date.now(), received_at:now, client_id:s(b.client_id),\n source:s(b.source), name:s(b.name), contact:s(b.contact||b.email||b.phone),\n service_interest:s(b.service_interest||b.service), message:s(b.message), status:'new',\n notified:true }}];" }, "id": "a5d0db99-93c2-46aa-8c0f-af1f4ee7300c", "name": "Build lead row", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [ - 460, - 300 - ] + "position": [460, 300] }, { "parameters": { - "authentication": "serviceAccount", - "resource": "sheet", - "operation": "append", - "documentId": { - "__rl": true, - "value": "1raMSWRZw_JfHlWqOb3LbhaQ6LWx0VGblxIV4Z2pSzp8", - "mode": "id" - }, - "sheetName": { - "__rl": true, - "value": "571719114", - "mode": "list", - "cachedResultName": "Leads" - }, - "columns": { - "mappingMode": "autoMapInputData", - "value": {}, - "matchingColumns": [], - "schema": [] + "method": "POST", + "url": "http://smb-crm:8080/api/leads", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { "name": "X-CRM-Token", "value": "__CRM_TOKEN__" } + ] }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify($json) }}", "options": {} }, - "id": "04c41d84-fb8c-41e3-aaba-5db270096a0c", - "name": "Append to Leads", - "type": "n8n-nodes-base.googleSheets", - "typeVersion": 4.5, - "position": [ - 680, - 300 - ], - "credentials": { - "googleApi": { - "id": "45TWPyl1wVk2Vxdm", - "name": "SMB Google (service account)" - } - }, + "id": "11111111-2222-3333-4444-555555555555", + "name": "Save to CRM (DB)", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [680, 300], "retryOnFail": true, "maxTries": 4, "waitBetweenTries": 3000 @@ -80,7 +56,7 @@ "resource": "message", "operation": "sendMessage", "chatId": "5499280257", - "text": "=\ud83c\udd95 Neue Anfrage\nName: {{ $('Build lead row').item.json.name || '\u2014' }}\nKontakt: {{ $('Build lead row').item.json.contact || '\u2014' }}\nInteresse: {{ $('Build lead row').item.json.service_interest || '\u2014' }}\nQuelle: {{ $('Build lead row').item.json.source || '\u2014' }}\nClient: {{ $('Build lead row').item.json.client_id || '\u2014' }}\nNachricht: {{ $('Build lead row').item.json.message || '\u2014' }}", + "text": "=🆕 Neue Anfrage\nName: {{ $('Build lead row').item.json.name || '—' }}\nKontakt: {{ $('Build lead row').item.json.contact || '—' }}\nInteresse: {{ $('Build lead row').item.json.service_interest || '—' }}\nQuelle: {{ $('Build lead row').item.json.source || '—' }}\nClient: {{ $('Build lead row').item.json.client_id || '—' }}\nNachricht: {{ $('Build lead row').item.json.message || '—' }}", "additionalFields": { "appendAttribution": false } @@ -89,128 +65,24 @@ "name": "Notify Telegram", "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, - "position": [ - 900, - 300 - ], + "position": [900, 300], "credentials": { "telegramApi": { "id": "d9s4Ec5ocEH8FAxh", "name": "SMB Telegram (leads bot)" } } - }, - { - "parameters": { - "jsCode": "const lead=$('Build lead row').first().json;\nconst now=new Date().toISOString().replace('T',' ').slice(0,16);\nreturn [{ json:{ ts:now, workflow:'lead-intake', client_id:lead.client_id||'',\n action:'lead captured + notified', detail:(lead.name||'')+' / '+(lead.contact||''), result:'ok' }}];" - }, - "id": "0c8f8de9-565a-40f3-a24f-cd1833545c4d", - "name": "Build activity", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 1120, - 300 - ] - }, - { - "parameters": { - "authentication": "serviceAccount", - "resource": "sheet", - "operation": "append", - "documentId": { - "__rl": true, - "value": "1raMSWRZw_JfHlWqOb3LbhaQ6LWx0VGblxIV4Z2pSzp8", - "mode": "id" - }, - "sheetName": { - "__rl": true, - "value": "170940481", - "mode": "list", - "cachedResultName": "Activity Log" - }, - "columns": { - "mappingMode": "autoMapInputData", - "value": {}, - "matchingColumns": [], - "schema": [] - }, - "options": {} - }, - "id": "6b4bf9d8-e850-473e-85d1-45aaa709d06a", - "name": "Append to Activity Log", - "type": "n8n-nodes-base.googleSheets", - "typeVersion": 4.5, - "position": [ - 1340, - 300 - ], - "credentials": { - "googleApi": { - "id": "45TWPyl1wVk2Vxdm", - "name": "SMB Google (service account)" - } - }, - "retryOnFail": true, - "maxTries": 4, - "waitBetweenTries": 3000 } ], "connections": { "Webhook": { - "main": [ - [ - { - "node": "Build lead row", - "type": "main", - "index": 0 - } - ] - ] + "main": [[{ "node": "Build lead row", "type": "main", "index": 0 }]] }, "Build lead row": { - "main": [ - [ - { - "node": "Append to Leads", - "type": "main", - "index": 0 - } - ] - ] + "main": [[{ "node": "Save to CRM (DB)", "type": "main", "index": 0 }]] }, - "Append to Leads": { - "main": [ - [ - { - "node": "Notify Telegram", - "type": "main", - "index": 0 - } - ] - ] - }, - "Notify Telegram": { - "main": [ - [ - { - "node": "Build activity", - "type": "main", - "index": 0 - } - ] - ] - }, - "Build activity": { - "main": [ - [ - { - "node": "Append to Activity Log", - "type": "main", - "index": 0 - } - ] - ] + "Save to CRM (DB)": { + "main": [[{ "node": "Notify Telegram", "type": "main", "index": 0 }]] } }, "settings": { @@ -218,4 +90,4 @@ "callerPolicy": "workflowsFromSameOwner", "availableInMCP": false } -} \ No newline at end of file +} diff --git a/n8n/onboarding.json b/n8n/onboarding.json index d078732..029ab85 100644 --- a/n8n/onboarding.json +++ b/n8n/onboarding.json @@ -1,232 +1,80 @@ { - "name": "SMB \u00b7 Onboarding", + "name": "SMB · Onboarding", "nodes": [ { "parameters": { "httpMethod": "POST", "path": "onboard", "responseMode": "onReceived", - "options": { - "allowedOrigins": "*" - } + "options": { "allowedOrigins": "*" } }, "id": "d1a08bba-5cc5-48fa-8063-bda2d58cab15", "name": "Onboard webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 2, - "position": [ - 240, - 300 - ], + "position": [240, 300], "webhookId": "96c28466-ee4b-4aa5-8f06-52d098b497a7" }, { "parameters": { - "authentication": "serviceAccount", - "resource": "sheet", - "operation": "read", - "documentId": { - "__rl": true, - "value": "1raMSWRZw_JfHlWqOb3LbhaQ6LWx0VGblxIV4Z2pSzp8", - "mode": "id" - }, - "sheetName": { - "__rl": true, - "value": "470934735", - "mode": "list", - "cachedResultName": "Clients" - }, - "options": {} - }, - "id": "92d09e48-6cac-43fc-b203-5dde0d08f61a", - "name": "Read Clients", - "type": "n8n-nodes-base.googleSheets", - "typeVersion": 4.5, - "position": [ - 460, - 300 - ], - "credentials": { - "googleApi": { - "id": "45TWPyl1wVk2Vxdm", - "name": "SMB Google (service account)" - } - } - }, - { - "parameters": { - "jsCode": "\nconst w=$('Onboard webhook').first().json; const body=w.body||w;\nconst rows=$('Read Clients').all().map(i=>i.json);\nlet maxC=0;\nfor(const r of rows){const m=String(r.client_id||'').match(/C-(\\d+)/);if(m)maxC=Math.max(maxC,+m[1]);}\nconst cid='C-'+String(maxC+1).padStart(4,'0');\nconst s=v=>{v=(v==null?'':String(v));return /^[=+\\-@\\t\\r]/.test(v)?\"'\"+v:v;};\nconst now=new Date();\nconst nowStr=now.toISOString().replace('T',' ').slice(0,16);\nfunction pd(v){if(!v)return null;const m=String(v).match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if(m)return new Date(Date.UTC(+m[1],+m[2]-1,+m[3]));const d=new Date(v);return isNaN(d)?null:d;}\nconst cycle=String(body.billing_cycle||'monthly').toLowerCase();\nconst start=pd(body.start_date)||new Date(Date.UTC(now.getUTCFullYear(),now.getUTCMonth(),now.getUTCDate()));\nconst startStr=start.toISOString().slice(0,10);\nlet renewal='';\nif(cycle==='monthly'){const d=new Date(start);d.setUTCMonth(d.getUTCMonth()+1);renewal=d.toISOString().slice(0,10);}\nelse if(cycle==='yearly'){const d=new Date(start);d.setUTCFullYear(d.getUTCFullYear()+1);renewal=d.toISOString().slice(0,10);}\nconst tier=String(body.tier||'A').toUpperCase().includes('B')?'B':'A';\nconst services=String(body.services||'site, booking, leads');\nconst client={client_id:cid,business_name:s(body.business_name),owner_name:s(body.owner_name),\n email:s(body.email),phone:s(body.phone),niche:s(body.niche),tier,status:'onboarding',\n domain:s(body.domain),stack_notes:s(body.stack_notes),vault_ref:s(body.vault_ref),\n services:s(services),billing_cycle:cycle,monthly_fee_eur:(body.monthly_fee_eur||''),\n start_date:startStr,renewal_date:renewal,created_at:nowStr,notes:s(body.notes)};\nconst checklist=services.split(',').map(x=>x.trim()).filter(Boolean).map(x=>x+'\u2610').join(' ');\nconst gl=new Date(start);gl.setUTCDate(gl.getUTCDate()+14);const goLive=gl.toISOString().slice(0,10);\nconst projects=[{project_id:'P-'+Date.now(),client_id:cid,deliverable:'Onboarding & Setup',\n tier,checklist,go_live_date:goLive,status:'todo'}];\nconst activity={ts:nowStr,workflow:'onboarding',client_id:cid,action:'client onboarded',\n detail:(client.business_name||'')+' / Tier '+tier+' / '+services,result:'ok'};\nconst summary='\u2705 Neuer Kunde angelegt: '+cid+'\\n'+(client.business_name||'')+' \u2014 Tier '+tier+\n '\\nServices: '+services+'\\nGeb\u00fchr: '+(client.monthly_fee_eur||'\u2014')+'\u20ac/'+cycle+\n '\\nVerl\u00e4ngerung: '+(renewal||'\u2014')+'\\nGo-Live Ziel: '+goLive;\nreturn [{json:{client,projects,activity,summary,cid}}];\n" + "jsCode": "const w=$('Onboard webhook').first().json; const body=w.body||w;\nconst s=v=>{v=(v==null?'':String(v));return /^[=+\\-@\\t\\r]/.test(v)?\"'\"+v:v;};\nconst now=new Date();\nfunction pd(v){if(!v)return null;const m=String(v).match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if(m)return new Date(Date.UTC(+m[1],+m[2]-1,+m[3]));const d=new Date(v);return isNaN(d)?null:d;}\nconst cycle=String(body.billing_cycle||'monthly').toLowerCase();\nconst start=pd(body.start_date)||new Date(Date.UTC(now.getUTCFullYear(),now.getUTCMonth(),now.getUTCDate()));\nconst startStr=start.toISOString().slice(0,10);\nconst tier=String(body.tier||'A').toUpperCase().includes('B')?'B':'A';\nconst services=String(body.services||'site, booking, leads');\nconst client={business_name:s(body.business_name),owner_name:s(body.owner_name),\n email:s(body.email),phone:s(body.phone),niche:s(body.niche),tier,status:'onboarding',\n domain:s(body.domain),stack_notes:s(body.stack_notes),vault_ref:s(body.vault_ref),\n services:s(services),billing_cycle:cycle,monthly_fee_eur:(body.monthly_fee_eur||''),\n start_date:startStr,notes:s(body.notes)};\nconst checklist=services.split(',').map(x=>x.trim()).filter(Boolean).map(x=>x+'\\u2610').join(' ');\nconst gl=new Date(start);gl.setUTCDate(gl.getUTCDate()+14);const goLive=gl.toISOString().slice(0,10);\nconst project={project_id:'P-'+Date.now(),deliverable:'Onboarding & Setup',tier,checklist,\n go_live_date:goLive,status:'todo'};\nreturn [{json:{client,project,tier,services,cycle,monthly_fee_eur:(body.monthly_fee_eur||''),\n business_name:client.business_name}}];" }, "id": "fe9a0f5d-c6f8-4328-a98e-edc022582ec3", - "name": "Compute IDs", + "name": "Compute", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [ - 680, - 300 - ] + "position": [460, 300] }, { "parameters": { - "jsCode": "return [{json: $('Compute IDs').first().json.client}];" - }, - "id": "4abc9b06-83ec-4d90-bb53-020e2dfade0d", - "name": "Map client", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 900, - 300 - ] - }, - { - "parameters": { - "authentication": "serviceAccount", - "resource": "sheet", - "operation": "append", - "documentId": { - "__rl": true, - "value": "1raMSWRZw_JfHlWqOb3LbhaQ6LWx0VGblxIV4Z2pSzp8", - "mode": "id" + "method": "POST", + "url": "http://smb-crm:8080/api/clients", + "sendHeaders": true, + "headerParameters": { + "parameters": [ { "name": "X-CRM-Token", "value": "__CRM_TOKEN__" } ] }, - "sheetName": { - "__rl": true, - "value": "470934735", - "mode": "list", - "cachedResultName": "Clients" - }, - "options": {}, - "columns": { - "mappingMode": "autoMapInputData", - "value": {}, - "matchingColumns": [], - "schema": [] - } - }, - "id": "330f5cec-c1dd-4197-92bf-01c66f9a795a", - "name": "Append Clients", - "type": "n8n-nodes-base.googleSheets", - "typeVersion": 4.5, - "position": [ - 1120, - 300 - ], - "credentials": { - "googleApi": { - "id": "45TWPyl1wVk2Vxdm", - "name": "SMB Google (service account)" - } + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify($json.client) }}", + "options": {} }, + "id": "22222222-3333-4444-5555-666666666666", + "name": "Save client", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [680, 300], "retryOnFail": true, "maxTries": 4, "waitBetweenTries": 3000 }, { "parameters": { - "jsCode": "return $('Compute IDs').first().json.projects.map(p=>({json:p}));" + "jsCode": "const c=$('Compute').first().json;\nconst cid=$('Save client').first().json.added;\nconst p=Object.assign({}, c.project, {client_id:cid});\nreturn [{json:p}];" }, - "id": "cc50083b-44f6-450b-ada3-ad3f2142441b", - "name": "Map projects", + "id": "33333333-4444-5555-6666-777777777777", + "name": "Build project", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [ - 1340, - 300 - ] + "position": [900, 300] }, { "parameters": { - "authentication": "serviceAccount", - "resource": "sheet", - "operation": "append", - "documentId": { - "__rl": true, - "value": "1raMSWRZw_JfHlWqOb3LbhaQ6LWx0VGblxIV4Z2pSzp8", - "mode": "id" + "method": "POST", + "url": "http://smb-crm:8080/api/projects", + "sendHeaders": true, + "headerParameters": { + "parameters": [ { "name": "X-CRM-Token", "value": "__CRM_TOKEN__" } ] }, - "sheetName": { - "__rl": true, - "value": "619112786", - "mode": "list", - "cachedResultName": "Projects" - }, - "options": {}, - "columns": { - "mappingMode": "autoMapInputData", - "value": {}, - "matchingColumns": [], - "schema": [] - } - }, - "id": "83b26e24-edd8-4663-b61c-aa5e25b33842", - "name": "Append Projects", - "type": "n8n-nodes-base.googleSheets", - "typeVersion": 4.5, - "position": [ - 1560, - 300 - ], - "credentials": { - "googleApi": { - "id": "45TWPyl1wVk2Vxdm", - "name": "SMB Google (service account)" - } - }, - "retryOnFail": true, - "maxTries": 4, - "waitBetweenTries": 3000 - }, - { - "parameters": { - "jsCode": "return [{json: $('Compute IDs').first().json.activity}];" - }, - "id": "8d3c0640-e186-4813-815e-8f807d7b6f4f", - "name": "Map activity", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 1780, - 300 - ] - }, - { - "parameters": { - "authentication": "serviceAccount", - "resource": "sheet", - "operation": "append", - "documentId": { - "__rl": true, - "value": "1raMSWRZw_JfHlWqOb3LbhaQ6LWx0VGblxIV4Z2pSzp8", - "mode": "id" - }, - "sheetName": { - "__rl": true, - "value": "170940481", - "mode": "list", - "cachedResultName": "Activity Log" - }, - "options": {}, - "columns": { - "mappingMode": "autoMapInputData", - "value": {}, - "matchingColumns": [], - "schema": [] - } - }, - "id": "c8773711-99d4-4722-9a13-5c8c9afa7060", - "name": "Append Activity", - "type": "n8n-nodes-base.googleSheets", - "typeVersion": 4.5, - "position": [ - 1780, - 460 - ], - "credentials": { - "googleApi": { - "id": "45TWPyl1wVk2Vxdm", - "name": "SMB Google (service account)" - } + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify($json) }}", + "options": {} }, + "id": "44444444-5555-6666-7777-888888888888", + "name": "Save project", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [1120, 300], "retryOnFail": true, "maxTries": 4, "waitBetweenTries": 3000 @@ -236,131 +84,29 @@ "resource": "message", "operation": "sendMessage", "chatId": "5499280257", - "text": "={{ $('Compute IDs').first().json.summary }}", - "additionalFields": { - "appendAttribution": false - } + "text": "=✅ Neuer Kunde angelegt: {{ $('Save client').item.json.added }}\n{{ $('Compute').item.json.business_name }} — Tier {{ $('Compute').item.json.tier }}\nServices: {{ $('Compute').item.json.services }}\nGebühr: {{ $('Compute').item.json.monthly_fee_eur || '—' }}€/{{ $('Compute').item.json.cycle }}\nGo-Live Ziel: {{ $('Build project').item.json.go_live_date }}", + "additionalFields": { "appendAttribution": false } }, "id": "ae19e543-1a6b-4466-8af8-bb436f51091b", "name": "Notify Telegram", "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, - "position": [ - 2000, - 300 - ], + "position": [1340, 300], "credentials": { - "telegramApi": { - "id": "d9s4Ec5ocEH8FAxh", - "name": "SMB Telegram (leads bot)" - } + "telegramApi": { "id": "d9s4Ec5ocEH8FAxh", "name": "SMB Telegram (leads bot)" } } } ], "connections": { - "Onboard webhook": { - "main": [ - [ - { - "node": "Read Clients", - "type": "main", - "index": 0 - } - ] - ] - }, - "Read Clients": { - "main": [ - [ - { - "node": "Compute IDs", - "type": "main", - "index": 0 - } - ] - ] - }, - "Compute IDs": { - "main": [ - [ - { - "node": "Map client", - "type": "main", - "index": 0 - } - ] - ] - }, - "Map client": { - "main": [ - [ - { - "node": "Append Clients", - "type": "main", - "index": 0 - } - ] - ] - }, - "Append Clients": { - "main": [ - [ - { - "node": "Map projects", - "type": "main", - "index": 0 - } - ] - ] - }, - "Map projects": { - "main": [ - [ - { - "node": "Append Projects", - "type": "main", - "index": 0 - } - ] - ] - }, - "Append Projects": { - "main": [ - [ - { - "node": "Map activity", - "type": "main", - "index": 0 - } - ] - ] - }, - "Map activity": { - "main": [ - [ - { - "node": "Append Activity", - "type": "main", - "index": 0 - } - ] - ] - }, - "Append Activity": { - "main": [ - [ - { - "node": "Notify Telegram", - "type": "main", - "index": 0 - } - ] - ] - } + "Onboard webhook": { "main": [[{ "node": "Compute", "type": "main", "index": 0 }]] }, + "Compute": { "main": [[{ "node": "Save client", "type": "main", "index": 0 }]] }, + "Save client": { "main": [[{ "node": "Build project", "type": "main", "index": 0 }]] }, + "Build project": { "main": [[{ "node": "Save project", "type": "main", "index": 0 }]] }, + "Save project": { "main": [[{ "node": "Notify Telegram", "type": "main", "index": 0 }]] } }, "settings": { "executionOrder": "v1", "callerPolicy": "workflowsFromSameOwner", "availableInMCP": false } -} \ No newline at end of file +}