feat(classes): weekly recurring schedules with lazy session materialization

class_schedules rules generate concrete session rows for a rolling 28-day
horizon — no cron; materialize runs idempotently on dashboard and public
reads via a partial unique index on (schedule_id, starts_at). Rule delete
stops future sessions; existing sessions keep bookings and overrides.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-18 17:31:17 +02:00
1 parent 7b9080eb1c
commit ad60167a31
15 files changed
+1029 -7

No files matched your search

+10 -1
View File
@@ -204,6 +204,15 @@
- verified live: staff report shows 4/3/1, 180/10560 minutes, 1% utilization, 3 600 Kč; service report aggregates correctly; browser download produced the real file; focused tests cover both reports and the capacity relationship
- remaining: live staging validation on Neon + pgx repository path
- `[done/partial]` Milestone 29: Recurring class schedules
- `class_schedules` table (migration 00018): weekly rules — template, optional location, `day_of_week` (Go weekday, Sun=0), `starts_local` time, `active`; sessions gained a nullable `schedule_id` link with a partial unique index on `(schedule_id, starts_at)` for idempotent regeneration
- no cron/batch scheduler: rules materialize concrete session rows lazily on read — `ListClassSessions` (dashboard) and `Availability` (public booking) both call `MaterializeScheduleSessions(tenant, now, 28d)`; PG does it in a single INSERT…SELECT with `ON CONFLICT DO NOTHING`, memory repo mirrors it; a live-verify bug (HH:MM:SS parse) was caught and fixed
- deleting a rule stops future sessions; sessions already materialized stay in place with their bookings — per-session capacity overrides and manual deletes survive regeneration
- `GET/POST/DELETE /v1/catalog/class-schedules` with tenant scoping, template/location ownership checks, weekday 0-6 and HH:MM validation
- dashboard: "Opakující se lekce" card between templates and sessions on the classes tab — rule form (template, weekday, time, optional location) + rule list; sessions expose `scheduleId` so rule-generated sessions are identifiable
- verified live: seeded Wed 10:00 + Sat 09:30 rules generated 8 sessions at correct local times; create → 4 Monday sessions materialized immediately; delete → rule gone, sessions remain; dayOfWeek 9 rejected; public availability converts local→UTC correctly; focused tests cover materialization, idempotency, validation
- remaining: live staging validation on Neon + pgx repository path; no bulk series edit (delete-and-recreate by design)
## Overview
Bookra is a Czech-first booking SaaS for small local service businesses. It targets salons, massage therapists, small clinics, repair shops, and small studios/clubs that need a simple public booking flow, internal schedule management, reminders, and basic business reporting without becoming a full ERP.
@@ -453,7 +462,7 @@ Key entities:
Shortlist of worthwhile directions once v1 stabilizes — kept deliberately small, in line with the "no feature sprawl" principle:
- **Google Calendar sync**: M22 shipped read-only ICS feeds; two-way sync (push bookings into Google, pull busy blocks back as availability exceptions) needs OAuth and conflict rules — larger commitment, defer until a concrete request.
- **Recurring class schedules**: templates currently schedule sessions one at a time; a weekly recurrence rule with per-session overrides would cut setup time for studios.
- **Series-aware session edits**: M29's rules generate sessions, but editing a rule means delete-and-recreate; an "apply to future sessions of this rule" update (time shift, capacity change) would remove the workaround.
- **Booking notes intake**: structured intake fields per service (e.g. "first visit?", "injuries?") instead of the single free-text notes field — still schema-light.
- **Browser push notifications**: M27 persists read state and the feed covers deliveries; Web Push (service worker + VAPID) would notify owners about new bookings without keeping the dashboard open — defer until a concrete request, email already covers off-site reach.
- **Service performance panel**: M28's service-report aggregation could surface in the dashboard (revenue by service, top performers) once the metric set proves stable — the CSV is the probe.