Main moved on by 27 commits since this branch forked, including three schema steps of its own (budget folders, placement, nested bills). Resolutions worth knowing: - The anchored-model migration becomes 10 -> 11; main's 7 -> 10 run first. - Main's nextUnconfirmedOccurrenceOnOrAfter walked the calendar grid for every cost, which would have shown the slot-model "next due" for an anchored bill in the budget tree. Both walks are now generators and the lookup dispatches on anchoring, like occurrencesOf. - A folder's "Bills due" summed only occurrences, and an anchored bill's charges are never occurrences, so a charged phone plan read as 0 due. budgetTree now adds an anchored bill's charges in the month. - Main's RecurringSection (with RecurringSplitEditor) supersedes this branch's split editor; the anchoring rules are ported onto it: start-date validation via startDateNeedsDay, month precision hidden for lastCharge, the anchoring control locked once a cost has charges. - PurchaseDialog keeps main's placement control. The "Recurring cost" select replaces main's "Unlink from bill" checkbox, since "None" does the same. A bill created from the dialog nests under its post like one from Settings. - Dropped main's "bounded projection ignores invalid confirmations" test: it exercised slot confirmations on a lastCharge cost, which no longer exist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
58 lines
1.8 KiB
TypeScript
58 lines
1.8 KiB
TypeScript
import { DEFAULT_DIGITS, SEED_CURRENCIES } from "./types.ts";
|
|
import type { CurrencyDef, Dataset, FxRate, MonthId, Post } from "./types.ts";
|
|
|
|
export const SCHEMA_VERSION = 11;
|
|
|
|
const SEED_POST_NAMES = ["Video Games", "Food", "Events and Social"] as const;
|
|
|
|
export function newId(): string {
|
|
return crypto.randomUUID();
|
|
}
|
|
|
|
/**
|
|
* The first-run dataset. Takes the start month AND the seed exchange rates as
|
|
* arguments because the domain layer must read neither the ambient clock nor
|
|
* the ambient environment. The store passes `BAKED_FX_RATES`; callers that
|
|
* want a bare dataset pass nothing.
|
|
*/
|
|
export function createSeedDataset(
|
|
startMonth: MonthId,
|
|
fxRates: readonly FxRate[] = [],
|
|
currencies: readonly CurrencyDef[] = SEED_CURRENCIES,
|
|
): Dataset {
|
|
const posts: Post[] = SEED_POST_NAMES.map((name, order) => ({
|
|
id: newId(),
|
|
name,
|
|
order,
|
|
folderId: null,
|
|
archived: false,
|
|
currency: "DKK",
|
|
// A new user has genuinely not budgeted anything yet. This is a different
|
|
// state from a rule of zero, and the app distinguishes them.
|
|
rules: [],
|
|
}));
|
|
|
|
return {
|
|
settings: {
|
|
baseCurrency: "DKK",
|
|
foldStartMonth: startMonth,
|
|
schemaVersion: SCHEMA_VERSION,
|
|
digits: DEFAULT_DIGITS,
|
|
},
|
|
// Copied, so a caller passing shared constants cannot have them edited
|
|
// out from under it the first time the user changes a rate or a currency.
|
|
currencies: currencies.map((currency) => ({ ...currency })),
|
|
fxRates: fxRates.map((rate) => ({ ...rate })),
|
|
folders: [],
|
|
posts,
|
|
months: [
|
|
{ id: startMonth, income: { amount: 0, currency: "DKK" }, ruleOverrides: {} },
|
|
],
|
|
purchases: [],
|
|
// A new dataset seeds no recurring costs. Unlike currencies and FX rates,
|
|
// there is no sensible default bill — the app cannot guess what the owner
|
|
// pays.
|
|
recurring: [],
|
|
};
|
|
}
|