From 7a32b2045e61181fea13c07ea882bf1351a58db8 Mon Sep 17 00:00:00 2001 From: Daniel Volz Date: Tue, 24 Feb 2026 21:21:34 +0100 Subject: [PATCH] fix: run one stock reminder catch-up after restart (#300) * fix: run one stock reminder catch-up after restart * fix(backend): persist scheduler stock-check timestamp in reminder state --- backend/src/services/reminder-scheduler.ts | 18 +++++++++++++++--- backend/src/utils/scheduler-utils.ts | 3 +++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/backend/src/services/reminder-scheduler.ts b/backend/src/services/reminder-scheduler.ts index e38e1f1..32eee03 100644 --- a/backend/src/services/reminder-scheduler.ts +++ b/backend/src/services/reminder-scheduler.ts @@ -567,6 +567,15 @@ ${getFooterPlain(language)}${isRepeatDaily ? `\n\n${tr.stockReminder.repeatDaily } async function checkAndSendReminder(logger: ServiceLogger): Promise { + // Track stock-scheduler daily execution separately from intake updates. + // This prevents intake reminders from suppressing stock catch-up after restarts. + const state = loadReminderState(); + const today = getTodayInTimezone(); + saveReminderState({ + ...state, + lastStockSchedulerCheckDate: today, + }); + // Get all user settings to iterate over each user const allUserSettings = await getAllUserSettings(); @@ -689,6 +698,7 @@ async function checkAndSendReminderForUser( saveReminderState({ lastAutoEmailSent: new Date().toISOString(), lastAutoEmailDate: today, + lastStockSchedulerCheckDate: currentState.lastStockSchedulerCheckDate, notifiedMedications: [...new Set([...currentState.notifiedMedications, userStockNotifiedKey])], nextScheduledCheck: currentState.nextScheduledCheck, lastNotificationType: "stock", @@ -892,6 +902,7 @@ async function checkAndSendReminderForUser( saveReminderState({ lastAutoEmailSent: new Date().toISOString(), lastAutoEmailDate: today, + lastStockSchedulerCheckDate: currentState.lastStockSchedulerCheckDate, notifiedMedications: [...new Set([...currentState.notifiedMedications, userPrescriptionNotifiedKey])], nextScheduledCheck: currentState.nextScheduledCheck, lastNotificationType: "prescription", @@ -947,9 +958,10 @@ export function startReminderScheduler(logger: ServiceLogger): void { const today = getTodayInTimezone(); const currentHour = getCurrentHourInTimezone(); - // If it's past REMINDER_HOUR today in the configured timezone and we haven't checked today, run immediately - if (currentHour >= REMINDER_HOUR && state.lastAutoEmailDate !== today) { - logger.info("[Reminder] Missed today's check, running now..."); + // If it's past REMINDER_HOUR today in the configured timezone and we haven't checked today, run one catch-up. + // This is intentionally a single current-state snapshot (no replay of missed days). + if (currentHour >= REMINDER_HOUR && state.lastStockSchedulerCheckDate !== today) { + logger.info("[Reminder] Missed today's check, running one catch-up snapshot (no historical replay)..."); checkAndSendReminder(logger).catch((err) => logger.error(`[Reminder] Error: ${err}`)); } diff --git a/backend/src/utils/scheduler-utils.ts b/backend/src/utils/scheduler-utils.ts index 75bb5c2..47fe7ad 100644 --- a/backend/src/utils/scheduler-utils.ts +++ b/backend/src/utils/scheduler-utils.ts @@ -483,6 +483,7 @@ export function getUpcomingIntakes( export type ReminderState = { lastAutoEmailSent: string | null; lastAutoEmailDate: string | null; + lastStockSchedulerCheckDate: string | null; notifiedMedications: string[]; nextScheduledCheck: string | null; lastNotificationType: "stock" | "intake" | "prescription" | null; @@ -505,6 +506,7 @@ export function createDefaultReminderState(): ReminderState { return { lastAutoEmailSent: null, lastAutoEmailDate: null, + lastStockSchedulerCheckDate: null, notifiedMedications: [], nextScheduledCheck: null, lastNotificationType: null, @@ -524,6 +526,7 @@ export function parseReminderState(json: string): ReminderState { return { lastAutoEmailSent: saved.lastAutoEmailSent ?? null, lastAutoEmailDate: saved.lastAutoEmailDate ?? null, + lastStockSchedulerCheckDate: saved.lastStockSchedulerCheckDate ?? null, notifiedMedications: saved.notifiedMedications ?? [], nextScheduledCheck: saved.nextScheduledCheck ?? null, lastNotificationType: saved.lastNotificationType ?? null,