a6566e801f975e73aa5766656c0ec99f7610260c
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dbdc8e671f
|
feat: entity-aware owner equity — chart templates, draws, distributions, dividends
Every business has owners, and how owners put money in and take it out
depends on what the business is. wanderbooks now knows the four common
US shapes:
- books.chart_template {entity_type, owners}: a starter chart for a sole
proprietorship (owner's capital and draws), partnership or
multi-member LLC (capital and distributions per partner, guaranteed
payments), S corporation (stock, APIC, shareholder distributions,
officer compensation, retained earnings) or C corporation (dividends,
income tax expense), plus common income and expense accounts with
tax categories, and the wanderbooks.toml settings that go with it
(tax form, members with equal-share placeholders that sum to exactly
100, close target). A proposal for the agent to review with the human.
- Closing: CloseTo is now RetainedEarnings { sweep }, Account { into,
sweep } (a sole proprietor's capital) or Members([MemberClose { capital,
share, distributions }]). Sweeps zero owner-pay equity accounts into
their target at close: draws into owner's capital, dividends or
distributions into retained earnings, each partner's distributions
into their own capital. Validation (equity, distinct, shares sum to 1)
runs in the pre-close preview, before anything is journaled. The
journal records the target; journals with no target or the old
"retained_earnings" string still replay.
- Members' equity: a partner's distributions account is part of their
capital; its activity reports as distributions.
- Config: members' distribution_account; [books] close_into and
close_sweep for single-owner businesses (rejected alongside members).
The cash flow names members on their distribution accounts too.
Tests: a sole proprietor (net 2,000 and 500 of draws into capital: 6,500,
the cash); a corporation (dividends 300 into retained earnings: 600); a
partnership with distribution accounts (Bob 100 + 25 - 20 = 105, swept at
close, February begins there); bad sweep accounts; templates round-trip
through the chart parser, unique codes, equity per entity, every tax
category valid for the entity's return; stdio: template -> settings ->
import -> postings -> close with the exact closing lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtYEaR6M6Ybdhm6X98BWNu
|
||
|
|
8164abb2b8
|
fix: period close sweeps only through the period end; partnerships close into members' capital
The close zeroed every revenue and expense account's all-time movement, so closing a period after later entries were posted swept those too: close January in March and February's and March's income landed in January's closing entry. The preview had the same bug and never showed where the net went. Now: - close_period_to(id, to) zeroes balances as of the period end; entries dated later stay open for their own period. Periods must close in order (LedgerError::CloseOutOfOrder): a later close has already swept the earlier period. - CloseTo::Members: for a partnership (multi-member LLC) the net goes to each member's capital account by share, to the cent, the last member absorbing rounding, which is what K-1 capital accounts track. CloseTo::RetainedEarnings (3999) stays the default; close_period is shorthand for it. books.close_period picks Members when wanderbooks.toml lists members. - The journal records the close target; replay re-derives the closing entry and now compares every line with the journal, not just the id, so a change to closing math can never silently rewrite history. - The tool's preview is the real close run on a clone of the ledger (Ledger now derives Clone), so preview and post can't disagree. - Members' equity: closing entries into a capital account are income, not contributions. Balance sheet for a partnership: capital accounts (with allocated earnings), then only unallocated earnings — "Undistributed earnings, prior years" if any, and this year's unclosed net income (loss). periods::allocate is shared by the close and the statement. Tests: January closed after February is posted sweeps exactly Dr 4000 2,500 / Cr 6100 800 / Cr 3999 1,700 (mutation-checked: the old code gives 3,500/120/800/2,580); out-of-order close refused; a partnership close splits 666.67 into 333.34/333.33 with no 3999, and the members' equity statement reads correctly before and after; store replay of a members close (reopen, rebuild, verify); partnership and corporate balance sheet layouts; stdio close preview equals the posted lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MtYEaR6M6Ybdhm6X98BWNu |
||
|
|
02b3b9c053
|
feat: general ledger by account, trial balance as of, members' equity
Three reports a CPA expects that the engine could not produce: - general_ledger(start, end): the canonical general ledger, organized by account in code order. Each account has its beginning balance, every posting in the period (date, entry, description, debit, credit, running balance on the normal side) and its ending balance, which ties to the trial balance. Each posting names its split: the one account on the opposite side of its entry, or "-Split-" when that side has several, so a compound entry (one debit, several credits) reads correctly from every account it touches. The entry-ordered listing the exports called a "general ledger" is really the general journal. - trial_balance_as_of(as_of): a trial balance is always as of a date. - members_equity(start, end, members): the statement of changes in members' equity for a multi-member LLC taxed as a partnership. Each member's capital roll-forward: beginning (capital plus their share of undistributed earnings), contributions, allocated net income (loss), distributions, ending. Allocation is by ownership share to the cent, the last member absorbing rounding, so totals are exact and the ending total equals balance-sheet equity; any equity change the roll-forward can't explain shows as "other" instead of disappearing. Shares must be positive and sum to exactly 1; capital accounts must be distinct equity accounts (LedgerError::InvalidMembers). - CashFlowLine.account: the account a line is about, so renderers can name members instead of account codes. Tests (every expectation computed by hand): trial balance as of Jan 31; February general ledger with beginning balances, running balances, a card that nets to zero, an inactive account still listed, and every ending balance tied to the trial balance; a compound entry's splits from all three sides; a two-member partnership where 666.67 and 100.01 split unevenly (333.34/333.33, 50.01/50.00), with and without closing January, tying to the balance sheet each month; bad member configuration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MtYEaR6M6Ybdhm6X98BWNu |
||
|
|
03212265ce |
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 |
||
|
|
c6faeeb827 |
feat: period-scoped P&L and as-of balance sheet; closes don't zero periods
The engine only had life-of-ledger P&L and balance sheet totals, so every export labeled with a date range or as-of date (PDF, XLSX, CPA package) printed all-time numbers under that label. Core now has: - profit_and_loss_for_period(start, end, class?) and balance_sheet_as_of(as_of), each with a line per account (StatementLine: code, name, normal-side amount) and exact totals. - Ledger::closing_entry_ids(). Period statements exclude closing entries. A close (Dr revenue / Cr expense / net to Retained Earnings, dated at period end) moves earnings; it doesn't earn or spend. Counting it made a closed period's P&L read as break-even, and made the cash flow show zero net income plus a fake "owner contribution" to Retained Earnings. The as-of balance sheet keeps closing entries: after a close the earnings belong in Retained Earnings, not current earnings. Tests: tests/statements.rs, a two-month fixture with every expected number computed by hand (Jan NI 1,700; Feb 880; balance sheets on four dates; a closed January still reporting 1,700 and a cash flow with one financing line). Mutation-checked: dropping the closing exclusion fails it (0 vs 1,700). Properties: period P&Ls partition a year at any split, and a range's net income equals the change in current earnings across it, with A = L + E on both dates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MtYEaR6M6Ybdhm6X98BWNu |