mivanchenko 456ca3872f
Test backoffice (smb-crm) / test (push) Successful in 1m46s
Add locations (Filialen) as a grouping layer above resources
Enables multiple barbers/staff bookable at the same location and time
-- previously "resource" conflated "location" and "the thing that
can't double-book itself" into one row, so a Filiale could only ever
have exactly one bookable slot at once.

- New `locations` table; `resources.location_id` with a generic,
  idempotent backfill migration (any resource without a location gets
  one auto-created matching its name -- not a one-off for any single
  client, protects any future resource stuck in the old flat shape too)
- `resources`/`resource_hours`/services keep everything they already
  had (hours, min-notice, max-advance, buffer, the no-overlap
  constraint) scoped to resource_id, not location_id -- two barbers at
  one location must stay independently bookable at the same time
- booking_db.py: new locations CRUD mirroring the existing
  resources/services pattern; create_resource now requires a
  location_id, guarded the same way every other tenant check here is
  (get_location existence check, no real FK -- matches this schema's
  existing no-FK convention throughout)
- app.py: new POST /api/locations provisioning route; POST
  /api/resources now requires location_id
- owner_settings.py + settings.html: new self-service "add a Filiale"
  / "add a barber" UI -- there was previously no way to create a
  resource at all outside the CRM/n8n provisioning API
- public_booking.py + book.html: new Filiale picker (reuses the
  existing wireOptionGroup button-group pattern), filtering the
  Mitarbeiter picker to the selected location -- a single-location
  client sees no extra click, same as before Filialen existed
- owner_booking.py + agenda.html: the Filiale show/hide toggle and
  hide-cancelled toggle (shipped earlier this session) now key off
  location_id instead of resource_id, so hiding a Filiale hides every
  barber's bookings at it; manual-booking dropdown grouped by Filiale
- n8n/onboarding.json: default provisioning now creates a "Hauptfiliale"
  location before its resource (inert until re-imported into the live
  n8n instance)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 03:02:56 +02:00

smb-online — "Get Your Small Business Online"

A productized service that gets local small businesses online: landing page + online booking + lead capture + small automations. Built to be thoroughly tracked and logged — every client, lead, booking and invoice lives in a Postgres CRM (sole source of truth), driven by n8n on the homelab.

Full documentation (architecture, data model, every component): Documentation.md.

Delivery tiers — an internal signal, not a fixed product

tier (A = free Google stack, B = self-hosted/privacy) started as two fixed product bundles but is being decoupled from the public offering (see TODO.md): which stack a client actually gets is a decision made with the client, case-by-case, and recorded on their clients row (tier, stack_notes) — it's no longer baked into the demos. In practice, booking has already converged on one shared self-hosted service (Easy!Appointments, see deploy/booking/) used for every client regardless of tier.

Repository layout

Documentation.md           Full architecture / component reference — start here for "how it works"
backoffice/                 Postgres CRM + Flask API + operator dashboard (the source of truth)
deploy/
  clients/                   One isolated nginx stack per signed client + new-client.sh scaffolder
  booking/                    Shared Easy!Appointments booking service (all clients)
  smb-demos/                  Public demo/preview hosting (Apache)
  backup/                      Daily Postgres backup script (cron on the homelab)
n8n/                         Exported n8n workflows (lead-intake, onboarding, booking-sync, renewal-reminder)
playbooks/                   Operator playbooks (outreach, lead-to-customer, tier-a, tier-b)
templates/landing/           Landing-page demos + client preview pages
  demo-tier-a/                  "Schnittpunkt" barbershop demo
  demo-tier-b/                  "PhysioVital" physio practice demo
  demo-pizzeria/                "Bella Napoli" pizzeria demo w/ online ordering
templates/onboarding/        Internal "create client" form → n8n onboarding workflow

View the demos

They are self-contained HTML — open directly:

xdg-open templates/landing/demo-tier-a/index.html    # barbershop
xdg-open templates/landing/demo-tier-b/index.html    # physio practice
xdg-open templates/landing/demo-pizzeria/index.html  # pizzeria / online ordering

Or serve them all with the chooser page:

python3 -m http.server 8080 --directory templates/landing
# → http://localhost:8080/   (links to all three demos)

Lead/callback forms POST live to the n8n lead-intake webhook; booking widgets link to the shared Easy!Appointments instance. Nothing here is stubbed anymore — see Documentation.md §4.5 for the full workflow wiring.

Phases (status)

Tracked against actual repo state — see Documentation.md for what backs each checkmark.

  • Phase 1 — Demo landing pages
  • Phase 2 — Accounts & setup: Google account + workbook, n8n live, Google creds bound, Vaultwarden (clients.vault_ref), domain (wildcard *.mivanchenko.de)
  • Phase 3 — CRM (Postgres-only, no Sheets mirror) + n8n lead-intake & renewal workflows
  • Phase 4 — Demo forms + booking wired live to n8n webhooks (lead-intake, booking-sync)
  • Phase 5 — First real client + reception v1 — onboarding automation and per-client deploy tooling exist and are exercised (deploy/clients/); not verifiable from the repo alone whether a paying client is live
  • Phase 6 — AI receptionist (Claude Haiku 4.5) — not started

Operator is in an Ausbildung as Cloud Engineer at NETWAYS. This offering deliberately stays out of NETWAYS's lane (no managed cloud hosting / monitoring / OpenStack/K8s hosting for clients). Before taking paying clients: (1) check the Ausbildungsvertrag for a Nebentätigkeit clause and get written employer approval if required; (2) Gewerbeanmeldung + Kleinunternehmerregelung (§19 UStG) once revenue starts; (3) confirm with the IHK / a Steuerberater if unsure.

S
Description
No description provided
Readme 860 KiB
Languages
HTML 56.3%
Python 39.7%
PLpgSQL 2%
CSS 1%
JavaScript 0.6%
Other 0.4%