Monthly budget view with snapshot-from-config and applied tracking #3

Closed
opened 2026-04-17 11:31:51 -06:00 by claude-code · 0 comments
Collaborator

Goal

Add per-month budget pages. The existing / page is the budget configuration: the plan. A month page is a snapshot of that plan for a specific calendar month (YYYY-MM), with an additional applied column per entry to track actuals against planned amounts.

URL shape

  • / budget configuration (unchanged)
  • /month/{YYYY-MM} monthly view for that month; 404 with a "Create this month" action if it does not yet exist
  • POST /month/{YYYY-MM}/create creates the month by snapshotting the current budget

Snapshot semantics

On creation, the month is populated from the current budget:

  1. Copy every budget entry to month_entry preserving section, name, and amount (renamed planned in this context). Applied starts at 0.00.
  2. Record source_entry_id pointing back at the budget entry (nullable, ON DELETE SET NULL) so we know where the row came from, even if the budget entry is later deleted.
  3. Also record origin_name and origin_planned so we can detect deviation after editing.
  4. Copy the current budget's debt target pointer to the per-month target, resolving through source_entry_id.

After creation, month entries are fully editable: name, planned, applied, and add/delete rows. The debt target can be re-selected per month.

Deviation indicator

Each month entry row compares current (name, planned) against (origin_name, origin_planned):

  • unchanged: both match, plain row.
  • edited: name or planned differs from origin, annotate the row (e.g. small "modified" label + subtle color tint).
  • new_in_month: no origin (row added after snapshot), annotate as "new this month".

Entries whose source budget row has been deleted since snapshot are still unchanged (the snapshot is self-contained); no special marker for that case.

Navigation

  • Header on / grows a "This month" link to /month/{current_YYYY-MM} and a month picker listing all existing months.
  • Month page shows: month label, prev-month button, next-month button, link back to /.
  • Prev/next buttons navigate to adjacent YYYY-MM slugs regardless of whether those months exist; landing on a non-existent one shows the "Create this month" UI.

Data model additions

month
  id           INTEGER PK
  year_month   TEXT NOT NULL UNIQUE   # "YYYY-MM"
  created_at   TIMESTAMP

month_entry
  id               INTEGER PK
  month_id         INTEGER NOT NULL REFERENCES month(id) ON DELETE CASCADE
  section          TEXT NOT NULL        # same enum as budget
  name             TEXT NOT NULL
  planned          NUMERIC(10,2) NOT NULL
  applied          NUMERIC(10,2) NOT NULL DEFAULT 0
  origin_name      TEXT NULL            # NULL for rows added after snapshot
  origin_planned   NUMERIC(10,2) NULL
  source_entry_id  INTEGER NULL REFERENCES entry(id) ON DELETE SET NULL
  created_at       TIMESTAMP
  updated_at       TIMESTAMP

month_debt_target                          # singleton per month
  month_id         INTEGER PK REFERENCES month(id) ON DELETE CASCADE
  month_entry_id   INTEGER NULL REFERENCES month_entry(id) ON DELETE SET NULL
  updated_at       TIMESTAMP

Acceptance criteria

  • /month/{YYYY-MM} for a non-existent month returns a page with a "Create this month" button
  • Creating the month snapshots the current budget entries and the current debt target
  • Each month entry row shows planned, applied (editable), and a delete button
  • Applied can be updated via HTMX without a full page reload; the section total applied and total planned both update
  • Names and planned amounts are editable via inline HTMX form; edits flip the row's deviation state
  • Adding a new row within a month works and marks the row as "new this month"
  • Prev/next month buttons work; navigating to a non-existent adjacent month lands on the create-flow
  • Debt target can be re-selected per month
  • Alembic migration adds the new tables without disturbing existing data
  • Pytest covers: month creation copies the budget, deviation flags flip correctly on edit, applied updates, prev/next navigation, per-month target change does not affect other months
  • README updated

Out of scope

  • Transaction log that rolls up into applied (deferred; may replace the applied field later)
  • Month "close out" or carryover between months
  • Cross-month summary or charts
  • Copy-forward month (a future month that snapshots another month instead of the budget)
## Goal Add per-month budget pages. The existing `/` page is the *budget configuration*: the plan. A month page is a snapshot of that plan for a specific calendar month (`YYYY-MM`), with an additional `applied` column per entry to track actuals against planned amounts. ## URL shape * `/` budget configuration (unchanged) * `/month/{YYYY-MM}` monthly view for that month; 404 with a "Create this month" action if it does not yet exist * `POST /month/{YYYY-MM}/create` creates the month by snapshotting the current budget ## Snapshot semantics On creation, the month is populated from the current budget: 1. Copy every budget entry to `month_entry` preserving `section`, `name`, and `amount` (renamed `planned` in this context). Applied starts at `0.00`. 2. Record `source_entry_id` pointing back at the budget entry (nullable, `ON DELETE SET NULL`) so we know where the row came from, even if the budget entry is later deleted. 3. Also record `origin_name` and `origin_planned` so we can detect deviation after editing. 4. Copy the current budget's debt target pointer to the per-month target, resolving through `source_entry_id`. After creation, month entries are fully editable: name, planned, applied, and add/delete rows. The debt target can be re-selected per month. ## Deviation indicator Each month entry row compares current `(name, planned)` against `(origin_name, origin_planned)`: * `unchanged`: both match, plain row. * `edited`: name or planned differs from origin, annotate the row (e.g. small "modified" label + subtle color tint). * `new_in_month`: no origin (row added after snapshot), annotate as "new this month". Entries whose source budget row has been deleted since snapshot are still `unchanged` (the snapshot is self-contained); no special marker for that case. ## Navigation * Header on `/` grows a "This month" link to `/month/{current_YYYY-MM}` and a month picker listing all existing months. * Month page shows: month label, prev-month button, next-month button, link back to `/`. * Prev/next buttons navigate to adjacent `YYYY-MM` slugs regardless of whether those months exist; landing on a non-existent one shows the "Create this month" UI. ## Data model additions ``` month id INTEGER PK year_month TEXT NOT NULL UNIQUE # "YYYY-MM" created_at TIMESTAMP month_entry id INTEGER PK month_id INTEGER NOT NULL REFERENCES month(id) ON DELETE CASCADE section TEXT NOT NULL # same enum as budget name TEXT NOT NULL planned NUMERIC(10,2) NOT NULL applied NUMERIC(10,2) NOT NULL DEFAULT 0 origin_name TEXT NULL # NULL for rows added after snapshot origin_planned NUMERIC(10,2) NULL source_entry_id INTEGER NULL REFERENCES entry(id) ON DELETE SET NULL created_at TIMESTAMP updated_at TIMESTAMP month_debt_target # singleton per month month_id INTEGER PK REFERENCES month(id) ON DELETE CASCADE month_entry_id INTEGER NULL REFERENCES month_entry(id) ON DELETE SET NULL updated_at TIMESTAMP ``` ## Acceptance criteria * [ ] `/month/{YYYY-MM}` for a non-existent month returns a page with a "Create this month" button * [ ] Creating the month snapshots the current budget entries and the current debt target * [ ] Each month entry row shows planned, applied (editable), and a delete button * [ ] Applied can be updated via HTMX without a full page reload; the section total applied and total planned both update * [ ] Names and planned amounts are editable via inline HTMX form; edits flip the row's deviation state * [ ] Adding a new row within a month works and marks the row as "new this month" * [ ] Prev/next month buttons work; navigating to a non-existent adjacent month lands on the create-flow * [ ] Debt target can be re-selected per month * [ ] Alembic migration adds the new tables without disturbing existing data * [ ] Pytest covers: month creation copies the budget, deviation flags flip correctly on edit, applied updates, prev/next navigation, per-month target change does not affect other months * [ ] README updated ## Out of scope * Transaction log that rolls up into applied (deferred; may replace the applied field later) * Month "close out" or carryover between months * Cross-month summary or charts * Copy-forward month (a future month that snapshots another month instead of the budget)
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: archeious/quartermaster#3
No description provided.