mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 07:41:16 +00:00
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:
1 parent
10df4e4c64
commit
e4d95bf504
3 files changed
+36
-19
No files matched your search
@@ -57,8 +57,42 @@ func PromoteWaitlist(ctx context.Context, repo db.Repository, tenantID, sessionI
|
|||||||
return promoted, err
|
return promoted, err
|
||||||
}
|
}
|
||||||
_ = repo.ResolveWaitlistEntry(ctx, tenantID, sessionID, booking.CustomerEmail)
|
_ = 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++
|
confirmed++
|
||||||
promoted = append(promoted, booking)
|
promoted = append(promoted, booking)
|
||||||
}
|
}
|
||||||
return promoted, nil
|
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
|
||||||
|
}
|
||||||
@@ -357,27 +357,9 @@ func (s *Service) Create(ctx context.Context, request domain.CreateBookingReques
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if scheduledFor, ok := reminderSchedule(startsAt); ok {
|
if err := armReminderJobs(ctx, s.repo, tenant.ID, created.ID, startsAt, request.CustomerPhone); err != nil {
|
||||||
if err := s.repo.CreateReminderJob(ctx, db.ReminderJobParams{
|
|
||||||
TenantID: tenant.ID,
|
|
||||||
BookingID: created.ID,
|
|
||||||
Channel: "email",
|
|
||||||
ScheduledFor: scheduledFor,
|
|
||||||
}); err != nil {
|
|
||||||
return domain.CreateBookingResponse{}, err
|
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send confirmation emails (async - don't fail booking if email fails)
|
// Send confirmation emails (async - don't fail booking if email fails)
|
||||||
go s.sendBookingConfirmationEmails(tenant, created, request.ServiceID, request.LocationID, customerName, customerEmail, startsAt, endsAt)
|
go s.sendBookingConfirmationEmails(tenant, created, request.ServiceID, request.LocationID, customerName, customerEmail, startsAt, endsAt)
|
||||||
|
|||||||
@@ -119,6 +119,7 @@
|
|||||||
- `[done/partial]` Milestone 18: Public class list polish
|
- `[done/partial]` Milestone 18: Public class list polish
|
||||||
- class sessions on the public booking page are now grouped by day (localized weekday + date headers) with compact timetable-style rows instead of one tall card per session — consistent with the stepped appointment picker above
|
- class sessions on the public booking page are now grouped by day (localized weekday + date headers) with compact timetable-style rows instead of one tall card per session — consistent with the stepped appointment picker above
|
||||||
- capacity states read honestly: spots-left in success/warning tones, `Obsazeno`/`Full` when empty, and the action switches to `Na čekací listinu`/`Join waitlist` (previously a full session still said "Book now" while silently producing a waitlisted booking)
|
- capacity states read honestly: spots-left in success/warning tones, `Obsazeno`/`Full` when empty, and the action switches to `Na čekací listinu`/`Join waitlist` (previously a full session still said "Book now" while silently producing a waitlisted booking)
|
||||||
|
- reminder arming extracted into a shared `armReminderJobs` helper (email + opt-in SMS) used by booking creation and waitlist promotion — a booking promoted after its reminder already dispatched-as-skipped now gets re-armed instead of going unreminded
|
||||||
- remaining: live staging validation
|
- remaining: live staging validation
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|||||||
Reference in new issue
Block a user