Add booking stack, client deploys, and back-office updates
- deploy/booking: shared Easy!Appointments stack with brand-matched wizard (flatpickr recolor, single-tenant provider hide, iframe auto-fit height reporter) - deploy/clients: per-client isolated nginx compose stacks with _template scaffold, new-client.sh, and happynails live site - deploy/backup: smb-db backup script - n8n: booking-sync workflow; onboarding tweaks - playbooks: lead-to-customer lifecycle + outreach - templates: nail-studio landing previews - backoffice: app/db/init/compose updates Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"name": "SMB · Booking Sync",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "booking",
|
||||
"responseMode": "onReceived",
|
||||
"options": {
|
||||
"allowedOrigins": "*"
|
||||
}
|
||||
},
|
||||
"id": "b0000000-1111-2222-3333-444444444444",
|
||||
"name": "Booking webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
240,
|
||||
300
|
||||
],
|
||||
"webhookId": "smb-booking-webhook-0001"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const w=$input.first().json; const b=w.body||w;\nconst s=v=>{v=(v==null?'':String(v));return /^[=+\\-@\\t\\r]/.test(v)?\"'\"+v:v;};\nconst EA='http://easyappointments';\nconst AUTH='Basic __EA_AUTH__';\nconst get=p=>this.helpers.httpRequest({method:'GET',url:EA+'/index.php/api/v1/'+p,headers:{Authorization:AUTH},json:true});\nlet booking, business_name='', notify_channel='telegram';\nif(b.action&&b.payload){\n const p=b.payload;\n const r=await Promise.all([get('customers/'+p.id_users_customer),get('services/'+p.id_services),get('providers/'+p.id_users_provider)]);\n const cust=r[0], svc=r[1], prov=r[2];\n const tz=prov.timezone||'UTC';\n const toUtc=dt=>{ if(!dt) return null; const d=DateTime.fromFormat(String(dt),'yyyy-MM-dd HH:mm:ss',{zone:tz}); return d.isValid?d.toUTC().toISO():String(dt).replace(' ','T'); };\n booking={booking_id:'EA-'+p.id,client_id:String(prov.notes||'').trim(),\n customer_name:s(((cust.firstName||'')+' '+(cust.lastName||'')).trim()),\n customer_contact:s(cust.email||cust.phone||''),service:s(svc.name),\n start_time:toUtc(p.start_datetime),end_time:toUtc(p.end_datetime),\n source:'easyappointments',status:p.status||'confirmed'};\n business_name=s((prov.firstName||'')+' '+(prov.lastName||''));\n} else {\n booking={booking_id:b.booking_id||'B-'+Date.now(),client_id:s(b.client_id),\n customer_name:s(b.customer_name||b.name),customer_contact:s(b.customer_contact||b.contact||b.email||b.phone),\n service:s(b.service||b.service_interest),start_time:b.start_time||null,end_time:b.end_time||null,\n source:s(b.source||'manual'),status:s(b.status||'confirmed')};\n business_name=s(b.business_name); notify_channel=s(b.notify_channel||'telegram');\n}\nreturn [{json:{booking,business_name,notify_channel}}];"
|
||||
},
|
||||
"id": "b0000000-1111-2222-3333-555555555555",
|
||||
"name": "Build booking row",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
460,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "http://smb-crm:8080/api/bookings",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "X-CRM-Token",
|
||||
"value": "__CRM_TOKEN__"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.booking) }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "b0000000-1111-2222-3333-666666666666",
|
||||
"name": "Save to CRM (bookings)",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
680,
|
||||
300
|
||||
],
|
||||
"retryOnFail": true,
|
||||
"maxTries": 4,
|
||||
"waitBetweenTries": 3000
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "message",
|
||||
"operation": "sendMessage",
|
||||
"chatId": "5499280257",
|
||||
"text": "=📅 Neue Buchung\nBetrieb: {{ $('Build booking row').item.json.business_name || $('Build booking row').item.json.booking.client_id }}\nKunde: {{ $('Build booking row').item.json.booking.customer_name || '—' }}\nLeistung: {{ $('Build booking row').item.json.booking.service || '—' }}\nWann: {{ $('Build booking row').item.json.booking.start_time || '—' }}\nKontakt: {{ $('Build booking row').item.json.booking.customer_contact || '—' }}\nQuelle: {{ $('Build booking row').item.json.booking.source }}",
|
||||
"additionalFields": {
|
||||
"appendAttribution": false
|
||||
}
|
||||
},
|
||||
"id": "b0000000-1111-2222-3333-777777777777",
|
||||
"name": "Notify owner (Telegram)",
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
900,
|
||||
300
|
||||
],
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "d9s4Ec5ocEH8FAxh",
|
||||
"name": "SMB Telegram (leads bot)"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Booking webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build booking row",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build booking row": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Save to CRM (bookings)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Save to CRM (bookings)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Notify owner (Telegram)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
}
|
||||
}
|
||||
+294
-17
@@ -6,13 +6,18 @@
|
||||
"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"
|
||||
},
|
||||
{
|
||||
@@ -23,7 +28,10 @@
|
||||
"name": "Compute",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [460, 300]
|
||||
"position": [
|
||||
460,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -31,7 +39,12 @@
|
||||
"url": "http://smb-crm:8080/api/clients",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [ { "name": "X-CRM-Token", "value": "__CRM_TOKEN__" } ]
|
||||
"parameters": [
|
||||
{
|
||||
"name": "X-CRM-Token",
|
||||
"value": "__CRM_TOKEN__"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
@@ -42,7 +55,10 @@
|
||||
"name": "Save client",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [680, 300],
|
||||
"position": [
|
||||
680,
|
||||
300
|
||||
],
|
||||
"retryOnFail": true,
|
||||
"maxTries": 4,
|
||||
"waitBetweenTries": 3000
|
||||
@@ -55,7 +71,10 @@
|
||||
"name": "Build project",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [900, 300]
|
||||
"position": [
|
||||
900,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -63,7 +82,12 @@
|
||||
"url": "http://smb-crm:8080/api/projects",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [ { "name": "X-CRM-Token", "value": "__CRM_TOKEN__" } ]
|
||||
"parameters": [
|
||||
{
|
||||
"name": "X-CRM-Token",
|
||||
"value": "__CRM_TOKEN__"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
@@ -74,7 +98,10 @@
|
||||
"name": "Save project",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [1120, 300],
|
||||
"position": [
|
||||
1120,
|
||||
300
|
||||
],
|
||||
"retryOnFail": true,
|
||||
"maxTries": 4,
|
||||
"waitBetweenTries": 3000
|
||||
@@ -85,28 +112,278 @@
|
||||
"operation": "sendMessage",
|
||||
"chatId": "5499280257",
|
||||
"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 }
|
||||
"additionalFields": {
|
||||
"appendAttribution": false
|
||||
}
|
||||
},
|
||||
"id": "ae19e543-1a6b-4466-8af8-bb436f51091b",
|
||||
"name": "Notify Telegram",
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [1340, 300],
|
||||
"position": [
|
||||
1340,
|
||||
300
|
||||
],
|
||||
"credentials": {
|
||||
"telegramApi": { "id": "d9s4Ec5ocEH8FAxh", "name": "SMB Telegram (leads bot)" }
|
||||
"telegramApi": {
|
||||
"id": "d9s4Ec5ocEH8FAxh",
|
||||
"name": "SMB Telegram (leads bot)"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const cid=$('Save client').first().json.added;\nconst biz=$('Compute').first().json.business_name||'Kunde';\nconst slug=String(cid).toLowerCase().replace(/[^a-z0-9]/g,'');\nconst password=Math.random().toString(36).slice(2,10)+Math.random().toString(36).slice(2,6);\nconst serviceBody={name:'Termin',duration:30,price:0,currency:'EUR',availabilitiesType:'flexible',attendantsNumber:1,isPrivate:false};\nreturn [{json:{cid,biz,username:slug,password,serviceBody}}];"
|
||||
},
|
||||
"id": "ea-ea-build-service",
|
||||
"name": "EA build service",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
680,
|
||||
520
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"method": "POST",
|
||||
"url": "http://easyappointments/index.php/api/v1/services",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "Basic __EA_AUTH__"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.serviceBody) }}"
|
||||
},
|
||||
"id": "ea-ea-create-service",
|
||||
"name": "EA create service",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
900,
|
||||
520
|
||||
],
|
||||
"retryOnFail": true,
|
||||
"maxTries": 3,
|
||||
"waitBetweenTries": 2000,
|
||||
"continueOnFail": true
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const prep=$('EA build service').first().json;\nconst svcId=$json.id;\nconst c=($('Compute').first().json.client)||{};\nconst parts=String(prep.biz).trim().split(/\\s+/);\nconst firstName=parts[0]||prep.biz;\nconst lastName=parts.slice(1).join(' ')||'Studio';\nconst wp={start:'09:00',end:'18:00',breaks:[]};\nconst providerBody={firstName,lastName,email:'provider+'+prep.username+'@booking.mivanchenko.de',\n phone:c.phone||'',services:[svcId],isPrivate:false,timezone:'Europe/Berlin',notes:prep.cid,\n settings:{username:prep.username,password:prep.password,\n workingPlan:{monday:wp,tuesday:wp,wednesday:wp,thursday:wp,friday:wp,saturday:wp,sunday:null}}};\nreturn [{json:{providerBody,svcId,cid:prep.cid,username:prep.username,biz:prep.biz}}];"
|
||||
},
|
||||
"id": "ea-ea-build-provider",
|
||||
"name": "EA build provider",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1120,
|
||||
520
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"method": "POST",
|
||||
"url": "http://easyappointments/index.php/api/v1/providers",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "Basic __EA_AUTH__"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($('EA build provider').item.json.providerBody) }}"
|
||||
},
|
||||
"id": "ea-ea-create-provider",
|
||||
"name": "EA create provider",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
1340,
|
||||
520
|
||||
],
|
||||
"retryOnFail": true,
|
||||
"maxTries": 3,
|
||||
"waitBetweenTries": 2000,
|
||||
"continueOnFail": true
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const bp=$('EA build provider').first().json;\nconst provId=$json.id;\nconst embed='https://booking.mivanchenko.de/index.php/booking?service='+bp.svcId+'&provider='+provId;\nconst stack_notes='Buchung-Embed: '+embed+' | EA provider '+provId+' / svc '+bp.svcId+' / login '+bp.username;\nreturn [{json:{cid:bp.cid,embed,provId,patch:{stack_notes}}}];"
|
||||
},
|
||||
"id": "ea-ea-booking-info",
|
||||
"name": "EA booking info",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1560,
|
||||
520
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {},
|
||||
"method": "PATCH",
|
||||
"url": "=http://smb-crm:8080/api/clients/{{ $json.cid }}",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "X-CRM-Token",
|
||||
"value": "__CRM_TOKEN__"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.patch) }}"
|
||||
},
|
||||
"id": "ea-ea-update-client",
|
||||
"name": "EA update client",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
1780,
|
||||
520
|
||||
],
|
||||
"retryOnFail": true,
|
||||
"maxTries": 3,
|
||||
"waitBetweenTries": 2000,
|
||||
"continueOnFail": true
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"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 }]] }
|
||||
"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
|
||||
},
|
||||
{
|
||||
"node": "EA build service",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build project": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Save project",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Save project": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Notify Telegram",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"EA build service": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "EA create service",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"EA create service": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "EA build provider",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"EA build provider": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "EA create provider",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"EA create provider": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "EA booking info",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"EA booking info": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "EA update client",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user