fix(reminders): re-arm reminder jobs on waitlist promotion

A booking promoted off the waitlist after its reminder had already
dispatched-as-skipped went unreminded. Promotion now clears pending
jobs and re-arms via the shared armReminderJobs helper — extracted
from booking creation so both paths arm email plus opt-in SMS.

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 13:18:39 +02:00
1 parent 10df4e4c64
commit e4d95bf504
3 files changed
+37 -20

No files matched your search

@@ -57,8 +57,42 @@ func PromoteWaitlist(ctx context.Context, repo db.Repository, tenantID, sessionI
return promoted, err
}
_ = repo.ResolveWaitlistEntry(ctx, tenantID, sessionID, booking.CustomerEmail)
// A booking promoted after its reminder already dispatched-as-skipped
// would otherwise go unreminded; re-arm whatever the horizon allows.
_ = repo.DeletePendingReminderJobsForBooking(ctx, booking.ID)
_ = armReminderJobs(ctx, repo, tenantID, booking.ID, booking.StartsAt, booking.CustomerPhone)
confirmed++
promoted = append(promoted, booking)
}
return promoted, nil
}
// armReminderJobs schedules the email reminder — and the SMS reminder when the
// tenant opted into SMS and a phone number is on file — for a booking whose
// start still falls inside a reminder horizon. A start too close or too far
// away simply arms nothing.
func armReminderJobs(ctx context.Context, repo db.Repository, tenantID, bookingID string, startsAt time.Time, customerPhone string) error {
scheduledFor, ok := reminderSchedule(startsAt)
if !ok {
return nil
}
if err := repo.CreateReminderJob(ctx, db.ReminderJobParams{
TenantID: tenantID,
BookingID: bookingID,
Channel: "email",
ScheduledFor: scheduledFor,
}); err != nil {
return err
}
if customerPhone != "" {
if smsSettings, err := repo.GetTenantSMSSettings(ctx, tenantID); err == nil && smsSettings.Enabled {
_ = repo.CreateReminderJob(ctx, db.ReminderJobParams{
TenantID: tenantID,
BookingID: bookingID,
Channel: "sms",
ScheduledFor: scheduledFor,
})
}
}
return nil
}
+2 -20
View File
@@ -357,26 +357,8 @@ func (s *Service) Create(ctx context.Context, request domain.CreateBookingReques
}
}
if scheduledFor, ok := reminderSchedule(startsAt); ok {
if err := s.repo.CreateReminderJob(ctx, db.ReminderJobParams{
TenantID: tenant.ID,
BookingID: created.ID,
Channel: "email",
ScheduledFor: scheduledFor,
}); err != nil {
return domain.CreateBookingResponse{}, err
}
// SMS reminders are a per-tenant opt-in and need a customer phone.
if request.CustomerPhone != "" {
if smsSettings, err := s.repo.GetTenantSMSSettings(ctx, tenant.ID); err == nil && smsSettings.Enabled {
_ = s.repo.CreateReminderJob(ctx, db.ReminderJobParams{
TenantID: tenant.ID,
BookingID: created.ID,
Channel: "sms",
ScheduledFor: scheduledFor,
})
}
}
if err := armReminderJobs(ctx, s.repo, tenant.ID, created.ID, startsAt, request.CustomerPhone); err != nil {
return domain.CreateBookingResponse{}, err
}
// Send confirmation emails (async - don't fail booking if email fails)