diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml
index 84cabb506..a02cb2e33 100644
--- a/docs/api/openapi.yaml
+++ b/docs/api/openapi.yaml
@@ -11992,14 +11992,21 @@ components:
description: Kind of physical container (PEN / AMPOULE / BLISTER / INHALER / BOTTLE / OTHER). Display-level
classification; defaults to OTHER.
unitsTotal:
- type: number
+ anyOf:
+ - type: number
+ - type: "null"
description: Units the container shipped with (tablets / ampoules / puffs; 1–1000). v1.16.12 — fractional, so a
- split-pill remainder reads cleanly. Dose-derived readouts divide by the medication's `unitsPerDose`.
+ split-pill remainder reads cleanly. Dose-derived readouts divide by the medication's `unitsPerDose`. v1.18.3
+ (iOS#31) — NULL when the unit count is unknown (a corrupt or legacy row); the client renders "unknown",
+ never a fabricated 0.
unitsRemaining:
- type: number
+ anyOf:
+ - type: number
+ - type: "null"
description: Units left in the container. v1.16.12 — fractional (a ½-tablet dose leaves 29.5 of 30). Decremented by the
intake consumption hook (FEFO with spillover across containers); refunded when a taken dose is skipped,
- edited away, or deleted.
+ edited away, or deleted. v1.18.3 (iOS#31) — NULL when the count is unknown (a corrupt or legacy row); the
+ client renders "unknown", never a fabricated 0 it could decrement into negatives.
firstUseAt:
anyOf:
- type: string
diff --git a/messages/de.json b/messages/de.json
index 0021ef660..b9082d82f 100644
--- a/messages/de.json
+++ b/messages/de.json
@@ -1667,6 +1667,7 @@
"summary": "{remaining} von {total} Dosen übrig",
"itemsTitle": "Packungen & Behälter",
"doses": "{remaining} / {total} Dosen",
+ "unknown": "Unbekannt",
"state": {
"ACTIVE": "Ungeöffnet",
"IN_USE": "In Gebrauch",
diff --git a/messages/en.json b/messages/en.json
index cbf3de315..ac16efad9 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -1667,6 +1667,7 @@
"summary": "{remaining} of {total} doses remaining",
"itemsTitle": "Packs & containers",
"doses": "{remaining} / {total} doses",
+ "unknown": "Unknown",
"state": {
"ACTIVE": "Unopened",
"IN_USE": "In use",
diff --git a/messages/es.json b/messages/es.json
index 4fcfb42c7..5408c4069 100644
--- a/messages/es.json
+++ b/messages/es.json
@@ -1667,6 +1667,7 @@
"summary": "{remaining} de {total} dosis restantes",
"itemsTitle": "Envases y recipientes",
"doses": "{remaining} / {total} dosis",
+ "unknown": "Desconocido",
"state": {
"ACTIVE": "Sin abrir",
"IN_USE": "En uso",
diff --git a/messages/fr.json b/messages/fr.json
index c2bdaa342..40cce8896 100644
--- a/messages/fr.json
+++ b/messages/fr.json
@@ -1667,6 +1667,7 @@
"summary": "{remaining} doses restantes sur {total}",
"itemsTitle": "Boîtes et contenants",
"doses": "{remaining} / {total} doses",
+ "unknown": "Inconnu",
"state": {
"ACTIVE": "Non ouvert",
"IN_USE": "En cours",
diff --git a/messages/it.json b/messages/it.json
index 140054cc0..3c8e01de3 100644
--- a/messages/it.json
+++ b/messages/it.json
@@ -1667,6 +1667,7 @@
"summary": "{remaining} dosi rimanenti su {total}",
"itemsTitle": "Confezioni e contenitori",
"doses": "{remaining} / {total} dosi",
+ "unknown": "Sconosciuto",
"state": {
"ACTIVE": "Non aperto",
"IN_USE": "In uso",
diff --git a/messages/pl.json b/messages/pl.json
index d799fb13b..f84f3320c 100644
--- a/messages/pl.json
+++ b/messages/pl.json
@@ -1667,6 +1667,7 @@
"summary": "Pozostało {remaining} z {total} dawek",
"itemsTitle": "Opakowania i pojemniki",
"doses": "{remaining} / {total} dawek",
+ "unknown": "Nieznane",
"state": {
"ACTIVE": "Nieotwarte",
"IN_USE": "W użyciu",
diff --git a/src/components/medications/sections/inventory-section.tsx b/src/components/medications/sections/inventory-section.tsx
index b36e11b92..28a769047 100644
--- a/src/components/medications/sections/inventory-section.tsx
+++ b/src/components/medications/sections/inventory-section.tsx
@@ -90,8 +90,10 @@ interface InventoryItem {
id: string;
state: InventoryState;
containerType: ContainerType;
- unitsTotal: number;
- unitsRemaining: number;
+ // v1.18.3 (iOS#31) — null = unknown unit count (corrupt / legacy row);
+ // the UI renders "—" instead of a fabricated 0.
+ unitsTotal: number | null;
+ unitsRemaining: number | null;
}
interface InventoryResponse {
@@ -203,13 +205,23 @@ export function InventorySection({
// payload and the GLP-1 endpoint on one predicate. Expired stock is
// never available; it renders as a separate muted suffix.
// Dose-derived headline (floor — a partial dose is not a dose).
+ // v1.18.3 (iOS#31) — an unknown-units row (null) contributes nothing to
+ // the available-supply math; it shows "—" in its own row but cannot pad
+ // the pooled headline. Coalesce null → 0 only for the summary inputs.
const {
unitsRemaining: remainingUnits,
unitsTotal: totalUnits,
dosesRemaining: remaining,
dosesTotal: total,
expiredUnits,
- } = summariseSupply(items, perDose);
+ } = summariseSupply(
+ items.map((item) => ({
+ state: item.state,
+ unitsTotal: item.unitsTotal ?? 0,
+ unitsRemaining: item.unitsRemaining ?? 0,
+ })),
+ perDose,
+ );
const dialogs = (
<>
@@ -370,19 +382,25 @@ export function InventorySection({
inline at meta-text size — read-only, never a control. */}
- {t("medications.detail.bestand.doses", {
- remaining: Math.floor(item.unitsRemaining / perDose),
- total: Math.floor(item.unitsTotal / perDose),
- })}
- {perDose !== 1 && (
- <>
- {" · "}
- {t("medications.detail.bestand.unitsDetail", {
- remaining: formatUnitCount(item.unitsRemaining),
- total: formatUnitCount(item.unitsTotal),
+ {/* v1.18.3 (iOS#31) — an unknown unit count (null) renders
+ "—" rather than a fabricated 0-dose figure. */}
+ {item.unitsRemaining == null || item.unitsTotal == null
+ ? t("medications.detail.bestand.unknown")
+ : t("medications.detail.bestand.doses", {
+ remaining: Math.floor(item.unitsRemaining / perDose),
+ total: Math.floor(item.unitsTotal / perDose),
})}
- >
- )}
+ {perDose !== 1 &&
+ item.unitsRemaining != null &&
+ item.unitsTotal != null && (
+ <>
+ {" · "}
+ {t("medications.detail.bestand.unitsDetail", {
+ remaining: formatUnitCount(item.unitsRemaining),
+ total: formatUnitCount(item.unitsTotal),
+ })}
+ >
+ )}