feat(medications): make low-stock alert reorder-lead-time aware

The low-stock alert fired when the projected runway dropped below the
user threshold (default 7 days). For a sparse cadence — a weekly
injection's 7-day floor is barely one dose-interval — the warning landed
with about one dose left, too late to reorder before the supply ran out.

Add an optional per-medication reorder lead time plus a user-level
default (0–60 days, default 10) and widen the alert trigger to
max(lowStockRunwayDays, leadDays + cadenceIntervalDays), so the warning
lands before the last dose for any cadence. Because the trigger is a
max() over the user floor it never shrinks anyone's current threshold; a
daily med with no lead keeps its prior boundary.

The push body and the card notice surface concrete dates — supply runs
out around <date>, reorder by <date> — with a last-dose informational
state and the days/units fallback when supply data is thin. Retire the
stale fixed 4-dose count threshold on the GLP-1 details endpoint and
route its card lowStock flag through the same runway evaluation so the
card and the daily cron agree. Surface the lead default in the low-stock
settings card with progressive disclosure; a single medication can
override it.

Additive schema: nullable Medication.reorderLeadDays (migration 0161, no
backfill) and notificationPrefs.medication.reorderLeadDays. OpenAPI
regenerated.
This commit is contained in:
Marc-André Bombeck committed 2026-06-14 20:39:06 +02:00
1 parent 52bf985734
commit 08aa655be4
29 files changed
+1060 -97

No files matched your search

@@ -92,6 +92,7 @@ describe("GET /api/auth/me/notification-prefs", () => {
clientManaged: false,
deliveryDefault: "server",
lowStockRunwayDays: 7,
reorderLeadDays: 10,
},
mood: { reminderHour: 22 },
cycle: { clientManaged: false },
@@ -123,6 +124,7 @@ describe("GET /api/auth/me/notification-prefs", () => {
clientManaged: true,
deliveryDefault: "server",
lowStockRunwayDays: 7,
reorderLeadDays: 10,
},
mood: { reminderHour: 22 },
cycle: { clientManaged: false },
@@ -156,6 +158,7 @@ describe("GET /api/auth/me/notification-prefs", () => {
clientManaged: false,
deliveryDefault: "server",
lowStockRunwayDays: 7,
reorderLeadDays: 10,
},
mood: { reminderHour: 22 },
cycle: { clientManaged: false },
@@ -200,6 +203,7 @@ describe("PATCH /api/auth/me/notification-prefs", () => {
clientManaged: true,
deliveryDefault: "server",
lowStockRunwayDays: 7,
reorderLeadDays: 10,
},
mood: { reminderHour: 22 },
cycle: { clientManaged: false },
@@ -220,6 +224,7 @@ describe("PATCH /api/auth/me/notification-prefs", () => {
clientManaged: true,
deliveryDefault: "server",
lowStockRunwayDays: 7,
reorderLeadDays: 10,
},
mood: { reminderHour: 22 },
cycle: { clientManaged: false },
@@ -244,6 +249,7 @@ describe("PATCH /api/auth/me/notification-prefs", () => {
clientManaged: false,
deliveryDefault: "server",
lowStockRunwayDays: 7,
reorderLeadDays: 10,
},
mood: { reminderHour: 22 },
cycle: { clientManaged: false },
@@ -260,6 +266,7 @@ describe("PATCH /api/auth/me/notification-prefs", () => {
clientManaged: true,
deliveryDefault: "server",
lowStockRunwayDays: 7,
reorderLeadDays: 10,
},
mood: { reminderHour: 22 },
cycle: { clientManaged: false },
@@ -329,6 +336,7 @@ describe("PATCH /api/auth/me/notification-prefs", () => {
clientManaged: true,
deliveryDefault: "server",
lowStockRunwayDays: 7,
reorderLeadDays: 10,
},
mood: { reminderHour: 22 },
cycle: { clientManaged: false },
@@ -363,6 +371,7 @@ describe("PATCH /api/auth/me/notification-prefs", () => {
clientManaged: true,
deliveryDefault: "server",
lowStockRunwayDays: 7,
reorderLeadDays: 10,
},
mood: { reminderHour: 22 },
cycle: { clientManaged: false },
@@ -383,6 +392,7 @@ describe("PATCH /api/auth/me/notification-prefs", () => {
clientManaged: true,
deliveryDefault: "server",
lowStockRunwayDays: 7,
reorderLeadDays: 10,
},
mood: { reminderHour: 22 },
cycle: { clientManaged: false },
@@ -422,6 +432,7 @@ describe("PATCH /api/auth/me/notification-prefs", () => {
clientManaged: false,
deliveryDefault: "server",
lowStockRunwayDays: 7,
reorderLeadDays: 10,
},
mood: { reminderHour: 9 },
cycle: { clientManaged: false },
@@ -471,6 +482,7 @@ describe("PATCH /api/auth/me/notification-prefs", () => {
clientManaged: false,
deliveryDefault: "server",
lowStockRunwayDays: 14,
reorderLeadDays: 10,
},
}),
},