fix(measurements): keep computed scores out of the bulk delete

The row menu stopped offering edit and delete on a computed score
(recovery, strain, stress), but the list still drew its selection
checkbox and "select all" took it into the page, so the bulk bar could
delete a page of scores, and the tombstones kept the recompute out.

A computed row now has no checkbox on either layout and stays out of
the page selection, and the bulk-delete route skips computed rows if a
client sends them anyway. The iOS app treats these rows as read-only
and does not rely on deleting them.
This commit is contained in:
Marc-André Bombeck committed 2026-11-12 12:48:03 +01:00
1 parent a4b14609bf
commit 048dbbee9f
6 files changed
+98 -9

No files matched your search

@@ -156,6 +156,20 @@ describe("POST /api/measurements/bulk-delete", () => {
});
});
it("leaves a computed score out of the selection it deletes", async () => {
// The read is scoped like the write, so a computed row never reaches it.
vi.mocked(prisma.measurement.findMany).mockResolvedValue([] as never);
vi.mocked(prisma.measurement.updateMany).mockResolvedValue({
count: 0,
} as never);
await POST(postReq({ ids: ["score-1"] }));
expect(
vi.mocked(prisma.measurement.findMany).mock.calls[0][0]?.where,
).toMatchObject({ source: { not: "COMPUTED" } });
});
it("collapses the rollup recompute to the unique (type, day) set, not per-row", async () => {
// 4 deleted rows: 2× WEIGHT on the same UTC day, 1× WEIGHT next day,
// 1× PULSE same day as the first → 3 distinct (type, day) keys.
@@ -81,9 +81,17 @@ async function postBulkDelete(request: NextRequest): Promise<Response> {
// other half and the cuff's pulse, `bloodPressureSiblings`) into the same
// statement, so the shared `deletedAt` lets the restore route bring the
// reading back whole.
//
// A computed score (recovery, strain, stress) is skipped: the server
// rewrites it from its inputs, and a tombstone kept the recompute out.
const { affected, count } = await prisma.$transaction(async (tx) => {
const selected = await tx.measurement.findMany({
where: { id: { in: ids }, userId: user.id, deletedAt: null },
where: {
id: { in: ids },
userId: user.id,
deletedAt: null,
source: { not: "COMPUTED" },
},
select: { id: true, type: true, source: true, measuredAt: true },
});
const siblings = await bloodPressureSiblings(tx, user.id, selected, "live");