fix(measurements): delete a blood-pressure reading as one

Systolic, diastolic and the cuff's pulse are three rows with no link
column, and the single DELETE tombstoned only the row it was given, so
deleting a reading's systolic left its diastolic and pulse behind as half
a reading. The bulk delete had the same gap.

A reading is the rows with the same owner, the same source and the
identical measuredAt: that is how the entry form, the MCP write, a
Withings measure group and a HealthKit correlation all store one, and how
the FHIR read pairs a diastolic onto its systolic. The looser five-minute
pairing of the chart reads is right for drawing a point and wrong for
taking rows away.

Deleting a systolic or diastolic now tombstones the rest of its reading in
the same transaction with the same deletedAt, each row with its own
syncVersion bump so paired clients see every tombstone. The restore route
brings back the rows deleted together with a named half (identical
deletedAt), so the undo, which names only the row that was tapped, puts
the whole reading back, from the web list and from any client that calls
it the same way. A pulse is still deleted on its own, and a row deleted at
another moment stays deleted. The response shapes do not change; the
OpenAPI descriptions say what the counts now include.
This commit is contained in:
Marc-André Bombeck committed 2026-10-29 08:56:19 +01:00
1 parent d88313e929
commit e2998183c8
9 files changed
+430 -48

No files matched your search

+10 -3
View File
@@ -4825,7 +4825,10 @@ paths:
tags:
- Measurements
summary: Delete a measurement
description: Soft-deletes the row (tombstone) so paired clients can reconcile the deletion.
description: "Soft-deletes the row (tombstone) so paired clients can reconcile the deletion. A blood-pressure systolic
or diastolic takes the rest of its reading with it: the other half and the pulse with the same source and the
identical `measuredAt`, tombstoned in the same transaction with the same `deletedAt`, each with its own
`syncVersion` bump. A pulse is deleted on its own."
parameters:
- in: path
name: id
@@ -4936,7 +4939,8 @@ paths:
description: Soft-deletes (tombstones) up to 200 of the caller's measurement rows in one call, mirroring the
single-DELETE contract. Idempotent via the `Idempotency-Key` header; rate-limited
`measurements:bulk-delete:<userId>`. Forged / foreign / already-deleted ids are silently skipped — `deleted` is
the count of rows actually tombstoned. The deletions surface in `/api/sync/changes` as tombstones.
the count of rows actually tombstoned, which includes the rest of any blood-pressure reading a selected systolic
or diastolic belongs to (as on the single DELETE). The deletions surface in `/api/sync/changes` as tombstones.
parameters:
- *a6
requestBody:
@@ -4965,7 +4969,10 @@ paths:
description: Un-tombstones up to 200 of the caller's soft-deleted measurement rows in one call — the delete-Undo
counterpart to `/api/measurements/bulk-delete`. Idempotent via the `Idempotency-Key` header; rate-limited
`measurements:restore:<userId>`. Forged / foreign / not-deleted ids are silently skipped — `restored` is the
count of rows actually un-tombstoned. The restored rows re-surface in `/api/sync/changes` as upserts.
count of rows actually un-tombstoned. A blood-pressure systolic or diastolic also restores the rows of its
reading that were deleted with it (same source, identical `measuredAt` and `deletedAt`), so naming one half
undoes the whole reading; a row deleted at another moment stays deleted. The restored rows re-surface in
`/api/sync/changes` as upserts.
parameters:
- *a6
requestBody: