diff --git a/server/src/db/repositories/TodoItems.repository.ts b/server/src/db/repositories/TodoItems.repository.ts index 3feae7474..7ad2b5368 100644 --- a/server/src/db/repositories/TodoItems.repository.ts +++ b/server/src/db/repositories/TodoItems.repository.ts @@ -245,7 +245,7 @@ export class TodoItemsRepository extends TrekRepository { /** * RJ5 (`reminder-jobs.service.ts#todoTick`, looped): claims the reminder * before it is sent. `UPDATE todo_items SET reminded_at = CURRENT_TIMESTAMP - * WHERE id = ? AND (reminded_at IS NULL OR reminded_at <= now - 20 h)`, the + * WHERE id = ? AND (reminded_at IS NULL OR reminded_at <= datetime(now, -20 hours))`, the * same dedup bound {@link listDueForReminder} selects by, so of two * processes running the tick only the one whose update lands sends. True * when this caller holds the claim. diff --git a/server/src/nest/notifications/reminder-jobs.service.ts b/server/src/nest/notifications/reminder-jobs.service.ts index f176f99fe..91d890926 100644 --- a/server/src/nest/notifications/reminder-jobs.service.ts +++ b/server/src/nest/notifications/reminder-jobs.service.ts @@ -96,6 +96,17 @@ export class ReminderJobsService implements OnApplicationBootstrap { * Each reminder is claimed before it is sent, so a second process running * the same tick skips it, and given back when the send fails, so the next * tick retries it until the trip starts. + * + * The upgrade backfill (`Migration20200101042100_…`) decides "already + * reminded" by UTC `date('now')` while this job counts days in the app TZ. + * That is deliberate, not a seam: the backfill stands in for the job before + * it, which matched `start_date = date('now', '+N days')` in UTC, so UTC is + * the clock those reminders went out by. Every UTC day had exactly one 9 AM + * run, so nothing the backfill marks was left unsent. Its strict `<` leaves + * the upgrade day's own reminders unmarked, because the boot may come before + * that day's run; when it comes after, such a trip is reminded once more on + * the first run after the upgrade. At most one extra reminder, never a lost + * one, which is why the migration stays as it shipped. */ async tripTick(): Promise { try {