feat(accounts): the account page as a statement, all accounts instead of the audit report

Asset and liability pages open with opening plus income and money moved
in, minus spending and money moved out, equals closing, then thirteen
months of in and out next to the daily balance, and the statement with
its balance column, day subtotals on ; and the last reconciled date.
The audit report becomes one table of all accounts for the period.
This commit is contained in:
hh committed 2026-10-16 15:36:41 +00:00
1 parent b10a6bd21a
commit f3a597d39c
11 files changed
+1118 -548

No files matched your search

-96
View File
@@ -1,96 +0,0 @@
import type { Item, Split, TransactionGroup } from "#lib/api/types.js";
export interface LedgerRow {
after: number;
amount: number;
before: number;
created: string | null;
group: string;
split: Split;
title: string | null;
updated: string | null;
}
export const AUDIT_COLUMNS = [
{ key: "icon", label: "Icon" },
{ key: "description", label: "Description" },
{ key: "balance_before", label: "Balance before" },
{ key: "amount", label: "Amount" },
{ key: "balance_after", label: "Balance after" },
{ key: "date", label: "Date" },
{ key: "from", label: "From" },
{ key: "to", label: "To" },
{ key: "budget", label: "Budget" },
{ key: "category", label: "Category" },
{ key: "bill", label: "Subscription" },
{ key: "created_at", label: "Created at" },
{ key: "updated_at", label: "Updated at" },
{ key: "notes", label: "Notes" },
{ key: "interest_date", label: "Interest date" },
{ key: "book_date", label: "Book date" },
{ key: "process_date", label: "Processing date" },
{ key: "due_date", label: "Due date" },
{ key: "payment_date", label: "Payment date" },
{ key: "invoice_date", label: "Invoice date" },
] as const;
export type AuditColumn = (typeof AUDIT_COLUMNS)[number]["key"];
export const DEFAULT_AUDIT: AuditColumn[] = [
"icon",
"description",
"balance_before",
"amount",
"balance_after",
"date",
"to",
];
function signed(split: Split, account: string, code: string | undefined) {
const foreign =
split.foreign_amount !== null &&
split.foreign_amount !== undefined &&
split.foreign_currency_code === code &&
split.currency_code !== code;
const value = Number(foreign ? split.foreign_amount : split.amount);
return String(split.source_id) === account ? -value : value;
}
export function ledger(
groups: Item<TransactionGroup>[],
account: string,
code: string | undefined,
start: number
): LedgerRow[] {
const rows = groups.flatMap((group) =>
group.attributes.transactions
.filter(
(split) =>
String(split.source_id) === account ||
String(split.destination_id) === account
)
.map((split) => ({ group, split }))
);
rows.sort(
(a, b) =>
a.split.date.localeCompare(b.split.date) ||
Number(a.split.transaction_journal_id) -
Number(b.split.transaction_journal_id)
);
let balance = start;
return rows.map(({ group, split }) => {
const amount = signed(split, account, code);
const before = balance;
balance += amount;
return {
after: balance,
amount,
before,
created: group.attributes.created_at ?? null,
group: group.id,
split,
title: group.attributes.group_title ?? null,
updated: group.attributes.updated_at ?? null,
};
});
}
-14
View File
@@ -1,7 +1,6 @@
/// <reference types="bun" />
import { expect, test } from "bun:test";
import type { Item, Split, TransactionGroup } from "#lib/api/types.js";
import { ledger } from "./audit.js";
import {
amountOf,
bucketRange,
@@ -171,16 +170,3 @@ test("currency exchanges between selected accounts are kept apart", () => {
},
]);
});
test("audit ledger runs the balance forward in the account currency", () => {
const rows = ledger(groups, "1", "UAH", 1000);
expect(rows.map((row) => [row.amount, row.after])).toEqual([
[-100, 900],
[-50, 850],
[-4000, -3150],
[300, -2850],
[-20, -2870],
]);
const usd = ledger(groups, "2", "USD", 0);
expect(usd.map((row) => row.amount)).toEqual([100]);
});