Commit Graph

10 Commits

Author SHA1 Message Date
mivanchenko 2515c8835b Fix: public booking page mixed both Filialen's barbers before any was picked
Test backoffice (smb-crm) / test (push) Successful in 1m37s
applyLocationFilter()'s "!selectedLocation" check was meant to mean "only
one Filiale exists, nothing to filter" but also fired in the multi-Filiale
case before the customer had clicked one yet, showing every barber from
every location mixed together in Mitarbeiter. Now keyed on LOCATIONS.length
instead, so with 2+ Filialen nothing shows until one is actually selected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 03:07:11 +02:00
mivanchenko 456ca3872f Add locations (Filialen) as a grouping layer above resources
Test backoffice (smb-crm) / test (push) Successful in 1m46s
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
mivanchenko 9fe495a363 Owner agenda: hide cancelled bookings by default, with a toggle
Test backoffice (smb-crm) / test (push) Successful in 1m35s
Cancelled bookings previously stayed visible (struck-through) in the
week view permanently, cluttering it over time. Adds a "Stornierte
anzeigen" checkbox next to the Filiale toggles -- unchecked by default
so cancelled rows are hidden, checkable to bring them back for
history/audit purposes. Client-side filter, remembered via
localStorage like the Filiale toggle.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 02:33:18 +02:00
mivanchenko b99a3806cf Owner agenda: per-Filiale show/hide toggle
Test backoffice (smb-crm) / test (push) Successful in 1m41s
Checkboxes above the week view, one per active resource -- client-side
filtering (no reload needed), remembered across week navigation via
localStorage since each week change is a full page load. Lets the
owner view one location, the other, or both at once.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 00:24:40 +02:00
mivanchenko 24d9aca812 Per-client ICS calendar feed, replacing the shared ICS_TOKEN (#22)
Test backoffice (smb-crm) / test (push) Has been cancelled
Each client now gets their own clients.ics_token (lazily generated on
first /owner/settings visit), which both authenticates and scopes
/api/bookings.ics -- closing the gap where any shared-token holder
could view another client's bookings by swapping the client_id query
param. The owner settings page now surfaces a copyable subscribe URL.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 11:55:35 +02:00
mivanchenko 16500c4392 Owner settings: services, hours/buffer, auto-confirm, notify channel (#21)
Adds a session-authenticated /owner/settings blueprint for services CRUD
(create/edit/deactivate), per-resource opening hours + min-notice/max-advance/
buffer, and client-level auto_confirm/notify_channel — all scoped to the
logged-in owner's own client_id. Extends booking_db.py with the missing
tenant-scoped update_service/update_resource/update_client writes, mirroring
the existing update_booking allowlist pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 09:00:38 +02:00
mivanchenko 528a13ca7c Owner calendar view + manual booking + owner cancel/reschedule (#20)
Adds an owner-authenticated weekly agenda (grouped by day, today highlighted)
with manual walk-in/phone booking creation, cancel, and reschedule -- all
routed through booking_api.py's create/cancel/reschedule logic (refactored
into shared helpers) so the EXCLUDE overlap constraint and confirmation
email stay on the single existing code path. Manual creation can skip the
opening-hours/min-notice/max-advance/buffer checks via an explicit override,
but never the overlap constraint itself.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 17:16:19 +02:00
mivanchenko 59c35ce39f Owner accounts: auth, password reset, CRM dashboard visibility (#19)
Flask-session login scoped to one client_id (never a request param),
self-service + operator-triggered password reset via single-use tokens,
and an Owner accounts tab on the CRM dashboard.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 17:00:37 +02:00
mivanchenko 644c99ee30 Booking confirmation email + customer self-service cancel/reschedule (#18)
Sends a confirmation email (best-effort, fire-and-forget SMTP via mailer.py)
on booking creation, with a manage-booking link embedding the ticket-2 signed
token. Adds /manage/<token>, a stateless cancel/reschedule page that reuses
the existing slot-picker against booking_api's create/cancel/reschedule API,
distinguishing an invalid/expired link from an already-cancelled one. Sender
address uses the client's own domain when configured, falling back to a
mivanchenko.de address otherwise.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 15:40:45 +02:00
mivanchenko b895663c3a Public booking page + iframe embed (#17)
Adds the customer-facing /book/<slug> page: service/slot picker, booking
form, and confirmation screen, built on #16's existing booking JSON API.
Includes iframe auto-fit height reporting (mirroring
deploy/booking/booking_layout.js's eaBookingHeight message), brand-color
theming via a ?color= query param, a honeypot field with a fake-success
response indistinguishable from a real booking, and a clear "just taken"
message on slot-conflict. Caddy per-IP rate limiting is documented in
deploy/booking/RATE_LIMIT.md for manual application (no Caddyfile is
tracked in this repo).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 15:15:29 +02:00