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 <noreply@anthropic.com>
This commit is contained in:
@@ -20,19 +20,24 @@ Implications to work through:
|
|||||||
replace with something hosting-agnostic (e.g. `hosting = google | selfhosted`, `plan = …`).
|
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.
|
- [ ] Onboarding form + workflow currently ask for Tier A/B — realign once the model is settled.
|
||||||
|
|
||||||
## Onboard dashboard → manage Leads & Clients (CRUD)
|
## Onboard dashboard → manage Leads & Clients (CRUD) — DONE 2026-06-25
|
||||||
The operator onboarding page (https://onboard.mivanchenko.de) currently only **creates** records.
|
Built as a DB-first back-office (architecture pivot: **Postgres is the source of truth**, Sheets
|
||||||
Extend it into a small back-office where I can **read, edit, delete and add** both Leads and
|
is a one-way mirror — see `backoffice/` and the `smb-db-source-of-truth` memory). Live at
|
||||||
Clients directly:
|
**onboard.mivanchenko.de/crm** behind the existing basic-auth.
|
||||||
- [ ] **Read:** pull and list the current `Leads` and `Clients` rows (via n8n webhook → Sheets
|
- [x] **Read:** Leads & Clients tables, served by `smb-crm` straight from Postgres.
|
||||||
`values.get`, or Sheets API) in a clean table on the onboard page.
|
- [x] **Add:** "+ Neu" modal per entity; auto-IDs (C-#### / L-epoch), renewal auto-compute.
|
||||||
- [ ] **Add:** new Lead / new Client manually (Clients add already exists via the onboarding form;
|
- [x] **Edit:** per-row ✎ modal, token-protected PATCH.
|
||||||
add a lighter "new lead" form too).
|
- [x] **Delete:** per-row 🗑 (token-protected), audit-logged — replaces the scratchpad cleanup.
|
||||||
- [ ] **Edit:** update a row in place (e.g. change a lead's status, fix a client's fee/renewal).
|
- [x] Behind Caddy basic-auth; mutations gated by `CRM_API_TOKEN`; all changes audit-logged.
|
||||||
- [ ] **Delete:** cleanly remove a row (Sheets `deleteDimension`), so test/junk rows don't pile up
|
- [x] One-way **DB→Sheets mirror** keeps the Sheet overview current after every change.
|
||||||
— replaces the current scratchpad `del_rows.py`/`del_lead.py` manual cleanup.
|
- [x] **n8n ingest swap:** `lead-intake` & `onboarding` now POST into the DB API (not Sheets);
|
||||||
- [ ] Stays behind the existing Caddy basic-auth (operator-only; these are billable/CRM records).
|
Telegram notify unchanged. `renewal-reminder` still reads Sheets (fine — Sheets mirrors DB).
|
||||||
- [ ] Needs new n8n webhooks (list/update/delete) keyed by `lead_id` / `client_id`.
|
|
||||||
|
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
|
## Other
|
||||||
- [ ] Harden n8n auth: compose still has deprecated `N8N_BASIC_AUTH_*` with password `password`
|
- [ ] 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 — Pizzeria demo with online ordering added.
|
||||||
- [x] 2026-06-25 — Barbershop "Google Kalender" dot-overlap styling fixed.
|
- [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 — 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.
|
||||||
|
|||||||
+26
-154
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "SMB \u00b7 Lead Intake",
|
"name": "SMB · Lead Intake",
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -14,63 +14,39 @@
|
|||||||
"name": "Webhook",
|
"name": "Webhook",
|
||||||
"type": "n8n-nodes-base.webhook",
|
"type": "n8n-nodes-base.webhook",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [240, 300],
|
||||||
240,
|
|
||||||
300
|
|
||||||
],
|
|
||||||
"webhookId": "bb5f87d2-4647-4f38-9d6b-82a863158937"
|
"webhookId": "bb5f87d2-4647-4f38-9d6b-82a863158937"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"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",
|
"id": "a5d0db99-93c2-46aa-8c0f-af1f4ee7300c",
|
||||||
"name": "Build lead row",
|
"name": "Build lead row",
|
||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [460, 300]
|
||||||
460,
|
|
||||||
300
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"authentication": "serviceAccount",
|
"method": "POST",
|
||||||
"resource": "sheet",
|
"url": "http://smb-crm:8080/api/leads",
|
||||||
"operation": "append",
|
"sendHeaders": true,
|
||||||
"documentId": {
|
"headerParameters": {
|
||||||
"__rl": true,
|
"parameters": [
|
||||||
"value": "1raMSWRZw_JfHlWqOb3LbhaQ6LWx0VGblxIV4Z2pSzp8",
|
{ "name": "X-CRM-Token", "value": "__CRM_TOKEN__" }
|
||||||
"mode": "id"
|
]
|
||||||
},
|
|
||||||
"sheetName": {
|
|
||||||
"__rl": true,
|
|
||||||
"value": "571719114",
|
|
||||||
"mode": "list",
|
|
||||||
"cachedResultName": "Leads"
|
|
||||||
},
|
|
||||||
"columns": {
|
|
||||||
"mappingMode": "autoMapInputData",
|
|
||||||
"value": {},
|
|
||||||
"matchingColumns": [],
|
|
||||||
"schema": []
|
|
||||||
},
|
},
|
||||||
|
"sendBody": true,
|
||||||
|
"specifyBody": "json",
|
||||||
|
"jsonBody": "={{ JSON.stringify($json) }}",
|
||||||
"options": {}
|
"options": {}
|
||||||
},
|
},
|
||||||
"id": "04c41d84-fb8c-41e3-aaba-5db270096a0c",
|
"id": "11111111-2222-3333-4444-555555555555",
|
||||||
"name": "Append to Leads",
|
"name": "Save to CRM (DB)",
|
||||||
"type": "n8n-nodes-base.googleSheets",
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
"typeVersion": 4.5,
|
"typeVersion": 4.2,
|
||||||
"position": [
|
"position": [680, 300],
|
||||||
680,
|
|
||||||
300
|
|
||||||
],
|
|
||||||
"credentials": {
|
|
||||||
"googleApi": {
|
|
||||||
"id": "45TWPyl1wVk2Vxdm",
|
|
||||||
"name": "SMB Google (service account)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"retryOnFail": true,
|
"retryOnFail": true,
|
||||||
"maxTries": 4,
|
"maxTries": 4,
|
||||||
"waitBetweenTries": 3000
|
"waitBetweenTries": 3000
|
||||||
@@ -80,7 +56,7 @@
|
|||||||
"resource": "message",
|
"resource": "message",
|
||||||
"operation": "sendMessage",
|
"operation": "sendMessage",
|
||||||
"chatId": "5499280257",
|
"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": {
|
"additionalFields": {
|
||||||
"appendAttribution": false
|
"appendAttribution": false
|
||||||
}
|
}
|
||||||
@@ -89,128 +65,24 @@
|
|||||||
"name": "Notify Telegram",
|
"name": "Notify Telegram",
|
||||||
"type": "n8n-nodes-base.telegram",
|
"type": "n8n-nodes-base.telegram",
|
||||||
"typeVersion": 1.2,
|
"typeVersion": 1.2,
|
||||||
"position": [
|
"position": [900, 300],
|
||||||
900,
|
|
||||||
300
|
|
||||||
],
|
|
||||||
"credentials": {
|
"credentials": {
|
||||||
"telegramApi": {
|
"telegramApi": {
|
||||||
"id": "d9s4Ec5ocEH8FAxh",
|
"id": "d9s4Ec5ocEH8FAxh",
|
||||||
"name": "SMB Telegram (leads bot)"
|
"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": {
|
"connections": {
|
||||||
"Webhook": {
|
"Webhook": {
|
||||||
"main": [
|
"main": [[{ "node": "Build lead row", "type": "main", "index": 0 }]]
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Build lead row",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"Build lead row": {
|
"Build lead row": {
|
||||||
"main": [
|
"main": [[{ "node": "Save to CRM (DB)", "type": "main", "index": 0 }]]
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Append to Leads",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"Append to Leads": {
|
"Save to CRM (DB)": {
|
||||||
"main": [
|
"main": [[{ "node": "Notify Telegram", "type": "main", "index": 0 }]]
|
||||||
[
|
|
||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
@@ -218,4 +90,4 @@
|
|||||||
"callerPolicy": "workflowsFromSameOwner",
|
"callerPolicy": "workflowsFromSameOwner",
|
||||||
"availableInMCP": false
|
"availableInMCP": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-302
@@ -1,232 +1,80 @@
|
|||||||
{
|
{
|
||||||
"name": "SMB \u00b7 Onboarding",
|
"name": "SMB · Onboarding",
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"httpMethod": "POST",
|
"httpMethod": "POST",
|
||||||
"path": "onboard",
|
"path": "onboard",
|
||||||
"responseMode": "onReceived",
|
"responseMode": "onReceived",
|
||||||
"options": {
|
"options": { "allowedOrigins": "*" }
|
||||||
"allowedOrigins": "*"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"id": "d1a08bba-5cc5-48fa-8063-bda2d58cab15",
|
"id": "d1a08bba-5cc5-48fa-8063-bda2d58cab15",
|
||||||
"name": "Onboard webhook",
|
"name": "Onboard webhook",
|
||||||
"type": "n8n-nodes-base.webhook",
|
"type": "n8n-nodes-base.webhook",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [240, 300],
|
||||||
240,
|
|
||||||
300
|
|
||||||
],
|
|
||||||
"webhookId": "96c28466-ee4b-4aa5-8f06-52d098b497a7"
|
"webhookId": "96c28466-ee4b-4aa5-8f06-52d098b497a7"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"authentication": "serviceAccount",
|
"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}}];"
|
||||||
"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"
|
|
||||||
},
|
},
|
||||||
"id": "fe9a0f5d-c6f8-4328-a98e-edc022582ec3",
|
"id": "fe9a0f5d-c6f8-4328-a98e-edc022582ec3",
|
||||||
"name": "Compute IDs",
|
"name": "Compute",
|
||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [460, 300]
|
||||||
680,
|
|
||||||
300
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"jsCode": "return [{json: $('Compute IDs').first().json.client}];"
|
"method": "POST",
|
||||||
},
|
"url": "http://smb-crm:8080/api/clients",
|
||||||
"id": "4abc9b06-83ec-4d90-bb53-020e2dfade0d",
|
"sendHeaders": true,
|
||||||
"name": "Map client",
|
"headerParameters": {
|
||||||
"type": "n8n-nodes-base.code",
|
"parameters": [ { "name": "X-CRM-Token", "value": "__CRM_TOKEN__" } ]
|
||||||
"typeVersion": 2,
|
|
||||||
"position": [
|
|
||||||
900,
|
|
||||||
300
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"authentication": "serviceAccount",
|
|
||||||
"resource": "sheet",
|
|
||||||
"operation": "append",
|
|
||||||
"documentId": {
|
|
||||||
"__rl": true,
|
|
||||||
"value": "1raMSWRZw_JfHlWqOb3LbhaQ6LWx0VGblxIV4Z2pSzp8",
|
|
||||||
"mode": "id"
|
|
||||||
},
|
},
|
||||||
"sheetName": {
|
"sendBody": true,
|
||||||
"__rl": true,
|
"specifyBody": "json",
|
||||||
"value": "470934735",
|
"jsonBody": "={{ JSON.stringify($json.client) }}",
|
||||||
"mode": "list",
|
"options": {}
|
||||||
"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)"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
"id": "22222222-3333-4444-5555-666666666666",
|
||||||
|
"name": "Save client",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.2,
|
||||||
|
"position": [680, 300],
|
||||||
"retryOnFail": true,
|
"retryOnFail": true,
|
||||||
"maxTries": 4,
|
"maxTries": 4,
|
||||||
"waitBetweenTries": 3000
|
"waitBetweenTries": 3000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"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",
|
"id": "33333333-4444-5555-6666-777777777777",
|
||||||
"name": "Map projects",
|
"name": "Build project",
|
||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [900, 300]
|
||||||
1340,
|
|
||||||
300
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"authentication": "serviceAccount",
|
"method": "POST",
|
||||||
"resource": "sheet",
|
"url": "http://smb-crm:8080/api/projects",
|
||||||
"operation": "append",
|
"sendHeaders": true,
|
||||||
"documentId": {
|
"headerParameters": {
|
||||||
"__rl": true,
|
"parameters": [ { "name": "X-CRM-Token", "value": "__CRM_TOKEN__" } ]
|
||||||
"value": "1raMSWRZw_JfHlWqOb3LbhaQ6LWx0VGblxIV4Z2pSzp8",
|
|
||||||
"mode": "id"
|
|
||||||
},
|
},
|
||||||
"sheetName": {
|
"sendBody": true,
|
||||||
"__rl": true,
|
"specifyBody": "json",
|
||||||
"value": "619112786",
|
"jsonBody": "={{ JSON.stringify($json) }}",
|
||||||
"mode": "list",
|
"options": {}
|
||||||
"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)"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
"id": "44444444-5555-6666-7777-888888888888",
|
||||||
|
"name": "Save project",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.2,
|
||||||
|
"position": [1120, 300],
|
||||||
"retryOnFail": true,
|
"retryOnFail": true,
|
||||||
"maxTries": 4,
|
"maxTries": 4,
|
||||||
"waitBetweenTries": 3000
|
"waitBetweenTries": 3000
|
||||||
@@ -236,131 +84,29 @@
|
|||||||
"resource": "message",
|
"resource": "message",
|
||||||
"operation": "sendMessage",
|
"operation": "sendMessage",
|
||||||
"chatId": "5499280257",
|
"chatId": "5499280257",
|
||||||
"text": "={{ $('Compute IDs').first().json.summary }}",
|
"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": {
|
"additionalFields": { "appendAttribution": false }
|
||||||
"appendAttribution": false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"id": "ae19e543-1a6b-4466-8af8-bb436f51091b",
|
"id": "ae19e543-1a6b-4466-8af8-bb436f51091b",
|
||||||
"name": "Notify Telegram",
|
"name": "Notify Telegram",
|
||||||
"type": "n8n-nodes-base.telegram",
|
"type": "n8n-nodes-base.telegram",
|
||||||
"typeVersion": 1.2,
|
"typeVersion": 1.2,
|
||||||
"position": [
|
"position": [1340, 300],
|
||||||
2000,
|
|
||||||
300
|
|
||||||
],
|
|
||||||
"credentials": {
|
"credentials": {
|
||||||
"telegramApi": {
|
"telegramApi": { "id": "d9s4Ec5ocEH8FAxh", "name": "SMB Telegram (leads bot)" }
|
||||||
"id": "d9s4Ec5ocEH8FAxh",
|
|
||||||
"name": "SMB Telegram (leads bot)"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"connections": {
|
"connections": {
|
||||||
"Onboard webhook": {
|
"Onboard webhook": { "main": [[{ "node": "Compute", "type": "main", "index": 0 }]] },
|
||||||
"main": [
|
"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 }]] },
|
||||||
"node": "Read Clients",
|
"Save project": { "main": [[{ "node": "Notify Telegram", "type": "main", "index": 0 }]] }
|
||||||
"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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"executionOrder": "v1",
|
"executionOrder": "v1",
|
||||||
"callerPolicy": "workflowsFromSameOwner",
|
"callerPolicy": "workflowsFromSameOwner",
|
||||||
"availableInMCP": false
|
"availableInMCP": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user