mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 15:02:04 +00:00
fix(cycle): give back a start that a mis-tapped earlier start folded in
A start logged within one period before an existing start folds that start into itself and soft-deletes it, since the two are one period entered out of order. Nothing recorded which start was folded, so taking the earlier start back could not bring the later one back: a mis-tap on the 5th with a real start on the 10th, then deleted, left the 10th gone and the cycle before it reopened. Migration 0345 adds menstrual_cycles.absorbed_into_id. The folded row names the start that absorbed it; removing that start restores it, re-derives its own end and hands its days back to it. The column is needed because a soft-deleted row alone cannot tell a folded start from one the person deleted, and the audit trail is not state a removal can read. Deleting a cycle directly now runs the same shared removal as deleting its day-log.
This commit is contained in:
1 parent
f28153c832
commit
e822154936
5 files changed
+155
-17
No files matched your search
@@ -19,7 +19,7 @@ import { auditLog } from "@/lib/auth/audit";
|
||||
import { overwriteDetails } from "@/lib/sharing/audit-details";
|
||||
import { apiError, getClientIp } from "@/lib/api-response";
|
||||
import { requireCycleEnabled } from "@/lib/cycle/gate";
|
||||
import { reanchorAfterRemovedStart } from "@/lib/cycle/cycle-boundaries";
|
||||
import { removeCycleStartedOn } from "@/lib/cycle/cycle-boundaries";
|
||||
|
||||
type RouteParams = { params: Promise<{ id: string }> };
|
||||
|
||||
@@ -45,13 +45,13 @@ export const DELETE = apiHandler(
|
||||
|
||||
// Tombstone + re-anchor as one unit: a neighbour re-derived against a
|
||||
// half-applied delete would read the row being removed as still live.
|
||||
const reanchored = await prisma.$transaction(async (db) => {
|
||||
await db.menstrualCycle.update({
|
||||
where: { id },
|
||||
data: { deletedAt: new Date(), syncVersion: { increment: 1 } },
|
||||
});
|
||||
return reanchorAfterRemovedStart(db, user.id, existing.startDate);
|
||||
});
|
||||
// The shared removal also gives back a start this one folded in and
|
||||
// hands the removed cycle's days back. `null` is a cycle already
|
||||
// tombstoned: nothing left to move.
|
||||
const removed = await prisma.$transaction((db) =>
|
||||
removeCycleStartedOn(db, user.id, existing.startDate),
|
||||
);
|
||||
const reanchored = removed?.reanchored ?? null;
|
||||
|
||||
await auditLog("cycle.cycle.delete", {
|
||||
userId: user.id,
|
||||
@@ -61,6 +61,9 @@ export const DELETE = apiHandler(
|
||||
details: {
|
||||
cycleId: id,
|
||||
startDate: existing.startDate,
|
||||
...(removed && removed.restoredCycleIds.length > 0
|
||||
? { restoredCycleIds: removed.restoredCycleIds }
|
||||
: {}),
|
||||
...(reanchored
|
||||
? {
|
||||
reanchoredCycleId: reanchored.cycleId,
|
||||
|
||||
Reference in new issue
Block a user