mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
refactor(medications): give the in-use clock list one owner
The supply tab carried its own copy of the two container kinds whose first opening starts a degradation clock, with a comment saying it mirrored the state machine's list. Nothing enforced the mirror — the state machine types itself against the generated Prisma client, which the browser bundle cannot carry, so a copy was the only way to say it. Move the list into a client-safe module with no imports and let both sides read it. The state machine re-exports it under the Prisma enum, so a renamed or dropped container kind is now a compile error rather than a member that quietly stops matching.
This commit is contained in:
1 parent
6ab3bc87ff
commit
146a02319d
3 files changed
+47
-17
No files matched your search
@@ -75,6 +75,7 @@ import {
|
||||
apiPost,
|
||||
apiPut,
|
||||
} from "@/lib/api/api-fetch";
|
||||
import { startsInUseClock } from "@/lib/medications/inventory/clock-container-types";
|
||||
import type { SupplySummary } from "@/lib/medications/inventory/summary";
|
||||
|
||||
/**
|
||||
@@ -169,15 +170,6 @@ const STATE_BADGE: Record<
|
||||
USED_UP: "outline",
|
||||
};
|
||||
|
||||
/**
|
||||
* Container kinds whose first opening starts a 30-day degradation clock.
|
||||
* Mirrors `IN_USE_CLOCK_CONTAINER_TYPES` in
|
||||
* `src/lib/medications/inventory/state-machine.ts` — the server owns the
|
||||
* transition; the client only needs to know which helper text to show
|
||||
* beside the opening date.
|
||||
*/
|
||||
const CLOCK_CONTAINER_TYPES: readonly ContainerType[] = ["PEN", "AMPOULE"];
|
||||
|
||||
/** Default container kind for the register flow, from the delivery form. */
|
||||
function defaultContainerType(deliveryForm: string | undefined): ContainerType {
|
||||
if (deliveryForm === "INJECTION") return "PEN";
|
||||
@@ -971,7 +963,7 @@ export function AdjustInventoryDialog({
|
||||
id="inventory-edit-opened-helper"
|
||||
className="text-muted-foreground text-xs"
|
||||
>
|
||||
{CLOCK_CONTAINER_TYPES.includes(item.containerType)
|
||||
{startsInUseClock(item.containerType)
|
||||
? t("medications.detail.bestand.editOpenedHelperClock")
|
||||
: t("medications.detail.bestand.editOpenedHelper")}
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* The one owner of "which container kinds start a degradation clock when
|
||||
* they are first opened".
|
||||
*
|
||||
* The state machine decides the transition and the settings client decides
|
||||
* which helper text to put beside the opening date. Both need the same
|
||||
* list, but the state machine types itself against the generated Prisma
|
||||
* client, which a browser bundle cannot carry — so the client kept its own
|
||||
* copy of the two literals with a comment promising they matched. Nothing
|
||||
* held the promise, and a release spent fixing a fact that drifted is a bad
|
||||
* place to keep a second one.
|
||||
*
|
||||
* So the list lives here: string literals and a pure predicate, no imports,
|
||||
* safe on either side of the boundary. `state-machine.ts` re-exports it
|
||||
* under the Prisma enum (`satisfies`), which turns a renamed or dropped
|
||||
* container kind into a compile error rather than a member that silently
|
||||
* stops matching. The per-kind reasoning stays in the state machine's
|
||||
* docblock, where the transition it drives is.
|
||||
*/
|
||||
|
||||
export const IN_USE_CLOCK_CONTAINER_TYPES = ["PEN", "AMPOULE"] as const;
|
||||
|
||||
/**
|
||||
* Does opening this container start the in-use window? Takes a loose
|
||||
* `string` so the client can ask about a container kind it read off the
|
||||
* wire without narrowing first; an unrecognised kind answers `false`,
|
||||
* which is the honest side (no deadline nobody entered).
|
||||
*/
|
||||
export function startsInUseClock(containerType: string): boolean {
|
||||
return (IN_USE_CLOCK_CONTAINER_TYPES as readonly string[]).includes(
|
||||
containerType,
|
||||
);
|
||||
}
|
||||
@@ -79,6 +79,10 @@ import type {
|
||||
MedicationContainerType,
|
||||
MedicationInventoryState,
|
||||
} from "@/generated/prisma/client";
|
||||
import {
|
||||
IN_USE_CLOCK_CONTAINER_TYPES as CLOCK_CONTAINER_TYPES,
|
||||
startsInUseClock,
|
||||
} from "./clock-container-types";
|
||||
|
||||
/** Default in-use window per EMA EPAR §6.3 for the strictest GLP-1
|
||||
* agonists (Mounjaro, Saxenda). 30 days. */
|
||||
@@ -90,17 +94,18 @@ export const DEFAULT_IN_USE_WINDOW_DAYS = 30;
|
||||
* Every other kind holds individually sealed units (or an unknown
|
||||
* arrangement) and expires only on its printed date. See the module
|
||||
* docblock for the per-member reasoning.
|
||||
*
|
||||
* The list itself lives in `./clock-container-types`, which the settings
|
||||
* client can import; `satisfies` here is the drift guard, so a container
|
||||
* kind renamed or dropped from the column stops compiling instead of
|
||||
* quietly never matching.
|
||||
*/
|
||||
export const IN_USE_CLOCK_CONTAINER_TYPES = [
|
||||
"PEN",
|
||||
"AMPOULE",
|
||||
] as const satisfies readonly MedicationContainerType[];
|
||||
export const IN_USE_CLOCK_CONTAINER_TYPES =
|
||||
CLOCK_CONTAINER_TYPES satisfies readonly MedicationContainerType[];
|
||||
|
||||
/** Does opening this container start the in-use window? */
|
||||
export function hasInUseClock(containerType: MedicationContainerType): boolean {
|
||||
return (IN_USE_CLOCK_CONTAINER_TYPES as readonly string[]).includes(
|
||||
containerType,
|
||||
);
|
||||
return startsInUseClock(containerType);
|
||||
}
|
||||
|
||||
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
Reference in new issue
Block a user