mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 14:40:52 +00:00
fix(medications): release-QA findings on the inventory overhaul
- The legacy GLP-1 ledger reads again: GET and the Coach snapshot fall back to ledger-derived pen/dose figures when a medication has no inventory items; items always win once they exist. The delta branch on the POST is marked deprecated in favour of the inventory endpoints. - Expired containers count as available nowhere: one shared supply summary (ACTIVE/IN_USE with units) feeds the overview row, the Bestand summary, the list payload, the GLP-1 card, and the runway estimate; expired units surface as a muted suffix instead. - The inventory endpoints speak one wire dialect: request fields renamed to unitsTotal/unitsRemaining to match the responses, OpenAPI regenerated. - The reorder dialog mirrors the page's grouping: active and inactive sections, each sortable within itself, so a saved order can never interleave them. - Polish strings avoid the 2-4 plural trap, sub-dose quantities render as below one dose instead of approximately zero, and the table's name sort collates by the active locale instead of hardcoded German.
This commit is contained in:
1 parent
b765febc97
commit
9dc0ccd03a
28 files changed
+1017
-180
No files matched your search
@@ -2,10 +2,11 @@
|
||||
* v1.16.10 — inventory + units-per-dose validation bounds.
|
||||
*
|
||||
* Pins the raised unit caps (100 → 1000 on container capacity and the
|
||||
* stock correction; `dosesPerUnit` rides along) and the new
|
||||
* `unitsPerDose` (1–100) and `containerType` fields. The GLP-1 legacy
|
||||
* ledger delta deliberately KEEPS its ±100 bound — it counts pens, not
|
||||
* units.
|
||||
* stock correction; `dosesPerUnit` rides along), the new
|
||||
* `unitsPerDose` (1–100) and `containerType` fields, and the symmetric
|
||||
* request field names (`unitsTotal` / `unitsRemaining` — the response
|
||||
* always carried them). The GLP-1 legacy ledger delta deliberately
|
||||
* KEEPS its ±100 bound — it counts pens, not units.
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
@@ -30,17 +31,17 @@ const MINIMAL_MEDICATION = {
|
||||
|
||||
describe("createInventoryItemSchema — unit cap 1000", () => {
|
||||
it("accepts 1000 units", () => {
|
||||
const r = createInventoryItemSchema.safeParse({ dosesTotal: 1000 });
|
||||
const r = createInventoryItemSchema.safeParse({ unitsTotal: 1000 });
|
||||
expect(r.success).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects 1001 units", () => {
|
||||
const r = createInventoryItemSchema.safeParse({ dosesTotal: 1001 });
|
||||
const r = createInventoryItemSchema.safeParse({ unitsTotal: 1001 });
|
||||
expect(r.success).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects zero", () => {
|
||||
const r = createInventoryItemSchema.safeParse({ dosesTotal: 0 });
|
||||
const r = createInventoryItemSchema.safeParse({ unitsTotal: 0 });
|
||||
expect(r.success).toBe(false);
|
||||
});
|
||||
|
||||
@@ -54,19 +55,19 @@ describe("createInventoryItemSchema — unit cap 1000", () => {
|
||||
"OTHER",
|
||||
]) {
|
||||
const r = createInventoryItemSchema.safeParse({
|
||||
dosesTotal: 4,
|
||||
unitsTotal: 4,
|
||||
containerType: ct,
|
||||
});
|
||||
expect(r.success).toBe(true);
|
||||
}
|
||||
const r = createInventoryItemSchema.safeParse({ dosesTotal: 4 });
|
||||
const r = createInventoryItemSchema.safeParse({ unitsTotal: 4 });
|
||||
expect(r.success).toBe(true);
|
||||
expect(r.success && r.data.containerType).toBeUndefined();
|
||||
});
|
||||
|
||||
it("rejects an unknown container type", () => {
|
||||
const r = createInventoryItemSchema.safeParse({
|
||||
dosesTotal: 4,
|
||||
unitsTotal: 4,
|
||||
containerType: "BAG",
|
||||
});
|
||||
expect(r.success).toBe(false);
|
||||
@@ -75,12 +76,12 @@ describe("createInventoryItemSchema — unit cap 1000", () => {
|
||||
|
||||
describe("updateInventoryItemSchema — stock correction cap 1000", () => {
|
||||
it("accepts 1000", () => {
|
||||
const r = updateInventoryItemSchema.safeParse({ dosesRemaining: 1000 });
|
||||
const r = updateInventoryItemSchema.safeParse({ unitsRemaining: 1000 });
|
||||
expect(r.success).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects 1001", () => {
|
||||
const r = updateInventoryItemSchema.safeParse({ dosesRemaining: 1001 });
|
||||
const r = updateInventoryItemSchema.safeParse({ unitsRemaining: 1001 });
|
||||
expect(r.success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -842,16 +842,16 @@ export type MedicationContainerTypeValue =
|
||||
export const createInventoryItemSchema = z
|
||||
.object({
|
||||
/** Units the container ships with. v1.16.10 raises the cap from
|
||||
* 100 to 1000 (large tablet packs); the wire field name stays
|
||||
* `dosesTotal` — it counts units, mapped to doses via
|
||||
* 100 to 1000 (large tablet packs) and renames the wire field to
|
||||
* `unitsTotal` — it counts units, mapped to doses via
|
||||
* `Medication.unitsPerDose`. */
|
||||
dosesTotal: z
|
||||
unitsTotal: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(1000)
|
||||
.describe(
|
||||
"Units the container ships with (tablets / ampoules / puffs; 1–1000). Stored as `unitsTotal`; dose-derived readouts divide by the medication's `unitsPerDose`.",
|
||||
"Units the container ships with (tablets / ampoules / puffs; 1–1000). Dose-derived readouts divide by the medication's `unitsPerDose`.",
|
||||
),
|
||||
/** v1.16.10 — container kind. Defaults to OTHER when absent. */
|
||||
containerType: z
|
||||
@@ -875,7 +875,7 @@ export const createInventoryItemSchema = z
|
||||
.meta({
|
||||
id: "CreateMedicationInventoryItemRequest",
|
||||
description:
|
||||
"Register a new supply container (pen / blister pack / bottle). `dosesTotal` counts UNITS (1–1000); the item starts ACTIVE with `unitsRemaining = unitsTotal` and the intake consumption hook decrements it per taken dose.",
|
||||
"Register a new supply container (pen / blister pack / bottle). `unitsTotal` counts UNITS (1–1000); the item starts ACTIVE with `unitsRemaining = unitsTotal` and the intake consumption hook decrements it per taken dose.",
|
||||
});
|
||||
|
||||
export const updateInventoryItemSchema = z
|
||||
@@ -903,9 +903,11 @@ export const updateInventoryItemSchema = z
|
||||
* flow). Sets the remaining-unit count directly; the route clamps to
|
||||
* `unitsTotal` and re-runs the canonical state machine (0 ⇒ USED_UP,
|
||||
* a raise out of 0 re-evaluates against the expiry clocks).
|
||||
* v1.16.10 raises the cap to 1000 alongside the capacity cap.
|
||||
* v1.16.10 raises the cap to 1000 alongside the capacity cap and
|
||||
* renames the wire field to `unitsRemaining` (it always counted
|
||||
* units), matching the response side.
|
||||
*/
|
||||
dosesRemaining: z
|
||||
unitsRemaining: z
|
||||
.number()
|
||||
.int()
|
||||
.min(0)
|
||||
@@ -972,6 +974,14 @@ export const glp1DoseChangePostSchema = z.object({
|
||||
note: z.string().max(MAX_NOTE_CHARS).nullable().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* DEPRECATED write path (v1.16.10) — the `inventory.delta` branch feeds
|
||||
* the legacy `MedicationInventoryEvent` running-sum ledger. The per-item
|
||||
* endpoints (`POST /api/medications/[id]/inventory`,
|
||||
* `PATCH /api/medications/[id]/inventory/[itemId]`) replaced it; reads
|
||||
* fall back to the ledger only while a medication has zero inventory
|
||||
* items. New callers must register containers instead of posting deltas.
|
||||
*/
|
||||
export const glp1InventoryPostSchema = z.object({
|
||||
delta: z
|
||||
.number()
|
||||
@@ -979,7 +989,10 @@ export const glp1InventoryPostSchema = z.object({
|
||||
.finite()
|
||||
.min(-100)
|
||||
.max(100)
|
||||
.refine((n) => n !== 0, { message: "delta must be non-zero" }),
|
||||
.refine((n) => n !== 0, { message: "delta must be non-zero" })
|
||||
.describe(
|
||||
"Deprecated since v1.16.10: pen-count delta on the legacy running-sum ledger. Register containers via the inventory endpoints instead; reads use the ledger only while the medication has no inventory items.",
|
||||
),
|
||||
reason: z.string().min(1).max(MAX_REASON_CHARS),
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user