feat: support unassigned purchases and allocation status
Allow month-only purchases to start finance plans. Keep unbudgeted posts distinct from explicit zero rules in overspend reporting.
This commit is contained in:
1 parent
c1446db39f
commit
b0e8145d2e
31 files changed
+469
-80
No files matched your search
@@ -55,6 +55,33 @@ const data: Dataset = {
|
||||
};
|
||||
|
||||
describe("monthView", () => {
|
||||
test("counts a loose purchase as spent without charging a post", () => {
|
||||
const loose: Dataset = { ...data, purchases: [{ ...spend("loose", "food", 75, "2026-01"), splits: [] }] };
|
||||
const view = monthView(loose, "2026-01");
|
||||
expect(view.totalCharges).toBe(75);
|
||||
expect(view.rows.every((row) => row.figures.charges === 0)).toBe(true);
|
||||
expect(view.rows[0]!.figures.remaining).toBe(2000);
|
||||
});
|
||||
|
||||
test("counts financed loose slices in their months and converts currency", () => {
|
||||
const loose: Dataset = {
|
||||
...data,
|
||||
fxRates: [{ currency: "EUR", baseUnitsPerOne: 7.5, updatedAt: "2026-01-01", source: "manual" }],
|
||||
purchases: [{
|
||||
...spend("loose", "food", 10, "2026-01"),
|
||||
total: { amount: 10, currency: "EUR" },
|
||||
splits: [],
|
||||
schedule: { slices: [
|
||||
{ month: "2026-01", amount: { amount: 4, currency: "EUR" } },
|
||||
{ month: "2026-02", amount: { amount: 6, currency: "EUR" } },
|
||||
] },
|
||||
}],
|
||||
};
|
||||
expect(monthView(loose, "2026-01").totalCharges).toBe(30);
|
||||
expect(monthView(loose, "2026-02").totalCharges).toBe(45);
|
||||
expect(yearView(loose, 2026).totalCharges).toBe(75);
|
||||
expect(summaryView(loose, "2026-01", "2026-02").looseCharges).toBe(75);
|
||||
});
|
||||
test("reports income, allocations, and the unallocated remainder", () => {
|
||||
const view = monthView(data, "2026-01");
|
||||
expect(view.income).toBe(10000);
|
||||
@@ -104,6 +131,35 @@ describe("monthView", () => {
|
||||
expect(view.overspentCount).toBe(0);
|
||||
});
|
||||
|
||||
test("no allocation rule stays distinct from an explicit zero allocation", () => {
|
||||
const unbudgeted = { ...post("none", 0, 0), rules: [] };
|
||||
const zero = post("zero", 1, 0);
|
||||
const caseData: Dataset = {
|
||||
...data,
|
||||
posts: [unbudgeted, zero],
|
||||
purchases: [spend("a", "none", 25, "2026-01"), spend("b", "zero", 25, "2026-01")],
|
||||
};
|
||||
const view = monthView(caseData, "2026-01");
|
||||
expect(view.rows.map((row) => row.hasAllocationRule)).toEqual([false, true]);
|
||||
expect(view.rows.map((row) => row.figures.remaining)).toEqual([-25, -25]);
|
||||
expect(view.overspentCount).toBe(1);
|
||||
expect(yearView(caseData, 2026).rows.map((row) => row.hasAllocationRuleByMonth[0])).toEqual([false, true]);
|
||||
});
|
||||
|
||||
test("a monthly override counts as an allocation rule even before any standing rule", () => {
|
||||
const caseData: Dataset = {
|
||||
...data,
|
||||
posts: [{ ...post("none", 0, 0), rules: [] }],
|
||||
months: [{ id: "2026-01", income: { amount: 10000, currency: "DKK" }, ruleOverrides: {
|
||||
none: { kind: "fixed", amount: { amount: 0, currency: "DKK" } },
|
||||
} }],
|
||||
purchases: [spend("a", "none", 25, "2026-01")],
|
||||
};
|
||||
const view = monthView(caseData, "2026-01");
|
||||
expect(view.rows[0]!.hasAllocationRule).toBe(true);
|
||||
expect(view.overspentCount).toBe(1);
|
||||
});
|
||||
|
||||
test("archived posts appear only when they have activity", () => {
|
||||
const withArchived: Dataset = {
|
||||
...data,
|
||||
|
||||
Reference in new issue
Block a user