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>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-18 12:47:07 +02:00
1 parent 0dc26e0c16
commit cdad7875e1
12 files changed
+272 -9

No files matched your search

+6
View File
@@ -90,6 +90,12 @@
- the booking-trend chart was already bucketing real `allBookings` into the current week — confirmed, no change needed
- demo mode intentionally keeps curated static KPI values; real tenants get the computed set
- remaining: live staging validation; "new clients" KPI still has no backend metric (customer entity lacks first-booking tracking)
- `[done/partial]` Milestone 13: Reminder pipeline reliability
- reminder jobs are lifecycle-aware: customer and dashboard cancellation mark pending jobs `cancelled`; reschedule deletes pending jobs and re-arms one via `reminderSchedule` for the new time (no job when moved inside the no-reminder horizon)
- dispatch now joins `bookings.status` and skips non-confirmed bookings (`skipped` job status, `skippedCount` in the response contract) — cancelled/waitlisted customers no longer receive reminders for bookings that are not happening
- reschedule hardened with the same validation as booking creation: appointment reschedules must fit an availability rule and avoid blocked days (`validateAppointmentTarget` is now a shared package-level helper); class bookings keep session-defined times
- new repository methods `CancelPendingReminderJobsForBooking` / `DeletePendingReminderJobsForBooking` in pgx and in-memory implementations
- remaining: live staging validation; SMS reminder channel exists in schema (`channel` column) but only `email` is dispatched — SMS reminders are a separate opt-in feature
## 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.