fix(notifications): fallback to generic medication names (#532)
* fix(notifications): fallback to generic medication names * test(backend): add timezone column to in-memory user_settings schemas
This commit is contained in:
@@ -19,7 +19,6 @@ import {
|
||||
cleanOldIntakeReminders,
|
||||
createDefaultIntakeReminderState,
|
||||
getEffectiveTimezone,
|
||||
getTimezone,
|
||||
getTodaysIntakes,
|
||||
getUpcomingIntakes,
|
||||
type IntakeReminderState,
|
||||
@@ -84,6 +83,16 @@ function formatIntakeLog(intake: {
|
||||
return `${intake.medName} (medId=${intake.medicationId}, intakeIndex=${intake.blisterIndex}, time=${intake.intakeTime.toISOString()}, localTime=${intake.intakeTimeStr}, usage=${intake.usage} ${doseUnit}, takenBy=${takenBy})`;
|
||||
}
|
||||
|
||||
function getMedicationDisplayName(med: { id: number; name: string | null; genericName: string | null }): string {
|
||||
const commercialName = med.name?.trim() ?? "";
|
||||
if (commercialName) return commercialName;
|
||||
|
||||
const genericName = med.genericName?.trim() ?? "";
|
||||
if (genericName) return genericName;
|
||||
|
||||
return `Medication #${med.id}`;
|
||||
}
|
||||
|
||||
async function autoMarkDueIntakesAsTaken(
|
||||
settings: UserSettings & { userId: number },
|
||||
rows: (typeof medications.$inferSelect)[],
|
||||
@@ -138,7 +147,7 @@ async function autoMarkDueIntakesAsTaken(
|
||||
}
|
||||
|
||||
const medicationTakenBy = parseTakenByJson(med.takenByJson);
|
||||
const medDisplayName = med.name || med.genericName || "";
|
||||
const medDisplayName = getMedicationDisplayName({ id: med.id, name: med.name, genericName: med.genericName });
|
||||
let remainingStock = computeMedicationCurrentStock({
|
||||
medication: med,
|
||||
doses: trackedDoses,
|
||||
@@ -489,7 +498,7 @@ export async function checkAndSendIntakeRemindersForUser(
|
||||
for (const { med, intakes, intakesWithReminders } of reminderEntries) {
|
||||
// Medication-level takenBy (for fallback/display purposes)
|
||||
const medicationTakenBy = parseTakenByJson(med.takenByJson);
|
||||
const medDisplayName = med.name || med.genericName || "";
|
||||
const medDisplayName = getMedicationDisplayName({ id: med.id, name: med.name, genericName: med.genericName });
|
||||
|
||||
// Process each intake separately to track blisterIndex
|
||||
intakesWithReminders.forEach((intake, _blisterIndex) => {
|
||||
|
||||
@@ -126,6 +126,16 @@ type PrescriptionReminderItem = {
|
||||
expiryDate: string | null;
|
||||
};
|
||||
|
||||
function getMedicationDisplayName(row: { id: number; name: string | null; genericName: string | null }): string {
|
||||
const commercialName = row.name?.trim() ?? "";
|
||||
if (commercialName) return commercialName;
|
||||
|
||||
const genericName = row.genericName?.trim() ?? "";
|
||||
if (genericName) return genericName;
|
||||
|
||||
return `Medication #${row.id}`;
|
||||
}
|
||||
|
||||
async function getMedicationsNeedingReminder(
|
||||
userId: number,
|
||||
reminderDaysBefore: number,
|
||||
@@ -297,7 +307,7 @@ async function getMedicationsNeedingReminder(
|
||||
|
||||
if (isCritical || isLow) {
|
||||
lowStock.push({
|
||||
name: row.name,
|
||||
name: getMedicationDisplayName({ id: row.id, name: row.name, genericName: row.genericName }),
|
||||
medsLeft: currentPills,
|
||||
daysLeft,
|
||||
depletionDate,
|
||||
@@ -323,7 +333,7 @@ async function getMedicationsNeedingPrescriptionReminder(userId: number): Promis
|
||||
(row.prescriptionRemainingRefills ?? 0) <= (row.prescriptionLowRefillThreshold ?? 1)
|
||||
)
|
||||
.map((row) => ({
|
||||
name: row.name,
|
||||
name: getMedicationDisplayName({ id: row.id, name: row.name, genericName: row.genericName }),
|
||||
remainingRefills: row.prescriptionRemainingRefills ?? 0,
|
||||
lowThreshold: row.prescriptionLowRefillThreshold ?? 1,
|
||||
expiryDate: row.prescriptionExpiryDate ?? null,
|
||||
|
||||
@@ -68,6 +68,7 @@ async function setStockMode(mode: "automatic" | "manual") {
|
||||
|
||||
async function createMedication(options: {
|
||||
name: string;
|
||||
genericName?: string | null;
|
||||
packCount?: number;
|
||||
blistersPerPack?: number;
|
||||
pillsPerBlister?: number;
|
||||
@@ -80,6 +81,7 @@ async function createMedication(options: {
|
||||
}) {
|
||||
const {
|
||||
name,
|
||||
genericName = null,
|
||||
packCount = 1,
|
||||
blistersPerPack = 1,
|
||||
pillsPerBlister = 10,
|
||||
@@ -106,16 +108,17 @@ async function createMedication(options: {
|
||||
|
||||
const result = await testClient.execute({
|
||||
sql: `INSERT INTO medications (
|
||||
user_id, name, taken_by_json, package_type,
|
||||
user_id, name, generic_name, taken_by_json, package_type,
|
||||
pack_count, blisters_per_pack, pills_per_blister, loose_tablets,
|
||||
stock_adjustment, last_stock_correction_at,
|
||||
usage_json, every_json, start_json, intakes_json,
|
||||
is_obsolete, intake_reminders_enabled
|
||||
) VALUES (?, ?, ?, 'blister', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)
|
||||
) VALUES (?, ?, ?, ?, 'blister', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)
|
||||
RETURNING id`,
|
||||
args: [
|
||||
1,
|
||||
name,
|
||||
genericName,
|
||||
JSON.stringify(takenBy),
|
||||
packCount,
|
||||
blistersPerPack,
|
||||
@@ -348,6 +351,21 @@ describe("Stock semantics parity (planner usage vs scheduler)", () => {
|
||||
const lowStock = await getMedicationsNeedingReminderForTests(1, 7, 365, "en", "automatic");
|
||||
expect(lowStock.some((r) => r.name === "Obsolete Med")).toBe(false);
|
||||
});
|
||||
|
||||
it("uses generic name fallback in scheduler reminders when commercial name is empty", async () => {
|
||||
await setStockMode("automatic");
|
||||
await createMedication({
|
||||
name: "",
|
||||
genericName: "Acetylsalicylic acid",
|
||||
packCount: 1,
|
||||
blistersPerPack: 1,
|
||||
pillsPerBlister: 10,
|
||||
intakes: [{ usage: 1, every: 1, start: "2026-01-01T08:00:00" }],
|
||||
});
|
||||
|
||||
const lowStock = await getMedicationsNeedingReminderForTests(1, 7, 365, "en", "automatic");
|
||||
expect(lowStock.some((r) => r.name === "Acetylsalicylic acid")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getLiquidReminderThresholds", () => {
|
||||
|
||||
Reference in New Issue
Block a user