feat(labs): delete a biomarker together with its values

Deleting a biomarker now removes its recorded values in the same
userId-narrowed transaction instead of unlinking them via the SetNull
FK. Removing a marker is a deliberate "drop this and its history"
action, so leaving orphaned, unlinked readings behind was surprising.

Update the delete-confirmation copy in all six locales to state that
the values are removed too.
This commit is contained in:
Marc-André Bombeck committed 2026-06-28 13:21:28 +02:00
1 parent d8c82910a7
commit 51aa37f654
7 files changed
+21 -14

No files matched your search

+1 -1
View File
@@ -6944,7 +6944,7 @@
"deletedToast": "Biomarker gelöscht",
"deleteError": "Biomarker konnte nicht gelöscht werden",
"deleteConfirmTitle": "Biomarker löschen?",
"deleteConfirmDescription": "Die Definition wird entfernt. Bestehende Werte bleiben erhalten, aber ohne Verknüpfung.",
"deleteConfirmDescription": "Der Biomarker und alle erfassten Werte werden dauerhaft gelöscht. Dies kann nicht rückgängig gemacht werden.",
"emptyTitle": "Noch keine Biomarker",
"emptyDescription": "Lege Marker wie LDL oder HbA1c an, um Werte zu erfassen.",
"loadError": "Biomarker konnten nicht geladen werden",
+1 -1
View File
@@ -6944,7 +6944,7 @@
"deletedToast": "Biomarker deleted",
"deleteError": "Could not delete the biomarker",
"deleteConfirmTitle": "Delete biomarker?",
"deleteConfirmDescription": "The definition is removed. Existing readings are kept but unlinked.",
"deleteConfirmDescription": "The biomarker and all of its recorded values are permanently deleted. This cannot be undone.",
"emptyTitle": "No biomarkers yet",
"emptyDescription": "Define markers like LDL or HbA1c to record values against them.",
"loadError": "Could not load biomarkers",
+1 -1
View File
@@ -6944,7 +6944,7 @@
"deletedToast": "Biomarcador eliminado",
"deleteError": "No se pudo eliminar el biomarcador",
"deleteConfirmTitle": "¿Eliminar biomarcador?",
"deleteConfirmDescription": "Se elimina la definición. Las mediciones existentes se conservan pero se desvinculan.",
"deleteConfirmDescription": "El biomarcador y todos sus valores registrados se eliminan de forma permanente. Esta acción no se puede deshacer.",
"emptyTitle": "Aún no hay biomarcadores",
"emptyDescription": "Define marcadores como LDL o HbA1c para registrar valores.",
"loadError": "No se pudieron cargar los biomarcadores",
+1 -1
View File
@@ -6944,7 +6944,7 @@
"deletedToast": "Biomarqueur supprimé",
"deleteError": "Impossible de supprimer le biomarqueur",
"deleteConfirmTitle": "Supprimer le biomarqueur ?",
"deleteConfirmDescription": "La définition est supprimée. Les mesures existantes sont conservées mais dissociées.",
"deleteConfirmDescription": "Le biomarqueur et toutes ses valeurs enregistrées sont définitivement supprimés. Cette action est irréversible.",
"emptyTitle": "Aucun biomarqueur pour l'instant",
"emptyDescription": "Définissez des marqueurs comme LDL ou HbA1c pour enregistrer des valeurs.",
"loadError": "Impossible de charger les biomarqueurs",
+1 -1
View File
@@ -6944,7 +6944,7 @@
"deletedToast": "Biomarcatore eliminato",
"deleteError": "Impossibile eliminare il biomarcatore",
"deleteConfirmTitle": "Eliminare il biomarcatore?",
"deleteConfirmDescription": "La definizione viene rimossa. Le misurazioni esistenti restano ma vengono scollegate.",
"deleteConfirmDescription": "Il biomarcatore e tutti i suoi valori registrati vengono eliminati definitivamente. L'operazione non è reversibile.",
"emptyTitle": "Ancora nessun biomarcatore",
"emptyDescription": "Definisci marcatori come LDL o HbA1c per registrare i valori.",
"loadError": "Impossibile caricare i biomarcatori",
+1 -1
View File
@@ -6944,7 +6944,7 @@
"deletedToast": "Biomarker usunięty",
"deleteError": "Nie udało się usunąć biomarkera",
"deleteConfirmTitle": "Usunąć biomarker?",
"deleteConfirmDescription": "Definicja zostanie usunięta. Istniejące pomiary pozostaną, ale bez powiązania.",
"deleteConfirmDescription": "Biomarker i wszystkie jego zapisane wartości zostaną trwale usunięte. Tej operacji nie można cofnąć.",
"emptyTitle": "Brak biomarkerów",
"emptyDescription": "Zdefiniuj markery, takie jak LDL lub HbA1c, aby zapisywać wartości.",
"loadError": "Nie udało się wczytać biomarkerów",
+15 -8
View File
@@ -24,10 +24,10 @@ import { effectiveBound, isInvertedRange } from "@/lib/validations/labs";
*
* PUT applies a partial edit (`data` built field-by-field; an explicit `null`
* clears `context` / `panel` / a bound, an omitted key leaves it untouched).
* DELETE hard-deletes the catalog definition — the `onDelete: SetNull` FK on
* `LabResult.biomarkerId` unlinks every reading without losing it (the row
* keeps its legacy `analyte` / `unit` historical fields). Cross-user rows
* surface as 404 (existence sealed).
* DELETE hard-deletes the catalog definition together with every reading it
* owns, in one userId-narrowed transaction — removing a biomarker is a
* deliberate "drop this and its values" action. Cross-user rows surface as
* 404 (existence sealed).
*/
type RouteParams = { params: Promise<{ id: string }> };
@@ -187,10 +187,17 @@ export const DELETE = apiHandler(
return apiError("Biomarker not found", 404);
}
// Hard delete — the `onDelete: SetNull` FK unlinks every reading rather
// than cascading it away. The readings keep their legacy `analyte` /
// `unit` so the history survives the catalog edit.
await prisma.biomarker.delete({ where: { id } });
// Hard delete the marker AND its readings in one transaction. Deleting a
// biomarker is a deliberate "remove this and its history" action, so the
// readings go with it rather than lingering as orphaned, unlinked rows.
// Both legs are userId-narrowed so a cross-user id can never reach another
// account's data.
await prisma.$transaction([
prisma.labResult.deleteMany({
where: { userId: user.id, biomarkerId: id },
}),
prisma.biomarker.delete({ where: { id } }),
]);
await auditLog("biomarker.delete", {
userId: user.id,