Month lifecycle: Planning, Active, Closed states with reconciliation gate #15
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
Each month moves through three explicit states. Closing requires the applied balance to be exactly
$0.00. The Primary Debt Target is a hint about where leftover money should go, but the allocation is manual: the user edits the target's applied amount until the month balances. Nothing is swept automatically.States
$0.00appliedCreating a month via
POST /month/YYYY-MM/createlands it in Planning.Activate
POST /month/YYYY-MM/activatemoves Planning → Active and stampsactivated_at. No other validation.Close
POST /month/YYYY-MM/closemoves Active → Closed and stampsclosed_at. Rejected with 400 if:income.applied - non_income.applied) is not exactly$0.00The UI shows the Close button disabled when the balance is not zero, with hover text explaining why. Server-side validation is authoritative.
Reopen
POST /month/YYYY-MM/reopenmoves Closed → Active and nullsclosed_at. No sweep to reverse (nothing was swept). User can edit, then re-close when balanced again.Schema additions
Edit locking on closed months
When
state = closed, all mutation routes for that month reject with 400:UI reflects this: inputs get the
disabledattribute, delete buttons are hidden, add forms are hidden, the target selector is hidden. A banner replaces them with "Closed on " and a Reopen button.UI
The Primary Debt Target card keeps its current shape. Its presence signals where leftover dollars "belong," but filling them in is the user's job via editing
appliedon the target row (or any other row).Acceptance criteria
state,activated_at,closed_atwith defaultscreate_monthlands in Planning statePOST /month/YYYY-MM/activatemoves Planning → Active and stampsactivated_atPOST /month/YYYY-MM/closerejects unless state is Active AND applied zero == 0POST /month/YYYY-MM/reopenmoves Closed → ActiveOut of scope
activated_at/closed_attimestamps