fix(medications): stop offering a dose once the course has ended

A medication whose end date had passed still showed taken and skip in the
table and on its card, because the action row only looked at whether the
medication was active. The schedule engine already stops minting doses
after the end date, so reminders, the doses card and the today list were
quiet, but the medication itself kept inviting a dose that is no longer
part of the course.

The server now resolves one rule and publishes it on the list and detail
reads: `courseStatus` (UPCOMING, CURRENT or ENDED, in calendar days on the
user's clock, both ends inclusive) and `intakeActionable` (active, intake
tracked, course includes today). The table and both card variants render
their actions from it, the take-all flow skips such a medication, the card
and table name an ended course with an "Ended" badge, and no overdue
escalation is raised for it. A course that ends today keeps its actions
for the whole local day.

The detail header's "Ended" status now follows the same field. It compared
the end date's UTC midnight with the clock and so called the whole last
course day ended. Logging a past dose from the history is unchanged.
This commit is contained in:
Marc-André Bombeck committed 2026-09-27 13:49:21 +02:00
1 parent 419ff04765
commit 8295dfe9b6
28 files changed
+640 -20

No files matched your search

+32
View File
@@ -43973,6 +43973,20 @@ components:
HealthKit Medications sync; NULL is a native HealthLog medication. Read-only echo of the stored provenance —
the create route sets it, the list read surfaces it so mirrored-vs-native is visible without inspecting the
iOS client's local registry.
courseStatus:
type: string
enum:
- UPCOMING
- CURRENT
- ENDED
description: "v1.39.4 — where today (on the user's clock) sits in the course: UPCOMING before `startsOn`, ENDED after
`endsOn`, CURRENT otherwise. Calendar days, both ends inclusive, so a course that ends today is CURRENT all
day. Read-only — computed, not stored."
intakeActionable:
type: boolean
description: "v1.39.4 — whether the medication offers taken / skip today: true only when it is `active`, its intake is
tracked (`trackIntake`) and `courseStatus` is CURRENT. Clients render the dose actions from this and do not
re-derive it. Logging a past dose from the history is not gated by it. Read-only — computed, not stored."
lastTakenAt:
anyOf:
- type: string
@@ -44043,6 +44057,8 @@ components:
- schedules
- category
- externalSource
- courseStatus
- intakeActionable
- lastTakenAt
- todayEventCount
- stockUnitsRemaining
@@ -44233,6 +44249,20 @@ components:
$ref: "#/components/schemas/MedicationScheduleOutput"
category:
$ref: "#/components/schemas/MedicationCategory"
courseStatus:
type: string
enum:
- UPCOMING
- CURRENT
- ENDED
description: "v1.39.4 — where today (on the user's clock) sits in the course: UPCOMING before `startsOn`, ENDED after
`endsOn`, CURRENT otherwise. Calendar days, both ends inclusive, so a course that ends today is CURRENT all
day. Read-only — computed, not stored."
intakeActionable:
type: boolean
description: "v1.39.4 — whether the medication offers taken / skip today: true only when it is `active`, its intake is
tracked (`trackIntake`) and `courseStatus` is CURRENT. Clients render the dose actions from this and do not
re-derive it. Logging a past dose from the history is not gated by it. Read-only — computed, not stored."
stockUnitsRemaining:
anyOf:
- type: number
@@ -44279,6 +44309,8 @@ components:
- updatedAt
- schedules
- category
- courseStatus
- intakeActionable
- stockUnitsRemaining
- stockDosesRemaining
- runwayDays