Commit Graph
11 Commits
Author SHA1 Message Date
Tomas DvorakandDevin 5c860a1264 feat: data surfacing — intake labels, service metrics, honest capacity
- booking views gain intakeLabels (declared key→label join); dashboard
  modal + manage view render real question labels with slug fallback
- GET /v1/metrics/services aggregates bookings/completed/cancelled/
  revenue per service + classBookings; service-report.csv delegates to
  the same ServiceMetrics aggregation and gains a completed column
- services page gains a Service performance card (per-service stat grid,
  locale CZK revenue, class-bookings footer); demo rows match the
  fabricated service list
- staff capacity utilization subtracts blocked days — tenant-wide and
  staff-scoped blocks remove that day's rule minutes via blockedAt
- tests: blocked-day capacity (tenant + staff scope), service aggregation

Authored By: TDvorak <[email protected]>

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-22 20:25:32 +02:00
Tomas DvorakandDevin 33b6e2a2a6 feat(intake): structured per-service intake questions
Services declare up to 8 intake fields (text/yesno, required) stored
as JSONB; shared/intake.go is the single normalization point — slug
keys, label/dup/unknown-key rules, yes/no canonicalization. Booking
create validates answers against the service declaration (missing
required -> 400), unknown keys are dropped before persist.

Answers surface on UpcomingBooking and CustomerBookingView; the
services page gains a compact editor and the public form renders
declared questions under contact details. Verified live end-to-end.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 18:04:32 +02:00
Tomas DvorakandDevin d86cba61e3 feat(activity): per-tenant audit feed in notification dropdown
activity_events records booking lifecycle transitions with the actor
behind each change — staff (dashboard), customer (self-service), or
system (auto-promotion). The notification dropdown leads with the real
feed instead of derived recent-booking guesses.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 16:06:13 +02:00
Tomas DvorakandDevin e5cb9bd543 feat(bookings): session-to-session move for class bookings
Class customers can move a booking to another future session of the
same template from the manage page. Full target sessions place the
booking on the waitlist; freeing a confirmed seat drains the old
session's queue via the existing promotion path. Reminders re-arm
for the new time.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 15:42:04 +02:00
Tomas DvorakandDevin 796c06ae66 feat(bookings): customer self-service rebooking with real slot picker
CustomerBookingView now carries tenantSlug/serviceId/staffId/
classSessionId so the manage page can query live availability; class
bookings resolve their session title as the display label. Reschedule
rejects waitlisted/completed bookings and session-bound class bookings
(400), collapsing a dead branch in the appointment path.

The manage page reschedule dialog replaces free-form date/time inputs
with a date → availability-slot picker filtered to appointment mode,
so customers can only move to a genuinely free slot with the correct
duration; 409 surfaces "slot just taken". Actions are status-gated:
reschedule for confirmed/pending appointments, cancel for live
bookings, explanatory note for class bookings.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 14:58:49 +02:00
Tomas DvorakandDevin 9f92a2026f fix(bookings): enforce no-overlap atomically via exclusion constraints
Check-then-insert left a race: two concurrent requests for the same
slot could both confirm. Migration 00014 adds three partial btree_gist
exclusion constraints mirroring resourcesCollide semantics (same
staff, same location, both-unassigned), excluding cancelled rows and
class bookings. SQLSTATE 23P01 maps to ErrBookingConflict at all three
write paths so a lost race returns a clean 409.

Validated on a local Postgres 17 container: constraint matrix at SQL
level, repo-level 23P01 detection, concurrent burst yielding exactly
one confirmed booking.

Authored By: TDvorak <[email protected]>

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 13:27:47 +02:00
Tomas DvorakandDevin 3892e9ce8c feat(waitlist): queue position on the customer manage page
A waitlisted customer saw only a bare status — worse, the badge fell
back to "Pending" for both waitlisted and cancelled states. The view
now carries a 1-based waitlistPosition (earlier-created waitlisted
bookings on the session + 1) and the page renders a warning-tone
notice with the queue spot and auto-promotion explanation, in CS/EN.
Also fixed the badge's nonexistent --danger-soft/--warning-soft tokens.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 13:07:06 +02:00
Tomas DvorakandDevin cdad7875e1 fix(reminders): lifecycle-aware jobs and dispatch skip guard
The reminder pipeline fired regardless of what happened to the booking
after creation: cancelled customers got reminded about bookings that
weren't happening, reschedules kept the old reminder time, and
waitlisted bookings armed jobs too. Pending jobs are now cancelled on
both cancel paths, deleted and re-armed on reschedule, and dispatch
joins bookings.status to skip anything not confirmed. Reschedule also
gained the same availability-window and blocked-day validation as
booking creation, closing a 3am-reschedule hole via the manage URL.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 12:47:07 +02:00
Tomas DvorakandDevin 668a54ae1d feat(bookings): dashboard cancellation and waitlist promotion
- POST /v1/catalog/bookings/{id}/cancel lets businesses cancel
  tenant-owned bookings; two-step confirm in the detail modal
- Shared PromoteWaitlist helper promotes earliest waitlisted bookings
  when a class seat frees, on both dashboard and customer cancel paths;
  promoted customers get a confirmation email, waitlist ledger marked
  resolved
- waitlisted status surfaced in bookings UI: stats card, filter, warning
  badge, demo seed, CS+EN strings
- BookingRecord gains created_at for fair ordering; GetBookingByID and
  ResolveWaitlistEntry added to the repository surface

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 11:21:46 +02:00
Tomas DvorakandDevin f2f7439050 refactor(db): replace sqlc with handwritten pgx repos
- Finish Paddle-to-Stripe billing migration; drop paddle/stripe libs
- Renumber migrations; add 00012 memberships+manage_token,
  00013 location_type_capacity
- Add tests: admin, auth, bookings customer service
- Remove graphify-out artifacts and stale env files

Authored By: TDvorak <[email protected]>

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 20:21:34 +02:00
Tomas Dvorak 48c3e15a38 cleanup 2026-05-05 09:48:07 +02:00