feat: Typst-typeset statements in US accounting format
The printpdf statements padded columns with spaces in a proportional font (amounts didn't line up), had no company name, printed negatives as "-207.68", and put everything on one page (a long statement was silently cut off past ~70 lines). Now: - PDFs are typeset by Typst from one template, ledger-import/templates/statement.typ. The laid-out statement goes in as JSON data (sys.inputs), so account names are never parsed as markup. Fonts are bundled (typst-assets: Libertinus Serif), so output doesn't depend on the machine and is byte-for-byte deterministic. Statements paginate, with the company/title repeated on later pages and "Page n of m" plus "Prepared from journal through seq N" in the footer. - US accounting presentation, decided once in ledger-import::report and applied by both PDF and XLSX: tabular figures; "$" on each section's first amount and on every subtotal and total, pinned to the column's left edge; negatives in parentheses with the closing paren hanging (its width reserved on every row, so decimal points align); zero as a dash; a single rule above sums (none over an empty section) and a double rule under statement totals; amounts never bold, because bold figures are wider and would leave the decimal column. XLSX uses Excel's own Accounting formats for the same result. - Cash flow: "Net change in cash" is a subtotal; only "Cash at end of period" takes the double rule. - Core fix: cash_flow walked the chart in HashMap order, so the order of working-capital and financing lines could change between runs. Lines now follow account code. Test: five owners, twenty runs, always 3000/3001/3003/3005/3009; fails without the sort. New dependencies (ledger-import): typst, typst-layout, typst-pdf, typst-assets (fonts), typst-as-lib (World/font plumbing), serde_json (the template's data). Removed: printpdf. Release ledger-mcp grows by the bundled fonts. Tests: exact template data for a hand-built P&L (like a golden); same statement renders to identical bytes; 200 rows of Typst markup characters render literally and span several pages; currency-sign and rule placement; amount splitting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MtYEaR6M6Ybdhm6X98BWNu
This commit is contained in:
1 parent
b5af21bb0e
commit
03212265ce
10 files changed
+2856
-545
No files matched your search
@@ -221,3 +221,47 @@ fn a_closed_period_still_reports_its_earnings() {
|
||||
assert_eq!(cf.cash_from_financing, dec(10_000));
|
||||
assert_eq!(cf.net_change_in_cash, dec(11_700));
|
||||
}
|
||||
|
||||
/// Cash flow lines come out in account-code order, every time. (The chart
|
||||
/// iterates in hash order; the statement must not.)
|
||||
#[test]
|
||||
fn cash_flow_lines_are_in_code_order() {
|
||||
for _ in 0..20 {
|
||||
let mut l = Ledger::new(Currency::USD);
|
||||
let cash = l
|
||||
.add_account("1000", "Cash", AccountType::Asset, None, false)
|
||||
.unwrap();
|
||||
let mut owners = Vec::new();
|
||||
for code in ["3005", "3001", "3009", "3000", "3003"] {
|
||||
owners.push(
|
||||
l.add_account(code, code, AccountType::Equity, None, false)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
for o in &owners {
|
||||
post(&mut l, d(3, 1), "Contribution", cash, *o, 100);
|
||||
}
|
||||
let cf = cash_flow(
|
||||
&l,
|
||||
&CashFlowParams {
|
||||
start: d(1, 1),
|
||||
end: d(12, 31),
|
||||
cash_accounts: vec![cash],
|
||||
financing_accounts: vec![],
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
// Labels read "Owner contributions — 3000 3000" (code, then name).
|
||||
let codes: Vec<&str> = cf
|
||||
.financing
|
||||
.iter()
|
||||
.map(|f| f.label.rsplit(' ').next().unwrap())
|
||||
.collect();
|
||||
assert_eq!(
|
||||
codes,
|
||||
["3000", "3001", "3003", "3005", "3009"],
|
||||
"{:?}",
|
||||
cf.financing
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user