docs: refresh wiki for posting ledger (applied now derived from postings)
parent
fe7edfe973
commit
1276499df5
4 changed files with 101 additions and 24 deletions
|
|
@ -68,17 +68,36 @@ month
|
|||
|
||||
month_entry
|
||||
id, month_id FK month(id) ON DELETE CASCADE,
|
||||
section, name, planned, applied, notes NULL,
|
||||
section, name, planned, notes NULL,
|
||||
origin_name NULL, origin_planned NULL,
|
||||
source_entry_id FK entry(id) ON DELETE SET NULL,
|
||||
created_at, updated_at
|
||||
|
||||
posting
|
||||
id, month_entry_id FK month_entry(id) ON DELETE CASCADE,
|
||||
occurred_on DATE, amount NUMERIC(10,2),
|
||||
description NULL, payee NULL,
|
||||
created_at, updated_at
|
||||
|
||||
month_debt_target (singleton per month)
|
||||
month_id PK FK month(id) ON DELETE CASCADE,
|
||||
month_entry_id FK month_entry(id) ON DELETE SET NULL,
|
||||
updated_at
|
||||
```
|
||||
|
||||
`month_entry.applied` is a derived Python property, not a column:
|
||||
|
||||
```python
|
||||
class MonthEntry(Base):
|
||||
@property
|
||||
def applied(self) -> Decimal:
|
||||
return sum((p.amount for p in self.postings), Decimal("0")).quantize(...)
|
||||
```
|
||||
|
||||
The relationship uses `lazy="selectin"` so a page that iterates
|
||||
`month.entries` fires one extra SELECT for all postings at once,
|
||||
rather than N+1 lazy loads.
|
||||
|
||||
Creating a month copies every budget entry into `month_entry` and
|
||||
preserves `origin_name`, `origin_planned`, and `notes`. `source_entry_id`
|
||||
links back to the budget but is informational only; the snapshot is
|
||||
|
|
@ -97,7 +116,26 @@ Computed from `(name, planned)` vs `(origin_name, origin_planned)`:
|
|||
tag in indigo; progress bar tinted indigo.
|
||||
|
||||
Notes changes do NOT flip the deviation state. Notes are annotation, not
|
||||
plan drift.
|
||||
plan drift. Posting activity also does NOT flip deviation; postings are
|
||||
normal month flow.
|
||||
|
||||
## Postings (transaction ledger)
|
||||
|
||||
Each `month_entry` owns a list of postings. A posting is a single
|
||||
debit or credit in the ledger:
|
||||
|
||||
* `occurred_on` (required) — ISO date; not constrained to the month
|
||||
(a posting dated May 3 may live under the April entry if you want).
|
||||
* `amount` (required) — can be negative for refunds or corrections.
|
||||
* `description`, `payee` — optional free-text.
|
||||
|
||||
Some entries will have one posting per month (mortgage, rent); others
|
||||
will have many (groceries). The UI exposes them inside each expanded
|
||||
entry row with an inline add form.
|
||||
|
||||
User-facing language is "transactions" throughout; the schema and ORM
|
||||
use "posting" (classical accounting term, avoids collision with
|
||||
SQLAlchemy's own `Session.transaction` concept).
|
||||
|
||||
### Per-month debt target
|
||||
|
||||
|
|
@ -164,17 +202,24 @@ POST /debt-target set / clear debt target
|
|||
### Month (`src/quartermaster/routes_month.py`)
|
||||
|
||||
```
|
||||
GET /month/{year_month} view page or create-flow
|
||||
POST /month/{year_month}/create snapshot the budget (lands in Planning)
|
||||
POST /month/{year_month}/activate Planning -> Active
|
||||
POST /month/{year_month}/close Active -> Closed (requires applied zero = 0)
|
||||
POST /month/{year_month}/reopen Closed -> Active
|
||||
GET /month/{year_month} view page or create-flow
|
||||
POST /month/{year_month}/create snapshot the budget (lands in Planning)
|
||||
POST /month/{year_month}/activate Planning -> Active
|
||||
POST /month/{year_month}/close Active -> Closed (requires applied zero = 0)
|
||||
POST /month/{year_month}/reopen Closed -> Active
|
||||
POST /month/{year_month}/sections/{section}/entries add month entry
|
||||
POST /month/{year_month}/entries/{entry_id} update (name / planned / applied / notes)
|
||||
POST /month/{year_month}/entries/{entry_id} update (name / planned / notes)
|
||||
DELETE /month/{year_month}/entries/{entry_id} remove month entry
|
||||
POST /month/{year_month}/target set / clear per-month target
|
||||
POST /month/{year_month}/entries/{entry_id}/postings add a transaction
|
||||
POST /month/{year_month}/postings/{posting_id} update a transaction
|
||||
DELETE /month/{year_month}/postings/{posting_id} delete a transaction
|
||||
POST /month/{year_month}/target set / clear per-month target
|
||||
```
|
||||
|
||||
`update_month_entry` no longer accepts an `applied` field. Applied
|
||||
is managed by posting create / update / delete; direct writes are
|
||||
not allowed.
|
||||
|
||||
All mutation routes return the section partial plus OOB swaps for:
|
||||
|
||||
* The debt target card when a Debt Minimums row was added / edited / deleted
|
||||
|
|
@ -186,10 +231,19 @@ accurate without a reload.
|
|||
|
||||
## HTMX conventions
|
||||
|
||||
* Month entry rows carry three inline inputs (`name`, `planned`,
|
||||
`applied`) plus a notes input, each with `hx-post` and
|
||||
`hx-trigger="change"`. Each input sends only its own field; the server
|
||||
accepts any subset.
|
||||
* Month entry rows carry inline inputs for `name` and `planned` plus a
|
||||
notes input inside the expanded body. Each input has `hx-post` and
|
||||
`hx-trigger="change"`. Each input sends only its own field; the
|
||||
server accepts any subset.
|
||||
* Applied is rendered as static text (not an input). To change
|
||||
applied, the user expands the row's `<details>` and edits or adds
|
||||
postings.
|
||||
* Posting rows inside the expanded body have four inline inputs each
|
||||
(date, description, payee, amount) also using `hx-post` +
|
||||
`hx-trigger="change"`. The add-posting form is a normal `<form>`
|
||||
that POSTs and re-renders the section on success.
|
||||
* Per-section add forms (adding a new entry) are hidden behind a
|
||||
small `+ add <section>` disclosure so the default view is dense.
|
||||
* Section partials swap via `hx-target="#section-{section}"` +
|
||||
`hx-swap="outerHTML"`.
|
||||
* OOB swaps use `hx-swap-oob="outerHTML"` on the top-level element of
|
||||
|
|
@ -218,8 +272,10 @@ accurate without a reload.
|
|||
* `details.group` collapsible groups with a hairline CSS chevron that
|
||||
rotates on `[open]`
|
||||
* `.section` with a small-caps header and an inline subtotal
|
||||
* `table.entries` with a 4-column grid (name / planned / applied /
|
||||
actions) and a 2px progress bar on each row's bottom border
|
||||
* Month entry rows are `<details class="entry-block">` blocks with a
|
||||
5-column summary grid (caret / name / planned / applied / actions).
|
||||
The 2px progress bar rides the summary's bottom border.
|
||||
* Budget entry rows use `table.entries` with a 4-column grid.
|
||||
* `.target-section` with a burgundy left bar and `↳` margin glyph
|
||||
* `.state-badge` tracked-caps label with bullet separators
|
||||
|
||||
|
|
|
|||
14
Home.md
14
Home.md
|
|
@ -23,7 +23,10 @@ real spending on the applied side.
|
|||
minimums + Primary Debt Target), **Savings** (sinking funds), and
|
||||
**Flexible** (food, subscriptions, other).
|
||||
* `/month/YYYY-MM` — a snapshot of the budget for a specific calendar
|
||||
month. Adds an `applied` column per entry to track actuals. Rows carry
|
||||
month. Each entry becomes a `<details>` block: the summary row shows
|
||||
planned (click-to-edit) and applied (derived); expand the row to see
|
||||
its backing ledger of transactions (postings) and add more. Applied
|
||||
is always `sum(postings.amount)`, never typed directly. Rows carry
|
||||
deviation tags when edited or added post-snapshot.
|
||||
* **Zero Amount header** on both pages. Green when every dollar is
|
||||
assigned, amber when unassigned income remains, red when over-budget.
|
||||
|
|
@ -31,6 +34,10 @@ real spending on the applied side.
|
|||
number on month pages.
|
||||
* **Per-entry notes** — free-text annotation on every row. Copied through
|
||||
at snapshot time; editable inline.
|
||||
* **Backing transaction ledger** — every month entry owns a list of
|
||||
postings (date, amount, optional description and payee). Applied is
|
||||
derived from the ledger. One posting for fixed items like rent,
|
||||
many for variable ones like groceries.
|
||||
|
||||
## Month lifecycle
|
||||
|
||||
|
|
@ -64,8 +71,9 @@ where leftover applied dollars belong; filling it is the user's job.
|
|||
Shipped: initial scaffold, monthly snapshot with deviation tags, database
|
||||
backup script with alembic auto-hook, zero-amount header, section groups
|
||||
with collapsible headers, sinking funds section, per-entry notes, month
|
||||
lifecycle with the balance gate, UI redesign in Barlow Condensed + logo.
|
||||
See the [Roadmap](Roadmap) for what is next.
|
||||
lifecycle with the balance gate, UI redesign in Barlow Condensed + logo,
|
||||
backing transaction ledger with `applied` derived from postings. See the
|
||||
[Roadmap](Roadmap) for what is next.
|
||||
|
||||
## Code
|
||||
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ Applied migrations at time of writing:
|
|||
| `03ebe3c07262` | add month snapshot tables (`month`, `month_entry`, `month_debt_target`) |
|
||||
| `ec804bdf366d` | add `notes` column to `entry` and `month_entry` |
|
||||
| `a4ec4f8f6e9f` | add month lifecycle columns (`state`, `activated_at`, `closed_at`) |
|
||||
| `cc60e7f73a1c` | add `posting` ledger table, seed opening-balance postings, drop `month_entry.applied` |
|
||||
|
||||
After pulling new code, `uv run alembic upgrade head` walks the chain
|
||||
and the backup hook fires between each hop.
|
||||
|
|
|
|||
24
Roadmap.md
24
Roadmap.md
|
|
@ -13,19 +13,31 @@
|
|||
| 13 | Notes field per entry | 2026-04-17 |
|
||||
| 15 | Month lifecycle: Planning, Active, Closed with reconciliation gate | 2026-04-17 |
|
||||
| 17 | UI redesign: condensed-sans ledger style with logo in the zero hero | 2026-04-17 |
|
||||
| 19 | Backing transaction ledger: replace applied field with Postings | 2026-04-17 |
|
||||
|
||||
## Deferred
|
||||
|
||||
Things we have explicitly called out as future work. File an issue when
|
||||
the time comes.
|
||||
|
||||
### Transaction log behind applied
|
||||
### Constrain posting dates to the month
|
||||
|
||||
Replace the hand-edited `applied` value with a log of dated
|
||||
transactions per entry per month. `applied` becomes a computed sum.
|
||||
Implies a new `month_transaction` table, a UI for entering
|
||||
transactions, and a migration path that preserves existing applied
|
||||
values as an opening balance.
|
||||
Postings are free-dated today (a May 3 transaction can live on an
|
||||
April entry). Most users would expect the date to fall within the
|
||||
month of the entry. A small validator + UI hint would tighten this
|
||||
without closing the escape hatch for one-off overrides.
|
||||
|
||||
### Bank-statement reconciliation
|
||||
|
||||
A view that ingests a CSV or OFX export and matches rows against
|
||||
existing postings. Would need a "cleared" flag on postings and a
|
||||
match-by-amount + date workflow.
|
||||
|
||||
### Recurring posting schedules
|
||||
|
||||
For regular bills (mortgage on the 1st, subscription on the 15th),
|
||||
let the user register a recurrence rule on a month_entry so the
|
||||
posting appears automatically when activating a new month.
|
||||
|
||||
### Closed-month "archived" visual treatment
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue