Add customer confirmation/cancellation flow for appointments:
- DB migration (0013): add confirmation_status, confirmed_at, cancelled_at,
confirmation_token to appointments table with index on token column
- schema.ts + factories.ts + types: expose new columns and ConfirmationStatus type
- GET /api/book/confirm/:token — tokenized confirm via email link (redirects)
- GET /api/book/cancel/:token — tokenized cancel via email link, single-use token
- POST /api/appointments/:id/confirm — portal/staff confirm endpoint
- POST /api/appointments/:id/cancel — portal/staff cancel endpoint
- Reminder emails now include Confirm/Cancel CTA buttons with tokenized links
- Reminder service generates confirmation token if missing before sending
- Staff calendar shows confirmation status indicator on appointment cards
and in the detail modal (confirmed ✓ / customer cancelled ✗)
- /booking/confirmed, /booking/cancelled, /booking/error redirect pages
- 23 new unit tests covering all new endpoints and edge cases
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Phase 1 — Seed Hardening:
- Replace all Math.random() calls in seed.ts with a Mulberry32 seeded PRNG
(seed 42) so the same data set is reproduced on every run
- Replace crypto.randomUUID() with a PRNG-based UUID v4 generator
- Add manager (Jordan Lee) and receptionist (Sam Rivera) staff members
to seed — previously all staff were groomers
- New packages/db/src/reset.ts drops all tables/enums and re-runs
migrate + seed; exposed as `pnpm db:reset` at root
- Generate migration 0010_impersonation_sessions.sql for the
impersonation_sessions and impersonation_audit_logs tables that were
already in schema.ts but had no corresponding migration
Phase 2 — Test Factories:
- New packages/db/src/factories.ts with buildStaff, buildClient, buildPet,
buildService, buildAppointment and resetFactoryCounters helpers
- Exported via @groombook/db/factories subpath (package.json + vitest alias)
- impersonation.test.ts updated to use buildStaff instead of hand-rolled
fixture objects
Closes#90 (Phases 1 + 2)
Co-Authored-By: Paperclip <noreply@paperclip.ing>