mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 15:51:15 +00:00
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:
1 parent
0dc26e0c16
commit
cdad7875e1
12 files changed
+272
-9
No files matched your search
@@ -108,7 +108,7 @@ func (r *PGRepository) ListDueReminderJobs(ctx context.Context, dueBefore time.T
|
||||
rows, err := r.pool.Query(ctx, `
|
||||
SELECT rj.id, rj.tenant_id, t.name, t.locale, t.timezone,
|
||||
rj.booking_id, rj.channel, rj.scheduled_for,
|
||||
b.customer_name, b.customer_email, b.reference, b.starts_at, rj.status
|
||||
b.customer_name, b.customer_email, b.reference, b.starts_at, rj.status, b.status
|
||||
FROM reminder_jobs rj
|
||||
INNER JOIN bookings b ON b.id = rj.booking_id
|
||||
INNER JOIN tenants t ON t.id = rj.tenant_id
|
||||
@@ -138,6 +138,7 @@ func (r *PGRepository) ListDueReminderJobs(ctx context.Context, dueBefore time.T
|
||||
&record.Reference,
|
||||
&record.StartsAt,
|
||||
&record.Status,
|
||||
&record.BookingStatus,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -191,3 +192,20 @@ func (r *PGRepository) GetDashboardMetrics(ctx context.Context, tenantID string,
|
||||
)
|
||||
return metrics, err
|
||||
}
|
||||
|
||||
func (r *PGRepository) CancelPendingReminderJobsForBooking(ctx context.Context, bookingID string) error {
|
||||
_, err := r.pool.Exec(ctx, `
|
||||
UPDATE reminder_jobs
|
||||
SET status = 'cancelled'
|
||||
WHERE booking_id = $1 AND status = 'pending'
|
||||
`, bookingID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *PGRepository) DeletePendingReminderJobsForBooking(ctx context.Context, bookingID string) error {
|
||||
_, err := r.pool.Exec(ctx, `
|
||||
DELETE FROM reminder_jobs
|
||||
WHERE booking_id = $1 AND status = 'pending'
|
||||
`, bookingID)
|
||||
return err
|
||||
}
|
||||
@@ -40,6 +40,8 @@ type Repository interface {
|
||||
CreateReminderJob(ctx context.Context, params ReminderJobParams) error
|
||||
ListDueReminderJobs(ctx context.Context, dueBefore time.Time, limit int) ([]ReminderJobRecord, error)
|
||||
MarkReminderJobDispatched(ctx context.Context, reminderJobID string, status string, dispatchedAt time.Time) error
|
||||
CancelPendingReminderJobsForBooking(ctx context.Context, bookingID string) error
|
||||
DeletePendingReminderJobsForBooking(ctx context.Context, bookingID string) error
|
||||
CreateNotificationDeliveryLog(ctx context.Context, params NotificationDeliveryLogParams) error
|
||||
GetDashboardMetrics(ctx context.Context, tenantID string, startsAt time.Time, endsAt time.Time) (DashboardMetrics, error)
|
||||
GetSubscriptionSnapshot(ctx context.Context, tenantID string) (BillingSnapshotRecord, error)
|
||||
@@ -418,6 +420,7 @@ type ReminderJobRecord struct {
|
||||
Reference string
|
||||
StartsAt time.Time
|
||||
Status string
|
||||
BookingStatus string
|
||||
}
|
||||
|
||||
type NotificationDeliveryLogParams struct {
|
||||
@@ -1413,6 +1416,12 @@ func (r *MemoryRepository) ListDueReminderJobs(_ context.Context, dueBefore time
|
||||
var out []ReminderJobRecord
|
||||
for _, job := range r.reminderJobs {
|
||||
if job.Status == "pending" && !job.ScheduledFor.After(dueBefore) {
|
||||
for _, booking := range r.bookings {
|
||||
if booking.ID == job.BookingID {
|
||||
job.BookingStatus = booking.Status
|
||||
break
|
||||
}
|
||||
}
|
||||
out = append(out, job)
|
||||
}
|
||||
}
|
||||
@@ -1433,6 +1442,27 @@ func (r *MemoryRepository) MarkReminderJobDispatched(_ context.Context, reminder
|
||||
return pgx.ErrNoRows
|
||||
}
|
||||
|
||||
func (r *MemoryRepository) CancelPendingReminderJobsForBooking(_ context.Context, bookingID string) error {
|
||||
for index := range r.reminderJobs {
|
||||
if r.reminderJobs[index].BookingID == bookingID && r.reminderJobs[index].Status == "pending" {
|
||||
r.reminderJobs[index].Status = "cancelled"
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *MemoryRepository) DeletePendingReminderJobsForBooking(_ context.Context, bookingID string) error {
|
||||
kept := r.reminderJobs[:0]
|
||||
for _, job := range r.reminderJobs {
|
||||
if job.BookingID == bookingID && job.Status == "pending" {
|
||||
continue
|
||||
}
|
||||
kept = append(kept, job)
|
||||
}
|
||||
r.reminderJobs = kept
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *MemoryRepository) CreateNotificationDeliveryLog(_ context.Context, params NotificationDeliveryLogParams) error {
|
||||
r.deliveryLogs = append(r.deliveryLogs, params)
|
||||
return nil
|
||||
|
||||
Reference in new issue
Block a user