feat: refine month totals and recurring purchase entry
This commit is contained in:
1 parent
7cccd7a41a
commit
c250bbe476
30 files changed
+634
-67
No files matched your search
@@ -62,6 +62,12 @@ aggregate envelope figures over `foldBalances` / `figuresFor`. Overall Spent
|
||||
also includes unassigned purchase slices, which have no envelope to fold. A
|
||||
closing balance is read from `byMonth[11].remaining`, never re-summed.
|
||||
|
||||
The month header's Actual available is entered income minus all actual Spent
|
||||
for that month. It includes refunds, unassigned spending and finance-plan slices,
|
||||
but no allocation, carry or unpaid expectations. Before the rollover start,
|
||||
the month view reads the existing charge calculation directly because the fold
|
||||
has deliberately produced no envelope figures yet.
|
||||
|
||||
A recurring cost adds a SECOND balance beside it, on its own carry:
|
||||
|
||||
```
|
||||
@@ -157,6 +163,11 @@ still works.
|
||||
|
||||
## Money
|
||||
|
||||
The default currency table seeds new datasets only. JPY, AUD, CAD and MXN join
|
||||
DKK, USD, EUR and GBP; existing currency tables are not rewritten. The build
|
||||
derives its FX targets from the seed table and carries complete committed
|
||||
fallback rates. These rates never replace one an owner has cleared.
|
||||
|
||||
A float, deliberately, with two rules that make it safe:
|
||||
|
||||
1. **Round at every boundary** via `roundMoney` — after any division, after any
|
||||
@@ -187,6 +198,11 @@ which also says what re-adding a per-currency override would take.
|
||||
|
||||
## Splits and finance plans compose
|
||||
|
||||
Purchase entry defaults to creating a new recurring cost when the owner chooses
|
||||
to make a purchase recur. Selecting an existing bill is enabled explicitly in
|
||||
Settings by the optional `allowExistingRecurringCost` boolean (absent is false),
|
||||
validated on JSON import. Existing purchase associations remain editable.
|
||||
|
||||
A purchase can be split across posts *and* spread over months at once. The trick
|
||||
is in `charges.ts`: first compute how the **whole** purchase divides across posts
|
||||
(`splitPartsOfTotal`), then distribute each month's slice proportionally *to
|
||||
@@ -211,6 +227,16 @@ explicit zero rule, including a zero monthly override, does have an overspend
|
||||
status when its balance is negative. The views use the presence of a rule rather
|
||||
than infer it from the numeric allocation, since both cases fold to zero.
|
||||
|
||||
## Display preferences and build identity
|
||||
|
||||
Monthly chart visibility and Expected's expanded state persist per browser,
|
||||
alongside the theme preference. They never enter an exported dataset or change
|
||||
the month calculations. Storage errors leave the display controls usable.
|
||||
|
||||
Colophon receives a source ref and UTC build date separately from the build.
|
||||
The date describes the artifact, not the browser's current day. A source archive
|
||||
has no Git ref but still receives a unique service-worker cache version.
|
||||
|
||||
## Things the app deliberately does NOT validate
|
||||
|
||||
Adding a "missing" guard here is a regression, not a fix:
|
||||
|
||||
+6
-2
@@ -71,6 +71,9 @@ Confirmed and in place:
|
||||
- A purchase may have no post. It remains visible in its month and counts in
|
||||
Spent, while leaving envelope balances unchanged.
|
||||
- Per-month income, entered directly, varying month to month.
|
||||
- The month header shows actual available: that month's entered income minus
|
||||
actual spending, including unassigned purchases and refunds. Monthly breakdown
|
||||
can be hidden and Expected collapsed; these choices persist in this browser.
|
||||
- Multi-currency (DKK/USD/EUR) against a user-maintained exchange-rate table,
|
||||
with an optional on-demand fetch that fails soft and is never required.
|
||||
- JSON export/import and ODS (spreadsheet) export, both generated in the
|
||||
@@ -92,8 +95,9 @@ Confirmed and in place:
|
||||
whichever charge was most recent — a phone plan that recharges early when
|
||||
its data runs out is the case this exists for. Each due date on the
|
||||
calendar model is a projected occurrence the owner confirms into a real
|
||||
purchase; a recurring cost can also be tagged onto a purchase, or created
|
||||
from one, while it is being entered, rather than only from Settings. The
|
||||
purchase; a recurring cost can be created from a purchase while it is being
|
||||
entered. Choosing an existing bill in Add purchase is enabled explicitly in
|
||||
Settings; existing purchase associations remain editable. The
|
||||
month view shows what is still expected alongside a second, projected
|
||||
balance per post, so a known bill is visible before it is paid without
|
||||
touching the reconciled figure a bank statement is checked against.
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ task moved to `AGENTS.md`.
|
||||
|
||||
**Current state:** the anchored model is merged with budget folders and
|
||||
placement. The month pie showcase, browser-stored session backups, and
|
||||
unassigned recurring costs are implemented. 881 tests pass across 64 files;
|
||||
unassigned recurring costs are implemented. 911 tests pass across 66 files;
|
||||
`bunx tsc --noEmit` is clean, and `SKIP_FX_FETCH=1 bun run build` succeeds
|
||||
using the committed seed rates.
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
# Month and purchase refinements
|
||||
|
||||
The owner requested this round on 2026-10-03, with the work queued in TODO
|
||||
before implementation. The follow-up added a visible Total-field reset bug.
|
||||
|
||||
## Actual available
|
||||
|
||||
The month header shows entered income minus actual spending beside Spent.
|
||||
This includes unassigned purchases, refunds, foreign-currency charges and the
|
||||
selected month's finance-plan slices. Unpaid recurring occurrences do not
|
||||
reduce it. Allocation and envelope carry do not enter this calculation.
|
||||
Negative values remain visible, and arithmetic and display use dataset digits.
|
||||
|
||||
Purchases before the envelope fold's start still count in Spent and Actual
|
||||
available. The month view uses the existing charge calculation for those
|
||||
months; the rollover fold continues to begin at its configured start.
|
||||
|
||||
## Display choices
|
||||
|
||||
Monthly breakdown has a Show chart checkbox. Its header and checkbox remain
|
||||
visible when the chart and detail list are hidden. Expected has an Expand /
|
||||
Collapse button and shows counts for expected, coming-up and overdue entries
|
||||
when closed. Closing it preserves any typed confirmation amount.
|
||||
|
||||
Both choices default to visible/expanded and persist across months and reloads
|
||||
in browser localStorage. This follows the theme decision: display preferences
|
||||
belong to this browser, not to the exported budget. Blocked localStorage must
|
||||
not stop the app or the controls from working. Neither choice changes money,
|
||||
occurrences or confirmation behavior.
|
||||
|
||||
## Purchase entry
|
||||
|
||||
New purchases begin with Recurring cost set to None. Their recurring option
|
||||
creates a new bill from this purchase, rather than selecting an existing bill.
|
||||
Creating a bill and its first charge remains one atomic mutation. Calendar
|
||||
slots and last-charge date requirements retain their existing rules.
|
||||
|
||||
Settings → Recurring costs includes a checkbox to allow selecting existing
|
||||
bills in Add purchase. The optional dataset boolean is
|
||||
`settings.allowExistingRecurringCost`; absent means false, so existing budgets
|
||||
adopt the new default without a migration. JSON import accepts only booleans
|
||||
when this field is present. The choice travels with a JSON export.
|
||||
|
||||
Editing a linked purchase continues to show its saved association even with
|
||||
the setting off, and choosing None can unlink it. Ordinary purchases do not
|
||||
automatically become recurring bills. This resolves the default-selection
|
||||
ambiguity in favor of an explicit recurring action.
|
||||
|
||||
The Total field has its own text state to preserve partly typed money inputs.
|
||||
Resetting the dialog now resets that text alongside the numeric draft: new
|
||||
purchases start blank and existing purchases start with their saved amount.
|
||||
The expanded new-bill form exceeded a phone viewport in a browser check. The
|
||||
purchase dialog now caps its height to the viewport and scrolls its contents,
|
||||
keeping both the first fields and Save reachable.
|
||||
|
||||
## Default currencies
|
||||
|
||||
New datasets include Japanese yen (JPY, ¥), Australian dollars (AUD, AU$),
|
||||
Canadian dollars (CAD, CA$) and Mexican pesos (MXN, MX$). Existing tables are
|
||||
not migrated: the request specified defaults, and editing an established
|
||||
table is the owner's action. Every currency still uses dataset-wide precision.
|
||||
|
||||
The build's FX targets derive from the seed table. All four have committed
|
||||
fallback rates as well as live build-time fetching. The committed rates were
|
||||
obtained from Frankfurter's DKK-base quote dated 2026-10-02 and inverted to
|
||||
DKK per unit, preserving six decimal places. Like the previous constants,
|
||||
these rows are marked manual and seed only new datasets.
|
||||
|
||||
Source: https://api.frankfurter.dev/v1/2026-10-02?base=DKK&symbols=JPY,AUD,CAD,MXN
|
||||
|
||||
The amount parser strips longer symbols before the plain dollar sign, so
|
||||
AU$, CA$ and MX$ work without leaving stray letters in the amount. An explicit
|
||||
currency code still disambiguates a symbol collision.
|
||||
|
||||
## Build stamp
|
||||
|
||||
Colophon shows the source ref and a separate build-time UTC date. An exact
|
||||
commit keeps its source link; an uncommitted checkout retains the dirty suffix.
|
||||
Source archives say source archive rather than inventing a Git ref, while
|
||||
the service worker still receives a unique cache version. Development says
|
||||
development and no build date. Browser accessors tolerate an absent process
|
||||
global, following the existing Bun environment-inlining rule.
|
||||
|
||||
## Verification
|
||||
|
||||
Regression coverage exercises the actual money calculation, purchase save /
|
||||
reopen, new recurring creation, enabled existing-bill selection, linked editing,
|
||||
display persistence, collapse/expand preserving a typed amount, Settings write
|
||||
errors, JSON preference validation, symbol parsing and new seed conversion.
|
||||
Chrome checks verify the month controls, saving and reopening a purchase,
|
||||
persisting the Settings choice to IndexedDB, creating a new bill and its first
|
||||
charge together, keeping mobile Save reachable, and production build metadata.
|
||||
Desktop and mobile captures cover light and dark themes. Screenshots establish
|
||||
layout and console behavior; visual feel still requires the owner.
|
||||
The complete suite passes 911 tests across 66 files, TypeScript is clean, and
|
||||
production builds succeed at both root and subpath base URLs with seed rates.
|
||||
Reference in new issue
Block a user