fix(recurring): updateRecurringCost can't touch archived/endedFrom (X4)

setRecurringCostEndedFrom is the only place that keeps archived and
endedFrom paired, and endRecurringCost/restoreRecurringCost/the Ends column
already delegate to it — but updateRecurringCost still typed its `changes`
as Partial<Omit<RecurringCost, "id">> and Object.assign'd it, so a caller
passing either field alone would desynchronise the pair. No current call
site does this, so it was latent; excluding both keys from the accepted
type makes it impossible rather than merely unexercised.
This commit is contained in:
Lucas Winther committed 2026-09-04 09:15:20 +02:00
1 parent 1281362388
commit e3a1fb366b
1 file changed
+11 -2
+11 -2
View File
@@ -446,13 +446,22 @@ export function addRecurringCost(
return created;
}
/**
* `archived` and `endedFrom` are excluded on purpose: they are a coherent
* pair (`archived` mirrors "`endedFrom` is set") maintained ONLY by
* `setRecurringCostEndedFrom`, and a caller able to pass either through here
* could desynchronise them — a bill that reads as live but is dimmed, or
* ended but not. Route a change to either field through
* `setRecurringCostEndedFrom` (or its `endRecurringCost`/`restoreRecurringCost`
* convenience wrappers) instead.
*/
export function updateRecurringCost(
draft: Dataset,
id: RecurringCostId,
changes: Partial<Omit<RecurringCost, "id">>,
changes: Partial<Omit<RecurringCost, "id" | "archived" | "endedFrom">>,
): void {
const cost = requireRecurringCost(draft, id);
const resolved: Partial<Omit<RecurringCost, "id">> = { ...changes };
const resolved: Partial<Omit<RecurringCost, "id" | "archived" | "endedFrom">> = { ...changes };
if (changes.recurrence) {
resolved.recurrence = requireRecurrence(changes.recurrence);