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>
n8n/onboarding.json now provisions a default resource (with Mon-Sat 09:00-18:00
hours so the public booking page has slots immediately), a starter service, and
an owner-login user for every new client, recording the temp password via the
existing credentials CRM entity -- gated behind an If check so a failed user
creation can't leave a stale credentials row. The EA-provisioning chain
(service/provider creation against Easy!Appointments) is removed entirely.
Adds POST /api/resources, /api/services, /api/owner_users to the backoffice API
for n8n to call, backed by booking_db.py's existing tenancy-safe create_*
helpers. Also adds "slug" to db.py's clients column list -- it was already a DB
column (#17) but the generic /api/clients POST silently dropped it, so
onboarding could never actually set a client's public-facing slug.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a `credentials` entity to the back office (never mirrored to
Sheets, gated by the CRM token even to read) so client logins like
the auto-generated Easy!Appointments provider password can be viewed
and copied from the dashboard instead of getting lost — the actual
cause of the happynails password going missing. Onboarding now saves
that generated password instead of discarding it. Also adds
Documentation.md, brings README/TODO in line with the current
Postgres-first architecture, and tidies the backlog.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
Webhook /webhook/onboard → read Clients → compute next C-#### →
append Clients row (status onboarding, renewal_date auto from billing
cycle) → seed Projects row (checklist from services, +14d go-live) →
log Activity → Telegram confirmation. Sheets writes retry on fail.
Verified end-to-end (C-0002 created across all three tabs).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>