feat: scheduled report digests + per-session capacity (M33)

POST /v1/internal/jobs/reports/dispatch emails each tenant owner a
trailing-window digest (bookings, revenue, per-service and per-staff
breakdown) localized to the tenant locale; zero-activity tenants are
skipped. Metrics cores extracted to tenant-scoped variants so the job
needs no principal.

PATCH /v1/catalog/class-sessions/:id sets a session's seat count with
a confirmed-bookings floor — shrinking below it needs allowOverCapacity,
matching the waitlist-promotion convention. Session rows gain an inline
capacity editor with an overbook confirm state.

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-22 21:29:44 +02:00
1 parent e6f1ec38a4
commit e680e0c2b8
14 files changed
+808 -10

No files matched your search

+9 -2
View File
@@ -236,6 +236,13 @@
- `waitlist_entries` dropped (migration 00020): the ledger was written on join/promotion but never read — positions derive from bookings' `created_at`
- verified live: PATCH retime cascades to session and booking windows; without `applyToFuture` sessions stay; 400/404 paths correct; focused tests cover cascade and no-cascade paths
- remaining: live staging validation on Neon + pgx repository path
- `[done]` Milestone 33: Scheduled report digests + per-session capacity
- `POST /v1/internal/jobs/reports/dispatch?days=&tenantId=` (job-runner key) emails each tenant owner a trailing-window digest — totals, per-service bookings/revenue, per-staff utilization — localized to the tenant locale, dashboard link included; tenants with zero activity are skipped (a silent week is signal, not noise), failed sends are counted and logged to the activity feed
- `bookings.Service` gains tenant-scoped `StaffMetricsForTenant`/`ServiceMetricsForTenant` cores so jobs need no principal; the principal-facing methods delegate
- `POST /v1/internal/jobs/reports/dispatch` accepts `tenantId` for single-tenant runs (manual retry, per-tenant crons); cadence lives with the external scheduler — the job itself is deliberately stateless
- `PATCH /v1/catalog/class-sessions/:id` sets a session's seat count; shrinking below confirmed bookings returns `409` unless `allowOverCapacity` — same explicit-override convention as waitlist promotion; session rows gain an inline capacity editor (CS/EN) with an overbook confirm state
- verified live: dispatch authorized by job key, sent 1 digest for the seeded tenant; capacity PATCH grew/shrank sessions and rejected below-floor values; tests cover localized render, zero-activity skip, send-failure counting, and the capacity floor
- remaining: live staging validation on Neon + pgx repository path
## 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.
@@ -487,10 +494,10 @@ Shortlist of worthwhile directions once v1 stabilizes — kept deliberately smal
- **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.
- **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.
- **Scheduled report delivery**: period-end exports emailed automatically (cron + existing mail transport) instead of manual downloads — pairs naturally with M28's report endpoints.
- **Intake reporting**: M31 shows labels, but answers aren't queryable — filtering or aggregating yes-no answers (e.g. "first visits this month") needs a stored projection, not JSONB scans; defer until a reporting request exists.
- **Bulk session shift**: M32 retimes future sessions of one rule; a genuine weekday *move* (relocate existing sessions to the new weekday, not just steer future materialization) and multi-rule shifts (holiday weeks) are still manual — defer until requested.
- **Per-session capacity override**: session capacity still comes only from the template; letting staff override capacity on one session (or one rule) is a small schema addition if a real need appears.
- **Digest preferences**: M33's digest is on-or-nothing per tenant — a per-tenant opt-out/cadence setting (or per-member recipients) belongs in notification settings if owners ask for control.
- **Digest attachments**: the digest is text-only; attaching the CSV exports (M28) needs multipart/mixed in the SMTP provider — small lift, defer until asked.
## Product Principles
- calm, premium, structured UX