fix: keep topical stock non-depleting in planner flows (#359)

* fix: keep topical stock non-depleting in planner and reports

* test: stabilize e2e selectors for updated medication semantics

* fix(backend): add missing planner translation keys
This commit is contained in:
Daniel Volz
2026-02-28 23:36:52 +01:00
committed by GitHub
parent 8efd99d738
commit 9e8a6315e7
19 changed files with 807 additions and 130 deletions
+6
View File
@@ -179,6 +179,8 @@ type TranslationKeys = {
common: {
pill: string;
pills: string;
units: string;
ml: string;
blister: string;
blisters: string;
day: string;
@@ -299,6 +301,8 @@ const translations: Record<Language, TranslationKeys> = {
common: {
pill: "pill",
pills: "pills",
units: "units",
ml: "ml",
blister: "blister",
blisters: "blisters",
day: "day",
@@ -420,6 +424,8 @@ const translations: Record<Language, TranslationKeys> = {
common: {
pill: "Tablette",
pills: "Tabletten",
units: "Einheiten",
ml: "ml",
blister: "Blister",
blisters: "Blister",
day: "Tag",
+20 -7
View File
@@ -42,6 +42,16 @@ type PlannerRow = {
packageType?: string;
};
function isContainerPackage(packageType?: string): boolean {
return packageType === "bottle" || packageType === "tube" || packageType === "liquid_container";
}
function getPlannerUnit(packageType: string | undefined, tr: ReturnType<typeof getTranslations>): string {
if (packageType === "tube") return tr.common.units;
if (packageType === "liquid_container") return tr.common.ml;
return tr.common.pills;
}
type SendEmailBody = {
email: string;
from: string;
@@ -168,16 +178,18 @@ ${summaryText}
${activeRows
.map((r) => {
const isBottle = r.packageType === "bottle";
const usage = `${r.plannerUsage} ${tr.common.pills}`;
const isBottle = isContainerPackage(r.packageType);
const usageUnit = getPlannerUnit(r.packageType, tr);
const usage = `${r.plannerUsage} ${usageUnit}`;
const needed = isBottle ? "" : `${r.blistersNeeded} × ${r.blisterSize}`;
const medPrescription = prescriptionMap.get(r.medicationId);
const rxRefills = medPrescription?.prescriptionEnabled
? String(medPrescription.prescriptionRemainingRefills ?? 0)
: dc.prescriptionNotApplicable;
const loosePills = Math.round((Number(r.loosePills) || 0) * 10) / 10;
const availableUnit = getPlannerUnit(r.packageType, tr);
const available = isBottle
? `${loosePills} ${tr.common.pills}`
? `${loosePills} ${availableUnit}`
: `${r.fullBlisters} ${tr.common.blisters}${loosePills > 0 ? ` + ${loosePills} ${tr.common.pills}` : ""}`;
const status = r.enough ? dc.statusEnough : dc.statusEmpty;
return `${r.medicationName}: ${usage}, ${needed}, ${dc.tableHeaders.prescriptionRefills}: ${rxRefills}, ${available} - ${status}`;
@@ -209,7 +221,7 @@ ${getFooterPlain(language)}`;
const safeBlisterSize = Number(row.blisterSize) || 0;
const safeFullBlisters = Number(row.fullBlisters) || 0;
const safeLoosePills = Math.round((Number(row.loosePills) || 0) * 10) / 10;
const isBottle = row.packageType === "bottle";
const isBottle = isContainerPackage(row.packageType);
// "Blisters needed" column: dash for bottles
const neededCell = isBottle ? "" : `${safeBlistersNeeded} × ${safeBlisterSize}`;
@@ -223,7 +235,8 @@ ${getFooterPlain(language)}`;
// "Available" column: match frontend format
let availableCell: string;
if (isBottle) {
availableCell = `${safeLoosePills} ${tr.common.pills}`;
const availableUnit = getPlannerUnit(row.packageType, tr);
availableCell = `${safeLoosePills} ${availableUnit}`;
} else {
availableCell = `${safeFullBlisters} ${tr.common.blisters}`;
if (safeLoosePills > 0) {
@@ -236,7 +249,7 @@ ${getFooterPlain(language)}`;
return `
<tr style="${rowBg}">
<td style="padding: 10px 12px; border-bottom: 1px solid #e5e7eb; white-space: nowrap;">${safeName}</td>
<td style="padding: 10px 12px; border-bottom: 1px solid #e5e7eb; text-align: center; white-space: nowrap;"><strong>${safePlannerUsage}</strong> ${tr.common.pills}</td>
<td style="padding: 10px 12px; border-bottom: 1px solid #e5e7eb; text-align: center; white-space: nowrap;"><strong>${safePlannerUsage}</strong> ${getPlannerUnit(row.packageType, tr)}</td>
<td style="padding: 10px 12px; border-bottom: 1px solid #e5e7eb; text-align: center; white-space: nowrap;">${neededCell}</td>
<td style="padding: 10px 12px; border-bottom: 1px solid #e5e7eb; text-align: center; white-space: nowrap;">${rxCell}</td>
<td style="padding: 10px 12px; border-bottom: 1px solid #e5e7eb; text-align: center; white-space: nowrap;">${availableCell}</td>
@@ -324,7 +337,7 @@ ${getFooterPlain(language)}`;
const pushTitle = t(dc.subject, { from: fromDate, until: untilDate });
const pushMessage = `${summaryText}\n\n${activeRows
.map((r) => {
const usage = `${r.plannerUsage} ${tr.common.pills}`;
const usage = `${r.plannerUsage} ${getPlannerUnit(r.packageType, tr)}`;
const status = r.enough ? dc.statusEnough : dc.statusEmpty;
return `${r.enough ? "✓" : "✗"} ${r.medicationName}: ${usage} - ${status}`;
})
+3 -1
View File
@@ -120,7 +120,9 @@ export async function shareRoutes(app: FastifyInstance) {
const takenByArray = parseTakenByJson(med.takenByJson);
const totalPills =
(med.packageType ?? "blister") === "bottle"
(med.packageType ?? "blister") === "bottle" ||
(med.packageType ?? "blister") === "tube" ||
(med.packageType ?? "blister") === "liquid_container"
? med.looseTablets + (med.stockAdjustment ?? 0)
: med.packCount * med.blistersPerPack * med.pillsPerBlister + med.looseTablets + (med.stockAdjustment ?? 0);
return {