diff --git a/messages/de.json b/messages/de.json index ed7059ac9..e9e579531 100644 --- a/messages/de.json +++ b/messages/de.json @@ -12061,7 +12061,8 @@ "customVaccineDelete": "{name} hat einen eigenen Impfstoff entfernt", "lifeEventCreate": "{name} hat ein Lebensereignis hinzugefügt", "lifeEventUpdate": "{name} hat ein Lebensereignis geändert", - "lifeEventDelete": "{name} hat ein Lebensereignis entfernt" + "lifeEventDelete": "{name} hat ein Lebensereignis entfernt", + "biomarkerMerge": "{name} hat Biomarker zusammengeführt" }, "section": { "measurements": { diff --git a/messages/en.json b/messages/en.json index 317fe14df..712ef3e10 100644 --- a/messages/en.json +++ b/messages/en.json @@ -12061,7 +12061,8 @@ "customVaccineDelete": "{name} removed an own vaccine", "lifeEventCreate": "{name} added a life event", "lifeEventUpdate": "{name} changed a life event", - "lifeEventDelete": "{name} removed a life event" + "lifeEventDelete": "{name} removed a life event", + "biomarkerMerge": "{name} merged biomarkers" }, "section": { "measurements": { diff --git a/messages/es.json b/messages/es.json index c14403377..244eeff75 100644 --- a/messages/es.json +++ b/messages/es.json @@ -12061,7 +12061,8 @@ "customVaccineDelete": "{name} quitó una vacuna propia", "lifeEventCreate": "{name} añadió un acontecimiento vital", "lifeEventUpdate": "{name} cambió un acontecimiento vital", - "lifeEventDelete": "{name} eliminó un acontecimiento vital" + "lifeEventDelete": "{name} eliminó un acontecimiento vital", + "biomarkerMerge": "{name} combinó biomarcadores" }, "section": { "measurements": { diff --git a/messages/fr.json b/messages/fr.json index 172d45319..c99baa24b 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -12061,7 +12061,8 @@ "customVaccineDelete": "{name} a retiré un vaccin personnel", "lifeEventCreate": "{name} a ajouté un événement de vie", "lifeEventUpdate": "{name} a modifié un événement de vie", - "lifeEventDelete": "{name} a supprimé un événement de vie" + "lifeEventDelete": "{name} a supprimé un événement de vie", + "biomarkerMerge": "{name} a fusionné des biomarqueurs" }, "section": { "measurements": { diff --git a/messages/it.json b/messages/it.json index 725cabf55..e1f8f751d 100644 --- a/messages/it.json +++ b/messages/it.json @@ -12061,7 +12061,8 @@ "customVaccineDelete": "{name} ha rimosso un vaccino personale", "lifeEventCreate": "{name} ha aggiunto un evento della vita", "lifeEventUpdate": "{name} ha modificato un evento della vita", - "lifeEventDelete": "{name} ha rimosso un evento della vita" + "lifeEventDelete": "{name} ha rimosso un evento della vita", + "biomarkerMerge": "{name} ha unito dei biomarcatori" }, "section": { "measurements": { diff --git a/messages/ko.json b/messages/ko.json index 93077aa28..806768ceb 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -12061,7 +12061,8 @@ "customVaccineDelete": "{name} 님이 직접 추가한 백신을 삭제했어요", "lifeEventCreate": "{name} 님이 삶의 사건을 추가했어요", "lifeEventUpdate": "{name} 님이 삶의 사건을 수정했어요", - "lifeEventDelete": "{name} 님이 삶의 사건을 삭제했어요" + "lifeEventDelete": "{name} 님이 삶의 사건을 삭제했어요", + "biomarkerMerge": "{name} 님이 바이오마커를 합쳤어요" }, "section": { "measurements": { diff --git a/messages/pl.json b/messages/pl.json index 3b7ea5962..f5fab9232 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -12061,7 +12061,8 @@ "customVaccineDelete": "{name} usunął własną szczepionkę", "lifeEventCreate": "{name} dodał wydarzenie życiowe", "lifeEventUpdate": "{name} zmienił wydarzenie życiowe", - "lifeEventDelete": "{name} usunął wydarzenie życiowe" + "lifeEventDelete": "{name} usunął wydarzenie życiowe", + "biomarkerMerge": "{name} scalił biomarkery" }, "section": { "measurements": { diff --git a/src/app/api/export/__tests__/per-type-routes.test.ts b/src/app/api/export/__tests__/per-type-routes.test.ts index bd0a478da..115bf4bc6 100644 --- a/src/app/api/export/__tests__/per-type-routes.test.ts +++ b/src/app/api/export/__tests__/per-type-routes.test.ts @@ -86,6 +86,8 @@ vi.mock("@/lib/db", () => ({ // helper now also reads (`buildRecordsBackupSection`). labResult: { findMany: vi.fn() }, biomarker: { findMany: vi.fn() }, + biomarkerAlias: { findMany: vi.fn() }, + biomarkerMerge: { findMany: vi.fn() }, illnessEpisode: { findMany: vi.fn() }, allergy: { findMany: vi.fn() }, familyHistoryEntry: { findMany: vi.fn() }, @@ -135,6 +137,8 @@ beforeEach(() => { // `resetAllMocks` clears the inline defaults, and the full-backup payload // builder reads these unconditionally. vi.mocked(prisma.nutrientIntakeDay.findMany).mockResolvedValue([] as never); + vi.mocked(prisma.biomarkerAlias.findMany).mockResolvedValue([] as never); + vi.mocked(prisma.biomarkerMerge.findMany).mockResolvedValue([] as never); vi.mocked(prisma.medicationCategoryLabel.findMany).mockResolvedValue( [] as never, ); diff --git a/src/app/api/export/__tests__/soft-delete-filter.test.ts b/src/app/api/export/__tests__/soft-delete-filter.test.ts index 288b99d8e..edbfc9d0b 100644 --- a/src/app/api/export/__tests__/soft-delete-filter.test.ts +++ b/src/app/api/export/__tests__/soft-delete-filter.test.ts @@ -67,6 +67,8 @@ vi.mock("@/lib/db", () => ({ cycleDayLog: { findMany: vi.fn() }, labResult: { findMany: vi.fn() }, biomarker: { findMany: vi.fn() }, + biomarkerAlias: { findMany: vi.fn() }, + biomarkerMerge: { findMany: vi.fn() }, allergy: { findMany: vi.fn() }, familyHistoryEntry: { findMany: vi.fn() }, workout: { findMany: vi.fn() }, @@ -205,6 +207,8 @@ beforeEach(() => { vi.mocked(prisma.cycleDayLog.findMany).mockResolvedValue([] as never); vi.mocked(prisma.labResult.findMany).mockResolvedValue([] as never); vi.mocked(prisma.biomarker.findMany).mockResolvedValue([] as never); + vi.mocked(prisma.biomarkerAlias.findMany).mockResolvedValue([] as never); + vi.mocked(prisma.biomarkerMerge.findMany).mockResolvedValue([] as never); vi.mocked(prisma.allergy.findMany).mockResolvedValue([] as never); vi.mocked(prisma.familyHistoryEntry.findMany).mockResolvedValue([] as never); vi.mocked(prisma.workout.findMany).mockResolvedValue([] as never); diff --git a/src/app/api/export/encrypted/__tests__/route.test.ts b/src/app/api/export/encrypted/__tests__/route.test.ts index a96e6ac92..7ea794bdd 100644 --- a/src/app/api/export/encrypted/__tests__/route.test.ts +++ b/src/app/api/export/encrypted/__tests__/route.test.ts @@ -77,6 +77,8 @@ vi.mock("@/lib/db", () => ({ // builder now also reads (`buildRecordsBackupSection`). labResult: { findMany: vi.fn().mockResolvedValue([]) }, biomarker: { findMany: vi.fn().mockResolvedValue([]) }, + biomarkerAlias: { findMany: vi.fn().mockResolvedValue([]) }, + biomarkerMerge: { findMany: vi.fn().mockResolvedValue([]) }, illnessEpisode: { findMany: vi.fn().mockResolvedValue([]) }, allergy: { findMany: vi.fn().mockResolvedValue([]) }, familyHistoryEntry: { findMany: vi.fn().mockResolvedValue([]) }, diff --git a/src/components/labs/biomarker-merge-sheet.tsx b/src/components/labs/biomarker-merge-sheet.tsx index 2a86ad0d3..8fd26f9f7 100644 --- a/src/components/labs/biomarker-merge-sheet.tsx +++ b/src/components/labs/biomarker-merge-sheet.tsx @@ -16,6 +16,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { Loader2 } from "lucide-react"; +import { toastWrittenOutcome } from "@/components/outcome/outcome-toast"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; @@ -71,7 +72,8 @@ export function BiomarkerMergeSheet({ setPreview(result); return; } - toast.success( + toastWrittenOutcome( + "success", tCount("labs.merge.doneToast", result.movedReadings, { name: result.target.name, }), diff --git a/src/lib/record-activity/activity-verb.ts b/src/lib/record-activity/activity-verb.ts index f370de57e..648b6bcee 100644 --- a/src/lib/record-activity/activity-verb.ts +++ b/src/lib/record-activity/activity-verb.ts @@ -179,6 +179,8 @@ export function recordActivityVerbLine( return t("recordSharing.activityVerb.biomarkerUpdate", { name }); case "biomarker.delete": return t("recordSharing.activityVerb.biomarkerDelete", { name }); + case "labs.biomarker.merge": + return t("recordSharing.activityVerb.biomarkerMerge", { name }); case "allergy.update": return t("recordSharing.activityVerb.allergyUpdate", { name }); diff --git a/tests/integration/biomarker-merge.test.ts b/tests/integration/biomarker-merge.test.ts index f434aab7f..87ac6b667 100644 --- a/tests/integration/biomarker-merge.test.ts +++ b/tests/integration/biomarker-merge.test.ts @@ -97,10 +97,9 @@ async function merge( body: { sourceIds: string[]; dryRun?: boolean }, ) { const { POST } = await import("@/app/api/biomarkers/[id]/merge/route"); - return POST( - json(body, `http://localhost/api/biomarkers/${targetId}/merge`), - { params: Promise.resolve({ id: targetId }) }, - ); + return POST(json(body, `http://localhost/api/biomarkers/${targetId}/merge`), { + params: Promise.resolve({ id: targetId }), + }); } async function tableCounts() { @@ -172,9 +171,8 @@ describe("POST /api/biomarkers/{id}/merge", () => { expect(detail.data.meta.total).toBe(42); // The doctor report: one line, counted 42, under the marker's name. - const { loadLabResults } = await import( - "@/lib/doctor-report/clinical-records" - ); + const { loadLabResults } = + await import("@/lib/doctor-report/clinical-records"); const report = await loadLabResults(USER_ID, daysAgo(1000), new Date()); expect(report).toHaveLength(1); expect(report![0]).toMatchObject({ @@ -206,14 +204,18 @@ describe("POST /api/biomarkers/{id}/merge", () => { if (!page.nextCursor) break; offset += page.readings!.length; } - expect(total.reduce((a, b) => a + b, 0), analyte).toBe(42); + expect( + total.reduce((a, b) => a + b, 0), + analyte, + ).toBe(42); } // The source is gone, its old id leads to the target, and the reading's // printed name survived. const prisma = getPrismaClient(); - expect(await prisma.biomarker.findUnique({ where: { id: source.id } })) - .toBeNull(); + expect( + await prisma.biomarker.findUnique({ where: { id: source.id } }), + ).toBeNull(); const { GET: getMarker } = await import("@/app/api/biomarkers/[id]/route"); const stale = await getMarker( new NextRequest(`http://localhost/api/biomarkers/${source.id}`), @@ -442,9 +444,11 @@ describe("POST /api/biomarkers/{id}/merge", () => { "Ferr", "Ferritin (Serum)", ]); - expect(await getPrismaClient().labResult.count({ - where: { biomarkerId: c.id }, - })).toBe(3); + expect( + await getPrismaClient().labResult.count({ + where: { biomarkerId: c.id }, + }), + ).toBe(3); }); }); @@ -469,7 +473,9 @@ describe("aliases on the marker", () => { }, }); - const res = await put(alp.id, { aliases: ["Alk.P", "AP", "alkaline phosphatase"] }); + const res = await put(alp.id, { + aliases: ["Alk.P", "AP", "alkaline phosphatase"], + }); expect(res.status).toBe(200); expect((await res.json()).data.aliases).toEqual(["Alk.P", "AP"]); const rows = await prisma.biomarkerAlias.findMany({ @@ -712,9 +718,9 @@ describe("a spelling resolves on its own once chosen", () => { where: { id: ref.recordId }, }); expect(reading.biomarkerId).toBe(alp.id); - expect( - (await prisma.biomarkerAlias.findFirstOrThrow({})).name, - ).toBe("Alk. Phosph."); + expect((await prisma.biomarkerAlias.findFirstOrThrow({})).name).toBe( + "Alk. Phosph.", + ); const again = serialiseFact(fact, await loadMarkerIndex(prisma, USER_ID)); expect(again).toMatchObject({ biomarkerId: alp.id, matchKind: "alias" });