Monthly budget view with snapshot and applied tracking #4

Merged
claude-code merged 5 commits from feat/3-monthly-view into main 2026-04-17 11:59:09 -06:00
Collaborator

Closes #3 (will be manually closed on merge).

Summary

Adds per-month budget pages that snapshot the budget at creation and track actuals against planned amounts. The existing / page remains the budget configuration.

  • /month/YYYY-MM renders the month; non-existent months return a single-button create flow
  • POST /month/YYYY-MM/create snapshots every budget entry and the current debt target, then redirects via HX-Redirect
  • Each month entry row has three inline HTMX-wired inputs (name, planned, applied); change any one and the section partial re-renders with fresh totals and deviation tags
  • Deviation states: unchanged, edited (orange tint + "modified" tag), new_in_month (blue tint + "new this month" tag)
  • Per-month debt target is selectable independently of the budget target
  • Budget page gains a "This month" link and a month picker; month pages have prev / next / picker / back-to-budget nav

Schema additions

month               (id, year_month UNIQUE, created_at)
month_entry         (id, month_id FK CASCADE, section, name, planned, applied,
                     origin_name NULL, origin_planned NULL,
                     source_entry_id FK SET NULL, created_at, updated_at)
month_debt_target   (month_id PK FK CASCADE, month_entry_id FK SET NULL,
                     updated_at)

Snapshot preserves origin_name / origin_planned so the UI can flag rows that have been edited since the copy. source_entry_id is informational only after creation; deleting the referenced budget row sets it null without affecting the month's data (deviation still reports unchanged, since the snapshot is self-contained).

Test plan

  • uv run pytest passes (36/36, +23 new)
  • uv run alembic upgrade head applies both migrations cleanly
  • Live curl smoke:
    • /month/2026-04 for missing month shows "No snapshot yet" with create button
    • POST /month/2026-04/create returns 204 with HX-Redirect
    • After create, page shows all four seeded entries with state-unchanged rows
    • POST /month/2026-04/entries/{id} with planned=... flips row to state-edited and renders the modified tag
    • POST with applied=... updates the total applied without touching planned
    • POST to /month/2026-04/sections/other/entries adds a row with state-new_in_month
    • POST /month/2026-04/target changes the per-month target
    • Deleting a debt minimum OOB-swaps the target card; shows "No target selected" when the target row is deleted
    • GET /month/2026-13 returns 404
    • Budget page shows the This-month link and month picker

Out of scope

  • Transaction log backing the applied field
  • Month close-out or carryover
  • Cross-month summaries / charts
Closes #3 (will be manually closed on merge). ## Summary Adds per-month budget pages that snapshot the budget at creation and track actuals against planned amounts. The existing `/` page remains the budget configuration. * `/month/YYYY-MM` renders the month; non-existent months return a single-button create flow * `POST /month/YYYY-MM/create` snapshots every budget entry and the current debt target, then redirects via `HX-Redirect` * Each month entry row has three inline HTMX-wired inputs (name, planned, applied); change any one and the section partial re-renders with fresh totals and deviation tags * Deviation states: `unchanged`, `edited` (orange tint + "modified" tag), `new_in_month` (blue tint + "new this month" tag) * Per-month debt target is selectable independently of the budget target * Budget page gains a "This month" link and a month picker; month pages have prev / next / picker / back-to-budget nav ## Schema additions ``` month (id, year_month UNIQUE, created_at) month_entry (id, month_id FK CASCADE, section, name, planned, applied, origin_name NULL, origin_planned NULL, source_entry_id FK SET NULL, created_at, updated_at) month_debt_target (month_id PK FK CASCADE, month_entry_id FK SET NULL, updated_at) ``` Snapshot preserves `origin_name` / `origin_planned` so the UI can flag rows that have been edited since the copy. `source_entry_id` is informational only after creation; deleting the referenced budget row sets it null without affecting the month's data (deviation still reports `unchanged`, since the snapshot is self-contained). ## Test plan * [x] `uv run pytest` passes (36/36, +23 new) * [x] `uv run alembic upgrade head` applies both migrations cleanly * [x] Live curl smoke: * [x] `/month/2026-04` for missing month shows "No snapshot yet" with create button * [x] `POST /month/2026-04/create` returns 204 with `HX-Redirect` * [x] After create, page shows all four seeded entries with `state-unchanged` rows * [x] `POST /month/2026-04/entries/{id}` with `planned=...` flips row to `state-edited` and renders the modified tag * [x] `POST` with `applied=...` updates the total applied without touching planned * [x] `POST` to `/month/2026-04/sections/other/entries` adds a row with `state-new_in_month` * [x] `POST /month/2026-04/target` changes the per-month target * [x] Deleting a debt minimum OOB-swaps the target card; shows "No target selected" when the target row is deleted * [x] `GET /month/2026-13` returns 404 * [x] Budget page shows the This-month link and month picker ## Out of scope * Transaction log backing the applied field * Month close-out or carryover * Cross-month summaries / charts
claude-code added 5 commits 2026-04-17 11:40:31 -06:00
A month is a snapshot of the budget. MonthEntry holds the copied planned
amount plus applied and origin_name/origin_planned so the UI can mark
edited rows. source_entry_id links back to the budget but is nullable
with ON DELETE SET NULL, so deleting a budget row after snapshot leaves
the month intact. MonthDebtTarget is one row per month via CASCADE from
month.

Refs #3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
create_month copies every budget entry into month_entry with
origin_name/origin_planned retained, resolves the budget's debt target
through source_entry_id to the corresponding MonthEntry, and is
idempotent. deviation_state classifies each row as unchanged, edited,
or new_in_month. Year-month handling (validation, shift across year
boundaries) lives here so the route layer stays thin.

Refs #3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Non-existent months return a page with a single "Create this month"
button; create POSTs return HX-Redirect to the newly-created month.
Each entry row carries three inline HTMX-wired inputs (name, planned,
applied) that trigger on change, posting only the field that changed.
Edits swap the section partial so totals and deviation tags update
together. Deleting a debt minimum in a month also re-renders the
target card via OOB swap. The budget page grows a This-month link and
a month picker; each month page has prev / next / picker / back-to-
config controls.

Refs #3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Service tests assert that create_month produces origin fields matching
the budget, that edits flip deviation_state to edited, that added rows
are new_in_month, and that a budget entry deleted after snapshot leaves
the month entry unchanged. Route tests exercise the create flow,
applied updates, name edits producing the modified tag, per-month
target isolation, and the malformed-year-month 404.

Refs #3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refs #3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
archeious force-pushed feat/3-monthly-view from 2d7ce333ea to b2d16120d2 2026-04-17 11:57:40 -06:00 Compare
claude-code merged commit 38c8921885 into main 2026-04-17 11:59:09 -06:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
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#4
No description provided.