fix(cash flow): a payment to a government is other operating, by its payee

The P3 cutover review's open point 2: a state's LLC filing fee, booked
to an organization-costs account (tax category organization_startup),
read as "Cash paid to suppliers and employees". B-14 rule 5 sent a
payment to class (g) only when every operating line's account was
taxes_licenses, so the account, not the payee, decided.

ASC 230-10-45-17, read in EY's guide that reproduces it (Statement of
cash flows, July 2026, p. 43; the PDF re-fetched and its SHA-256
recorded in docs/spec/sources/fasb-asc.md), lists apart "(b) Cash
payments to other suppliers and employees for other goods or services"
and "(c) Cash payments to governments for taxes, duties, fines, and
other fees or penalties". 45-25's classes name only income taxes (f)
among the latter, so the rest are (g), other operating cash payments.
A formation filing fee is rightly an organization cost (its account and
tax line), and still a payment to a government: the payee is what
45-17(c) is about.

So rule 5 now sends a payment's government part to (g): of an
operating payment the structure classes (d), what went to a party with
the government_agency or tax_agency role (the chain root's parties, so
a reversal keeps its original's class) is (g), on any account; and, as
before, a payment whose every operating line is on a taxes_licenses
account is (g). Every party a government: the whole payment. A
government and a supplier named together: the payment is split by their
resolved shares (B-35), the government's part (g), the rest (d), since
ASC 230-10-45-22 has "each separately identifiable source or each
separately identifiable use" classed "on the basis of the nature of the
underlying cash flows" (EY p. 44; quoted in fasb-asc.md; it names the
three activities, read for 45-25's classes too as a CONVENTION). The
government's part is the payment x its share / the whole, rounded half
to even to the base currency's places, the rest exact, so the line still
sums to the payment and a reversal mirrors it. Shares that don't
resolve leave the whole payment (d). One naming no party is decided by
its accounts. Receipts are untouched (a refund from a government stays
(c)). No total changes, only which operating row a payment is in.

Two limits, from the review of this change: a government selling goods
or services (postage) is 45-17(b)'s supplier "for other goods or
services", so the rule says, and the handbook tells the agent, to add
such a payee as a vendor, never with the government_agency role (the
books can't tell what a payment bought; the role says it). And payroll
keeps its structure (CONVENTION): an entry with a line on the salary
expense or a payroll payable control account, or on a pay tax category
(salaries_wages, officer_compensation, employee_benefits,
pension_profit_sharing), stays (d) whoever it names: withheld taxes
remitted are the employees' pay, and the payables mix them with the
employer's taxes, which a remittance can't tell apart. So a remittance
to the IRS is (d) as it was before this change; the employer's payroll
tax paid on its own expense account is a tax, (g).

The first company's books have this case: their state filing fee moves
from "Cash paid to suppliers and employees" to "Other operating cash
payments" at every date once its entry names the state's agency with the
government_agency role and its own entry is the one whose cash the
statement lists (paid from the bank, or on a card that is a lender's
credit, D-42, or by a member through the members' account while it is
a financing account, B-14 rule 11, the way that fee was paid);
charged to an operating-payable card, its cash leaves in the card
payment, whose payee is the bank, and it stays with suppliers.
STATUS says how to check both, and that the cutover goldens' cash-flow
snapshots will differ on the two class lines only (totals the same),
so the next cutover diff isn't read as a regression.

Docs: rules.md B-14 rule 5 (the rule, its source quoted, the date and
what it was before); fasb-asc.md (45-17(b) and (c) quoted exactly, with
the guide's SHA-256); the handbook's entries section (name the agency as
the party on a tax, fee, fine or penalty paid to a government; a shared
payment named with both amounts; a government selling services added as
a vendor; payroll remittances stay with pay; a fee on a card follows the
card payment); fasb-asc.md also quotes 45-22 and 45-22A; bookspec
example cash-flow.government-fees.toml (a filing fee and a lawyer on
one organization-costs account, a license on taxes and licenses with no
party, a penalty to a tax agency on the wrong account, paper from a
supplier, an annual report paid through a registered agent, 37.00 the
state's and 100.00 the agent's; every figure by hand) with a new `class.<a..g>`
key in the
cash_flow query (bookspec README; the runner checks every class the
statement has is given); testing.md; STATUS.

Tests: ledger-core tests/spec_cash_flow.rs
`a_payment_to_a_government_is_other_operating` (the filing fee and a
penalty to a tax agency (g); the same account paid to a lawyer, or with
no payee, (d); its reversal next month keeps (g); a refund (c)),
`a_government_payee_moves_a_fee_on_any_account` (a taxes_licenses
license with no party (g); a payment shared half and half between a
government and a printer split 50.00 (g) and 50.00 (d), the government
alone all (g)), `a_shared_government_payment_splits_to_the_cent` (a
third by percent of 100.01: 33.34 (g), 66.67 (d); amounts split as
given; the reversal mirrors to the cent),
`a_payroll_remittance_to_a_tax_agency_stays_pay` (withheld taxes
remitted from a payable holding its payroll role, and pay on a
salaries_wages account, stay (d) with the IRS named; the employer's tax
on its own expense account is (g)),
`a_government_selling_services_is_a_supplier` (postage to a vendor (d);
given government_agency, (g): the limit the handbook guards); src
cashflow.rs `tests` `a_government_part_rounds_half_to_even` and the
proptest `a_government_part_sums_and_mirrors`; the bookspec example;
from the P4 review, `a_reversal_takes_its_originals_payee` (a fee and
its reversal next month land in one class, the chain's payee deciding:
the books keep a chain's parties one, so reading the reversal's own
parties is an equivalent mutant) and
`a_shared_government_payment_a_member_paid_splits_both_ways` (a shared
payment a member paid: each part with its own on_behalf; fails with
the on-behalf part unsplit). docs/mcp-interface.md: books.cash_flow
and the Roles paragraph say a government or tax agency payee makes a
payment other operating, and a shared one two operating lines.
Broken to confirm: without the payee check the five spec tests and the
example fail; without the split for mixed payees the two shared-payment
tests and the example (class.d -549.50) fail; without the payroll
exclusion the payroll test fails; rounding toward zero fails both unit
tests; without the tax-category route
`operating_classes_follow_asc_230_10_45_25`, the second test and the
example fail. The viewer's cash flow page,
rendered on made-up books before and after: the fee moved from "Cash
paid to suppliers and employees" to "Other operating cash payments",
every total the same; and on made-up books with an annual report
shared between the state and an agent: each part on its own class
line, every total the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-22 19:31:18 -06:00
1 parent 6c587086a0
commit 535874e0f3
11 files changed
+1049 -63

No files matched your search

+1
View File
@@ -406,6 +406,7 @@ the 1st, among others).
| `<section>.<N>` | event N's entry's cash in that section (`financing.4`) |
| `<section>.<N>.on_behalf` | of it, what a member or a lender paid or received for the company, no cash account moving (constructive receipt and disbursement) |
| `<section>.count` | how many entries the section lists |
| `class.<a..g>` | an operating class's total, by ASC 230-10-45-25's letter: (a) from customers, (b) interest and dividends received, (c) other receipts, (d) paid to suppliers and employees, (e) interest paid, (f) income taxes paid, (g) other payments; given one, every class the statement has must be given |
| `noncash.<N>` | event N's entry's noncash investing and financing disclosure |
| `noncash.count` | how many entries are disclosed |
| `reconciliation.depreciation` | depreciation added back |
@@ -0,0 +1,184 @@
# A payment to a government is not a supplier's, whatever account it is
# booked to: the payee decides (and so does a taxes-and-licenses account).
#
# wanderbooks: the P3 cutover review's open point 2 (a state's LLC filing
# fee, booked to an organization-costs account, read as cash paid to
# suppliers); rules.md B-14 rule 5; regression tests
# `a_payment_to_a_government_is_other_operating`,
# `a_government_payee_moves_a_fee_on_any_account`,
# `a_shared_government_payment_splits_to_the_cent`,
# `operating_classes_follow_asc_230_10_45_25` (ledger-core
# tests/spec_cash_flow.rs).
id = "cash-flow.government-fees"
title = "Fees paid to a government are other operating payments, not cash paid to suppliers"
rule = "In a statement of cash flows by the direct method, an operating cash payment to a government for taxes, duties, fines or other fees or penalties MUST NOT be reported as cash paid to suppliers and employees: it MUST be reported among other operating cash payments (income taxes apart), whether the payee is a government or the account is one for taxes and licenses. A payment to a supplier on the same account MUST stay cash paid to suppliers, and a payment shared between a government and a supplier MUST be split by their shares."
status = "AUTHORITY"
sources = [
"ASC 230-10-45-17(b): \"Cash payments to other suppliers and employees for other goods or services\"; 45-17(c): \"Cash payments to governments for taxes, duties, fines, and other fees or penalties\" (EY Financial reporting developments, Statement of cash flows, July 2026, p. 43; docs/spec/sources/fasb-asc.md)",
"ASC 230-10-45-22: \"a reporting entity shall determine each separately identifiable source or each separately identifiable use within the cash receipts and cash payments on the basis of the nature of the underlying cash flows\" (EY p. 44)",
"ASC 230-10-45-25: the classes reported at a minimum, (d) cash paid to employees and other suppliers, (f) income taxes paid, (g) other operating cash payments (EY p. 8)",
]
tags = ["cash", "book", "us", "cash-flow", "operating-classes"]
[setup]
currency = "USD"
fiscal_year_start = "01-01"
basis = "cash"
rounding = "half-even"
cash_accounts = ["1000"]
[[setup.accounts]]
code = "1000"
name = "Checking"
type = "asset"
[[setup.accounts]]
code = "3000"
name = "Capital"
type = "equity"
# Formation costs: a state's filing fee and a lawyer's bill both land here.
[[setup.accounts]]
code = "6000"
name = "Organization costs"
type = "expense"
tax_category = "organization_startup"
[[setup.accounts]]
code = "6400"
name = "Office supplies"
type = "expense"
tax_category = "office_expense"
[[setup.accounts]]
code = "6700"
name = "Taxes and licenses"
type = "expense"
tax_category = "taxes_licenses"
[[setup.parties]]
name = "Department of Commerce, Corporations Division"
roles = ["government_agency"]
[[setup.parties]]
name = "Department of Revenue"
roles = ["tax_agency"]
[[setup.parties]]
name = "Ashgrove Legal LLP"
roles = ["vendor"]
[[setup.parties]]
name = "Paper Goods Co."
roles = ["vendor"]
[[setup.parties]]
name = "Northgate Registered Agents"
roles = ["vendor"]
# Event 1.
[[events]]
date = "2026-01-05"
description = "Opening contribution"
lines = [
{ account = "1000", side = "debit", amount = "2000.00" },
{ account = "3000", side = "credit", amount = "2000.00" },
]
# Event 2: the state's fee to file the company, on the formation account.
[[events]]
date = "2026-02-02"
description = "Certificate of organization filing fee"
lines = [
{ account = "6000", side = "debit", amount = "75.00" },
{ account = "1000", side = "credit", amount = "75.00" },
]
parties = [{ party = "Department of Commerce, Corporations Division" }]
# Event 3: the lawyer's bill for the operating agreement, the same account.
[[events]]
date = "2026-02-03"
description = "Operating agreement"
lines = [
{ account = "6000", side = "debit", amount = "350.00" },
{ account = "1000", side = "credit", amount = "350.00" },
]
parties = [{ party = "Ashgrove Legal LLP" }]
# Event 4: a business license, on the taxes-and-licenses account, no
# party named.
[[events]]
date = "2026-02-10"
description = "City business license"
lines = [
{ account = "6700", side = "debit", amount = "40.00" },
{ account = "1000", side = "credit", amount = "40.00" },
]
# Event 5: a late-filing penalty paid to the tax agency, on supplies by
# mistake: still the government's.
[[events]]
date = "2026-02-12"
description = "Late filing penalty"
lines = [
{ account = "6400", side = "debit", amount = "15.00" },
{ account = "1000", side = "credit", amount = "15.00" },
]
parties = [{ party = "Department of Revenue" }]
# Event 6: paper and toner.
[[events]]
date = "2026-02-20"
description = "Paper and toner"
lines = [
{ account = "6400", side = "debit", amount = "62.50" },
{ account = "1000", side = "credit", amount = "62.50" },
]
parties = [{ party = "Paper Goods Co." }]
# Event 7: an annual report filed through a registered agent, one
# payment: 37.00 of it the state's fee, 100.00 the agent's service.
[[events]]
date = "2026-02-25"
description = "Annual report through the registered agent"
lines = [
{ account = "6000", side = "debit", amount = "137.00" },
{ account = "1000", side = "credit", amount = "137.00" },
]
parties = [
{ party = "Department of Commerce, Corporations Division", amount = "37.00" },
{ party = "Northgate Registered Agents", amount = "100.00" },
]
# By hand: February's payments are 75.00 + 350.00 + 40.00 + 15.00 + 62.50
# + 137.00 = 679.50, all operating. To governments (45-17(c)), other
# operating cash payments (g): the filing fee (a government agency is its
# payee), the license (its account is taxes and licenses), the penalty (a
# tax agency is its payee) and the state's part of the annual report
# (separately identifiable by its share, 45-22) = 75.00 + 40.00 + 15.00 +
# 37.00 = 167.00. To suppliers (45-17(b)), (d): the lawyer, the paper and
# the agent's part = 350.00 + 62.50 + 100.00 = 512.50. Cash 2,000.00 to
# 1,320.50.
[[expect]]
query = "cash_flow"
from = "2026-02-01"
to = "2026-02-28"
[expect.values]
operating = "-679.50"
investing = "0.00"
financing = "0.00"
net_change = "-679.50"
cash_beginning = "2000.00"
cash_ending = "1320.50"
difference = "0.00"
"operating.2" = "-75.00"
"operating.3" = "-350.00"
"operating.4" = "-40.00"
"operating.5" = "-15.00"
"operating.6" = "-62.50"
"operating.7" = "-137.00"
"operating.count" = "6"
"class.d" = "-512.50"
"class.g" = "-167.00"
+221 -46
View File
@@ -759,6 +759,9 @@ struct Roles<'a> {
declared: BTreeSet<AccountId>,
/// The registered fixed assets' depreciation expense accounts (rule 8).
depreciation: BTreeSet<AccountId>,
/// What moves money with a party, for resolving an entry's party
/// shares to amounts (rule 5's payments to governments).
money: crate::parties::MoneyAccounts,
}
impl<'a> Roles<'a> {
@@ -830,6 +833,11 @@ impl<'a> Roles<'a> {
receivables: ledger.role_account(&crate::RECEIVABLES),
member_of,
members: params.members.iter().map(|m| m.name.clone()).collect(),
money: crate::parties::money_accounts(
ledger,
&params.cash_accounts,
&params.financing_accounts,
),
})
}
@@ -1546,7 +1554,9 @@ fn label(ledger: &Ledger, a: &Analysed<'_>) -> String {
/// Rule 5: the class of one kind of an entry's operating cash, from the
/// direction of its root's cash: interest received or paid by its own
/// lines (whatever else the entry settles); everything else by the
/// structure of the entry's other operating lines.
/// structure of the entry's other operating lines. A payment's part that
/// went to a government is then taken out by its payee
/// (`government_part`).
fn class_of(
roles: &Roles<'_>,
a: &Analysed<'_>,
@@ -1564,19 +1574,7 @@ fn class_of(
(Bucket::InterestPaid, true) => return Ok(OperatingClass::OtherReceipts),
_ => {}
}
let mut ops: Vec<&Account> = Vec::new();
for p in a.parts.iter().filter(|p| {
p.flow == Flow::Operating
&& matches!(
p.bucket,
Bucket::Operating
| Bucket::Receivables
| Bucket::Income
| Bucket::GuaranteedPayments
)
}) {
ops.push(roles.account(p.account)?);
}
let ops = operating_accounts(roles, a)?;
let receivable = |x: &Account| Some(x.id) == roles.receivables;
Ok(if receipt {
let customer = roles
@@ -1599,9 +1597,11 @@ fn class_of(
.iter()
.all(|x| x.tax_category.as_deref() == Some("taxes_licenses"))
{
// Taxes and fees paid to governments are not suppliers' (ASC
// 230-10-45-17(c) apart from 45-17(b)); 45-25 names only income
// taxes, (f), so the rest are (g).
// "Cash payments to governments for taxes, duties, fines, and
// other fees or penalties" (ASC 230-10-45-17(c)) are not "cash
// payments to other suppliers" (45-17(b)): the account (taxes and
// licenses) says so here, the payee in `government_part`; 45-25
// names only income taxes, (f), so the rest are (g).
OperatingClass::OtherPayments
} else if ops.iter().any(|x| match x.account_type {
AccountType::Expense | AccountType::Liability => true,
@@ -1614,6 +1614,112 @@ fn class_of(
})
}
/// Rule 5: the accounts of an entry's other operating lines (not
/// interest), whose structure classes its operating cash.
fn operating_accounts<'a>(
roles: &Roles<'a>,
a: &Analysed<'_>,
) -> Result<Vec<&'a Account>, LedgerError> {
let mut ops: Vec<&Account> = Vec::new();
for p in a.parts.iter().filter(|p| {
p.flow == Flow::Operating
&& matches!(
p.bucket,
Bucket::Operating
| Bucket::Receivables
| Bucket::Income
| Bucket::GuaranteedPayments
)
}) {
ops.push(roles.account(p.account)?);
}
Ok(ops)
}
/// Tax categories of pay: wages, officers' pay, benefits, pensions.
const PAY: [&str; 4] = [
"salaries_wages",
"officer_compensation",
"employee_benefits",
"pension_profit_sharing",
];
/// Rule 5: of an operating payment the structure put in another class,
/// what went to a government, as (the government's size, everyone
/// else's), from the entry's party shares (its chain root's: a reversal
/// takes its original's). A party is a government if it has the
/// `government_agency` or `tax_agency` role. None when nothing did: no
/// party named, none a government, or the shares don't resolve to
/// amounts (then the whole payment keeps its structure's class).
///
/// Payroll keeps its structure (CONVENTION): an entry with a line on a
/// payroll payable or wage account (their control roles) or on a pay tax
/// category is pay to employees, 45-17(b), whoever is named; withheld
/// taxes remitted are the employees' pay, and the payables mix them with
/// the employer's taxes.
fn government_part(
roles: &Roles<'_>,
a: &Analysed<'_>,
) -> Result<Option<(Decimal, Decimal)>, LedgerError> {
let shares = roles.ledger.entry_parties(a.basis.id());
let government = |party| {
roles.ledger.get_party(party).is_some_and(|p| {
let r = p.all_roles();
r.contains(&crate::Role::GovernmentAgency) || r.contains(&crate::Role::TaxAgency)
})
};
if !shares.iter().any(|s| government(s.party)) {
return Ok(None);
}
let payroll: Vec<AccountId> = std::iter::once(&crate::PAYROLL[0])
.chain(&crate::PAYROLL[2..])
.filter_map(|c| roles.ledger.role_account(c))
.collect();
if operating_accounts(roles, a)?.iter().any(|x| {
payroll.contains(&x.id) || x.tax_category.as_deref().is_some_and(|c| PAY.contains(&c))
}) {
return Ok(None);
}
if shares.iter().all(|s| government(s.party)) {
return Ok(Some((Decimal::ONE, Decimal::ZERO)));
}
let Ok(resolved) =
crate::shares::resolve(roles.ledger, a.basis.id(), shares, &roles.money, &[])
else {
return Ok(None);
};
let (mut ours, mut theirs) = (Decimal::ZERO, Decimal::ZERO);
for p in &resolved.parts {
let side = if government(p.party) {
&mut ours
} else {
&mut theirs
};
*side = add(*side, p.amount.abs(), "an entry's party shares")?;
}
Ok((!ours.is_zero()).then_some((ours, theirs)))
}
/// Rule 5: `amount` split as `ours` to `theirs`, the government's part
/// rounded half to even to the base currency's places, the rest the
/// other's: the two always sum to `amount`.
fn split_part(
amount: Decimal,
(ours, theirs): (Decimal, Decimal),
places: u32,
) -> Result<(Decimal, Decimal), LedgerError> {
let whole = add(ours, theirs, "an entry's party shares")?;
let part = amount
.checked_mul(ours)
.and_then(|x| x.checked_div(whole))
.ok_or_else(|| too_large("a payment's government part"))?
.round_dp_with_strategy(places, rust_decimal::RoundingStrategy::MidpointNearestEven);
let rest = amount
.checked_sub(part)
.ok_or_else(|| too_large("a payment's government part"))?;
Ok((part, rest))
}
/// The codes of the accounts in some parts, sorted, once each.
fn codes<'p>(
roles: &Roles<'_>,
@@ -1718,6 +1824,7 @@ pub fn cash_flow(
}
}
let paired = pairs(&kept);
let places = ledger.base_currency().allocation_places();
// Rule 5: the body, one line per entry, section and kind.
type Sorted = (KindOrder, NaiveDate, EntryId, CashFlowLine);
@@ -1741,45 +1848,70 @@ pub fn cash_flow(
} in &a.kinds[k]
{
let inflow = (*amount > Decimal::ZERO) != a.flip;
let (order, kind, class) = if *section == CashFlowSection::Operating {
// The kind's cash, by caption: one piece, or two when a
// payment went partly to a government (rule 5).
let mut pieces = Vec::new();
if *section == CashFlowSection::Operating {
let class = class_of(&roles, a, bucket, inflow)?;
(
(class.order(), 0, String::new()),
class.label().to_string(),
Some(class),
)
let interest =
matches!(bucket, Bucket::InterestReceived | Bucket::InterestPaid);
let split = if class == OperatingClass::ToSuppliersAndEmployees && !interest {
government_part(&roles, a)?
} else {
None
};
let mut push = |class: OperatingClass, cash, behalf| {
pieces.push((
(class.order(), 0, String::new()),
class.label().to_string(),
Some(class),
cash,
behalf,
));
};
match split {
Some(sizes) => {
let (cash, rest) = split_part(*amount, sizes, places)?;
let (behalf, behalf_rest) = split_part(*on_behalf, sizes, places)?;
push(OperatingClass::OtherPayments, cash, behalf);
push(class, rest, behalf_rest);
}
None => push(class, *amount, *on_behalf),
}
} else {
let (order, kind) = roles.kind(bucket, inflow);
(order, kind, None)
};
pieces.push((order, kind, None, *amount, *on_behalf));
}
let accounts = codes(
&roles,
a.parts
.iter()
.filter(|p| p.flow == Flow::SECTIONS[k] && p.bucket == *bucket),
)?;
if let Some((_, _, _, same)) =
mine.iter_mut().find(|m| m.0 == order && m.3.kind == kind)
{
same.amount = add(same.amount, *amount, "an entry's line")?;
same.on_behalf = add(same.on_behalf, *on_behalf, "an entry's line")?;
same.accounts.extend(accounts);
same.accounts.sort();
same.accounts.dedup();
continue;
for (order, kind, class, amount, on_behalf) in pieces {
if let Some((_, _, _, same)) =
mine.iter_mut().find(|m| m.0 == order && m.3.kind == kind)
{
same.amount = add(same.amount, amount, "an entry's line")?;
same.on_behalf = add(same.on_behalf, on_behalf, "an entry's line")?;
same.accounts.extend(accounts.iter().cloned());
same.accounts.sort();
same.accounts.dedup();
continue;
}
let line = CashFlowLine {
entry: a.entry.id(),
date: a.entry.date(),
label: label(ledger, a),
kind,
amount,
on_behalf,
accounts: accounts.clone(),
reverses: a.entry.reversal_of(),
class,
};
mine.push((order, a.entry.date(), a.entry.id(), line));
}
let line = CashFlowLine {
entry: a.entry.id(),
date: a.entry.date(),
label: label(ledger, a),
kind,
amount: *amount,
on_behalf: *on_behalf,
accounts,
reverses: a.entry.reversal_of(),
class,
};
mine.push((order, a.entry.date(), a.entry.id(), line));
}
body[k].extend(mine.into_iter().filter(|m| !m.3.amount.is_zero()));
}
@@ -2150,3 +2282,46 @@ fn warnings(
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn x(s: &str) -> Decimal {
s.parse().unwrap()
}
/// Rule 5's split of a payment between a government and the rest:
/// the government's part rounded half to even, the rest exact, a
/// whole government payment untouched.
#[test]
fn a_government_part_rounds_half_to_even() {
let split =
|a: &str, ours: &str, theirs: &str| split_part(x(a), (x(ours), x(theirs)), 2).unwrap();
assert_eq!(split("-10.00", "1", "2"), (x("-3.33"), x("-6.67")));
assert_eq!(split("-10.00", "2", "1"), (x("-6.67"), x("-3.33")));
// 0.025 and 0.035: to the even cent.
assert_eq!(split("-0.05", "1", "1"), (x("-0.02"), x("-0.03")));
assert_eq!(split("0.07", "1", "1"), (x("0.04"), x("0.03")));
assert_eq!(split("-37.00", "1", "0"), (x("-37.00"), x("0")));
}
proptest::proptest! {
/// Rule 5: the two parts always sum to the payment, the
/// government's within half a cent of its exact share, a payment
/// and its reversal split as mirrors.
#[test]
fn a_government_part_sums_and_mirrors(
cents in -10_000_000i64..10_000_000, ours in 1i64..1_000_000, theirs in 0i64..1_000_000,
) {
let amount = Decimal::new(cents, 2);
let sizes = (Decimal::new(ours, 2), Decimal::new(theirs, 2));
let (part, rest) = split_part(amount, sizes, 2).unwrap();
proptest::prop_assert_eq!(part + rest, amount);
let exact = amount * sizes.0 / (sizes.0 + sizes.1);
proptest::prop_assert!((part - exact).abs() <= Decimal::new(5, 3));
let (back, back_rest) = split_part(-amount, sizes, 2).unwrap();
proptest::prop_assert_eq!((back, back_rest), (-part, -rest));
}
}
}
+43 -2
View File
@@ -1722,6 +1722,18 @@ fn cash_flow_value(
.sum(),
);
}
if head == "class" {
// `class.<a..g>`: the operating class's total (ASC
// 230-10-45-25's letters).
let c = operating_class(tail)?;
return Some(
cf.operating_by_class()
.into_iter()
.filter(|(k, _)| *k == c)
.map(|(_, a)| a)
.sum(),
);
}
if head == "reconciliation" {
let (kind, account) = match tail.split_once('.') {
Some((k, a)) => (k, Some(a)),
@@ -1768,6 +1780,21 @@ fn cash_flow_value(
})
}
/// The operating class ASC 230-10-45-25's letter names.
fn operating_class(letter: &str) -> Option<ledger_core::OperatingClass> {
use ledger_core::OperatingClass::*;
Some(match letter {
"a" => FromCustomers,
"b" => InterestAndDividendsReceived,
"c" => OtherReceipts,
"d" => ToSuppliersAndEmployees,
"e" => InterestPaid,
"f" => IncomeTaxesPaid,
"g" => OtherPayments,
_ => return None,
})
}
/// The reconciliation key a line answers to.
fn reconciliation_key(l: &ledger_core::ReconcilingLine) -> String {
let kind = match l.kind {
@@ -1827,6 +1854,20 @@ fn check_cash_flow(b: &Books<'_>, x: &Expect) {
);
}
}
// Class keys given: every class the statement has is one.
if values.keys().any(|k| k.starts_with("class.")) {
for (c, amount) in cf.operating_by_class() {
let letter = ["a", "b", "c", "d", "e", "f", "g"]
.into_iter()
.find(|l| operating_class(l) == Some(c))
.unwrap();
assert!(
values.contains_key(&format!("class.{letter}")),
"{}: the statement has class.{letter} ({amount}), the example doesn't",
ex.id
);
}
}
// Reconciliation keys given: every line the statement has is one.
if values.keys().any(|k| k.starts_with("reconciliation.")) {
for line in &cf.operating_reconciliation.lines {
@@ -3205,13 +3246,13 @@ fn every_bookspec_example_holds() {
*checked.entry(q).or_insert(0) += 1;
}
}
// Each query the examples use ran: the cash flow's six examples at
// Each query the examples use ran: the cash flow's seven examples at
// least, the as-of and 1099 examples', the members closes', the
// formats appendix's, and P3's default period, rename, tax category,
// parties, 1099, depreciation, schedules, asset basis and K-1 tax-basis
// examples'.
for (q, at_least) in [
("cash_flow", 6),
("cash_flow", 7),
("trial_balance", 2),
("profit_and_loss", 2),
("vendor_1099_paid", 4),
+419
View File
@@ -3231,3 +3231,422 @@ fn a_disposals_depreciation_credit_back_is_depreciation() {
);
assert_eq!(cf.cash_from_investing, x("975.00"));
}
/// rules.md B-14 rule 5 (the P3 cutover review's open point 2): ASC
/// 230-10-45-17(c), "Cash payments to governments for taxes, duties,
/// fines, and other fees or penalties", is not 45-17(b)'s "Cash payments
/// to other suppliers and employees": a state's filing fee booked to an
/// organization-costs account (tax category `organization_startup`), its
/// payee a government agency, is (g), other operating cash payments, not
/// (d). So is a penalty paid to a tax agency on any account. The same
/// account paid to a lawyer stays (d), and so does a fee with no payee
/// named (nothing says it went to a government). A reversal in the next
/// period takes its original's class (rule 6), and a refund from a
/// government is a receipt, untouched: (c).
#[test]
fn a_payment_to_a_government_is_other_operating() {
let mut b = funded();
let org = b.acct("6050", "Organization costs", AccountType::Expense);
b.l.set_account_tax(org, Some("organization_startup"), None)
.unwrap();
let state =
b.l.add_party(
"Department of Commerce, Corporations Division",
&[ledger_core::Role::GovernmentAgency],
)
.unwrap();
let revenue =
b.l.add_party("Department of Revenue", &[ledger_core::Role::TaxAgency])
.unwrap();
let lawyer = b.l.add_vendor("Ashgrove Legal LLP").unwrap();
let lawyer = b.l.party_of_vendor(lawyer).unwrap();
let pay = |b: &mut Books, day, desc: &str, acct: &str, amount| {
b.post(
d(2026, 2, day),
desc,
&[(acct, DR, amount), ("1000", CR, amount)],
)
};
let fee = pay(&mut b, 2, "Filing fee", "6050", "75.00");
b.l.set_entry_party(fee, Some(state)).unwrap();
let legal = pay(&mut b, 3, "Operating agreement", "6050", "350.00");
b.l.set_entry_party(legal, Some(lawyer)).unwrap();
let unnamed = pay(&mut b, 4, "Fee, payee not named", "6050", "20.00");
let penalty = pay(&mut b, 5, "Late filing penalty", "6000", "15.00");
b.l.set_entry_party(penalty, Some(revenue)).unwrap();
let refund = b.post(
d(2026, 2, 6),
"Fee refunded",
&[("1000", DR, "10.00"), ("6050", CR, "10.00")],
);
b.l.set_entry_party(refund, Some(state)).unwrap();
let cf = feb(&b, &[], &[]);
let class = |e| cf.operating.iter().find(|l| l.entry == e).unwrap().class;
use OperatingClass::*;
assert_eq!(class(fee), Some(OtherPayments));
assert_eq!(class(penalty), Some(OtherPayments));
assert_eq!(class(legal), Some(ToSuppliersAndEmployees));
assert_eq!(class(unnamed), Some(ToSuppliersAndEmployees));
assert_eq!(class(refund), Some(OtherReceipts));
assert_eq!(
cf.operating_by_class(),
[
(OtherReceipts, x("10.00")),
(ToSuppliersAndEmployees, x("-370.00")),
(OtherPayments, x("-90.00")),
]
);
// Reversed in March: the fee's class, its own sign.
let back = b.reverse(fee, d(2026, 3, 2));
let march = b.cf(d(2026, 3, 1), d(2026, 3, 31), &["1000"], &[], &[]);
foots(&march);
let line = march.operating.iter().find(|l| l.entry == back).unwrap();
assert_eq!((line.class, line.amount), (Some(OtherPayments), x("75.00")));
}
/// rules.md B-14 rule 5: the payee decides on any account, and the tax
/// category still does without one: a license on `taxes_licenses` with no
/// party is (g). An entry naming a government and a supplier together
/// (half each) is split by their shares, as ASC 230-10-45-22 has each
/// separately identifiable use classed by its nature: half (g), half (d).
/// Named the city alone, the whole payment is the government's.
#[test]
fn a_government_payee_moves_a_fee_on_any_account() {
let mut b = funded();
let fees = b.acct("6700", "Taxes and licenses", AccountType::Expense);
b.l.set_account_tax(fees, Some("taxes_licenses"), None)
.unwrap();
let city =
b.l.add_party("City Treasurer", &[ledger_core::Role::GovernmentAgency])
.unwrap();
let printer = b.l.add_vendor("Print Shop").unwrap();
let printer = b.l.party_of_vendor(printer).unwrap();
let pay = |b: &mut Books, day, desc: &str, acct: &str, amount| {
b.post(
d(2026, 2, day),
desc,
&[(acct, DR, amount), ("1000", CR, amount)],
)
};
let license = pay(&mut b, 2, "Business license", "6700", "40.00");
let shared = pay(&mut b, 3, "Permit and signage", "6000", "100.00");
let half = |party| ledger_core::PartyShare {
party,
share: ledger_core::Share::Percent(x("50")),
};
b.l.set_entry_parties(shared, &[half(city), half(printer)])
.unwrap();
let cf = feb(&b, &[], &[]);
let classes = |cf: &CashFlowStatement, e| {
cf.operating
.iter()
.filter(|l| l.entry == e)
.map(|l| (l.class.unwrap(), l.amount))
.collect::<Vec<_>>()
};
use OperatingClass::*;
assert_eq!(classes(&cf, license), [(OtherPayments, x("-40.00"))]);
assert_eq!(
classes(&cf, shared),
[
(ToSuppliersAndEmployees, x("-50.00")),
(OtherPayments, x("-50.00"))
]
);
// Named the city alone, the same payment is the government's.
b.l.set_entry_party(shared, Some(city)).unwrap();
let cf = feb(&b, &[], &[]);
assert_eq!(classes(&cf, shared), [(OtherPayments, x("-100.00"))]);
}
/// rules.md B-14 rule 5: a split by shares that leaves an odd cent rounds
/// the government's part half to even, the supplier's the rest, so the
/// line still sums to the payment; its reversal next month mirrors it to
/// the cent. Fixed amounts split as given.
#[test]
fn a_shared_government_payment_splits_to_the_cent() {
let mut b = funded();
let state =
b.l.add_party(
"Department of Commerce",
&[ledger_core::Role::GovernmentAgency],
)
.unwrap();
let agent = b.l.add_vendor("Registered Agents Co.").unwrap();
let agent = b.l.party_of_vendor(agent).unwrap();
let pay = |b: &mut Books, day, amount| {
b.post(
d(2026, 2, day),
"Filing through an agent",
&[("6000", DR, amount), ("1000", CR, amount)],
)
};
let share = |party, share| ledger_core::PartyShare { party, share };
use ledger_core::Share::{Amount, Percent};
// A third to the state: 100.01 / 3 = 33.336..., 33.34 to the state.
let third = pay(&mut b, 2, "100.01");
b.l.set_entry_parties(
third,
&[
share(state, Percent(x("33.333333333333333333333333333"))),
share(agent, Percent(x("66.666666666666666666666666667"))),
],
)
.unwrap();
// 37.00 of 137.00 to the state, as amounts.
let fixed = pay(&mut b, 3, "137.00");
b.l.set_entry_parties(
fixed,
&[
share(state, Amount(x("37.00"))),
share(agent, Amount(x("100.00"))),
],
)
.unwrap();
let cf = feb(&b, &[], &[]);
let classes = |cf: &CashFlowStatement, e| {
cf.operating
.iter()
.filter(|l| l.entry == e)
.map(|l| (l.class.unwrap(), l.amount))
.collect::<Vec<_>>()
};
use OperatingClass::*;
assert_eq!(
classes(&cf, third),
[
(ToSuppliersAndEmployees, x("-66.67")),
(OtherPayments, x("-33.34"))
]
);
assert_eq!(
classes(&cf, fixed),
[
(ToSuppliersAndEmployees, x("-100.00")),
(OtherPayments, x("-37.00"))
]
);
let back = b.reverse(third, d(2026, 3, 2));
let march = b.cf(d(2026, 3, 1), d(2026, 3, 31), &["1000"], &[], &[]);
foots(&march);
assert_eq!(
classes(&march, back),
[
(ToSuppliersAndEmployees, x("66.67")),
(OtherPayments, x("33.34"))
]
);
}
/// rules.md B-14 rule 5: a reversal takes its original's payee, so the
/// pair always lands in one class and nets. A state's fee paid in
/// February and reversed in March: (g) both. The books keep a reversal
/// chain's parties one (Ledger::set_entry_parties INVARIANT 5): tagging
/// the reversal to the agent tags the fee too, and both move to (d);
/// untagging it untags both, still (d). (So the cash flow reading the
/// reversal's own parties rather than its chain root's can't differ: the
/// P4 review's mutant is equivalent.)
#[test]
fn a_reversal_takes_its_originals_payee() {
let mut b = funded();
let state =
b.l.add_party(
"Department of Commerce",
&[ledger_core::Role::GovernmentAgency],
)
.unwrap();
let agent = b.l.add_vendor("Registered Agents Co.").unwrap();
let agent = b.l.party_of_vendor(agent).unwrap();
let fee = b.post(
d(2026, 2, 2),
"Filing fee",
&[("6000", DR, "75.00"), ("1000", CR, "75.00")],
);
b.l.set_entry_party(fee, Some(state)).unwrap();
let back = b.reverse(fee, d(2026, 3, 2));
let classes = |b: &Books| {
let class = |month: u32, e| {
let last = if month == 2 { 28 } else { 31 };
let cf = b.cf(d(2026, month, 1), d(2026, month, last), &["1000"], &[], &[]);
foots(&cf);
cf.operating
.iter()
.filter(|l| l.entry == e)
.map(|l| (l.class.unwrap(), l.amount))
.collect::<Vec<_>>()
};
(class(2, fee), class(3, back))
};
use OperatingClass::*;
let both = |class| (vec![(class, x("-75.00"))], vec![(class, x("75.00"))]);
assert_eq!(classes(&b), both(OtherPayments));
b.l.set_entry_party(back, Some(agent)).unwrap();
assert_eq!(b.l.entry_party(fee), Some(agent), "the chain is tagged");
assert_eq!(classes(&b), both(ToSuppliersAndEmployees));
b.l.set_entry_party(back, None).unwrap();
assert!(b.l.entry_parties(fee).is_empty());
assert_eq!(classes(&b), both(ToSuppliersAndEmployees));
}
/// rules.md B-14 rules 5 and 11: a payment shared between a state and an
/// agent that a member paid personally (Dr 6000 137.00 / Cr 2100, the
/// members' financing account) is borrowed and spent, and its spending is
/// split by the parties' shares as a bank payment's is, the part no bank
/// account moved (`on_behalf`) with it: other operating −37.00, all on
/// behalf; to suppliers −100.00, all on behalf; financing +137.00. A
/// third to the state of 100.01 paid the same way: −33.34 and −66.67,
/// each on behalf.
#[test]
fn a_shared_government_payment_a_member_paid_splits_both_ways() {
let mut b = funded();
let state =
b.l.add_party(
"Department of Commerce",
&[ledger_core::Role::GovernmentAgency],
)
.unwrap();
let agent = b.l.add_vendor("Registered Agents Co.").unwrap();
let agent = b.l.party_of_vendor(agent).unwrap();
let share = |party, share| ledger_core::PartyShare { party, share };
use ledger_core::Share::{Amount, Percent};
let fixed = b.post(
d(2026, 2, 3),
"Annual report through an agent, paid by Member A",
&[("6000", DR, "137.00"), ("2100", CR, "137.00")],
);
b.l.set_entry_parties(
fixed,
&[
share(state, Amount(x("37.00"))),
share(agent, Amount(x("100.00"))),
],
)
.unwrap();
let third = b.post(
d(2026, 2, 4),
"Filing through an agent, paid by Member A",
&[("6000", DR, "100.01"), ("2100", CR, "100.01")],
);
b.l.set_entry_parties(
third,
&[
share(state, Percent(x("33.333333333333333333333333333"))),
share(agent, Percent(x("66.666666666666666666666666667"))),
],
)
.unwrap();
let cf = feb(&b, &["2100"], &[]);
let lines = |e| {
cf.operating
.iter()
.filter(|l| l.entry == e)
.map(|l| (l.class.unwrap(), l.amount, l.on_behalf))
.collect::<Vec<_>>()
};
use OperatingClass::*;
assert_eq!(
lines(fixed),
[
(ToSuppliersAndEmployees, x("-100.00"), x("-100.00")),
(OtherPayments, x("-37.00"), x("-37.00"))
]
);
assert_eq!(
lines(third),
[
(ToSuppliersAndEmployees, x("-66.67"), x("-66.67")),
(OtherPayments, x("-33.34"), x("-33.34"))
]
);
assert_eq!(
em(&cf.financing),
[(fixed, x("137.00")), (third, x("100.01"))]
);
assert_eq!(cf.net_change_in_cash, Decimal::ZERO);
}
/// rules.md B-14 rule 5 (CONVENTION): payroll keeps its structure. Taxes
/// withheld from employees and remitted to the tax agency are the
/// employees' pay (45-17(b), "Cash payments to other suppliers and
/// employees"), and the payroll payables mix them with the employer's
/// own taxes, so a remittance from them stays (d) though its payee is a
/// tax agency; so does pay on a wages tax category. The employer's payroll
/// tax paid on its own expense account is a tax: (g).
#[test]
fn a_payroll_remittance_to_a_tax_agency_stays_pay() {
let mut b = funded();
let wages = b.acct("6160", "Salaries", AccountType::Expense);
b.l.assign_role("salary_expense", Some(wages)).unwrap();
b.acct("6110", "Payroll Tax Expense", AccountType::Expense);
// The withholding payable at a code of the chart's own, given its
// role.
let withheld = b.acct("2210", "Withholding payable", AccountType::Liability);
b.l.assign_role("federal_withholding", Some(withheld))
.unwrap();
let bonus = b.acct("6150", "Bonuses", AccountType::Expense);
b.l.set_account_tax(bonus, Some("salaries_wages"), None)
.unwrap();
let irs =
b.l.add_party("Internal Revenue Service", &[ledger_core::Role::TaxAgency])
.unwrap();
b.post(
d(2026, 1, 31),
"January payroll",
&[
("6160", DR, "1000.00"),
("2210", CR, "200.00"),
("1000", CR, "800.00"),
],
);
let remit = b.post(
d(2026, 2, 3),
"Remit withheld taxes",
&[("2210", DR, "200.00"), ("1000", CR, "200.00")],
);
b.l.set_entry_party(remit, Some(irs)).unwrap();
let garnish = b.post(
d(2026, 2, 4),
"Bonus paid to a levy",
&[("6150", DR, "30.00"), ("1000", CR, "30.00")],
);
b.l.set_entry_party(garnish, Some(irs)).unwrap();
let employer = b.post(
d(2026, 2, 5),
"Employer FUTA",
&[("6110", DR, "42.00"), ("1000", CR, "42.00")],
);
b.l.set_entry_party(employer, Some(irs)).unwrap();
let cf = feb(&b, &[], &[]);
let class = |e| cf.operating.iter().find(|l| l.entry == e).unwrap().class;
use OperatingClass::*;
assert_eq!(class(remit), Some(ToSuppliersAndEmployees));
assert_eq!(class(garnish), Some(ToSuppliersAndEmployees));
assert_eq!(class(employer), Some(OtherPayments));
}
/// rules.md B-14 rule 5: a government selling goods or services is a
/// supplier (45-17(b), "for other goods or services"), not 45-17(c)'s
/// taxes, fees or penalties: the post office named with the `vendor`
/// role, postage stays (d). The roles decide, so a post office given the
/// `government_agency` role would read as (g): the handbook says not to.
#[test]
fn a_government_selling_services_is_a_supplier() {
let mut b = funded();
b.acct("6120", "Postage", AccountType::Expense);
let usps = b.l.add_vendor("Postal Service").unwrap();
let usps = b.l.party_of_vendor(usps).unwrap();
let stamps = b.post(
d(2026, 2, 4),
"Stamps",
&[("6120", DR, "30.00"), ("1000", CR, "30.00")],
);
b.l.set_entry_party(stamps, Some(usps)).unwrap();
let cf = feb(&b, &[], &[]);
let line = cf.operating.iter().find(|l| l.entry == stamps).unwrap();
assert_eq!(line.class, Some(OperatingClass::ToSuppliersAndEmployees));
b.l.set_party_role(usps, ledger_core::Role::GovernmentAgency, true)
.unwrap();
let cf = feb(&b, &[], &[]);
let line = cf.operating.iter().find(|l| l.entry == stamps).unwrap();
assert_eq!(line.class, Some(OperatingClass::OtherPayments));
}
+1
View File
@@ -4,4 +4,5 @@
- Every entry must balance: debits equal credits, exactly. Every line needs a real account from books.chart; add a missing one with books.add_account first, never guess one.
- When unsure of the category, draft and ask. A draft is cheap; a wrong posting is a reversal.
- Name the entry's party when you draft it (books.draft_entry party), so the preview can show the split (the parties-and-tins section).
- A tax, fee, fine or penalty paid to a government (a state's filing fee, a license, a late penalty): name the agency as the party, one with the government_agency or tax_agency role, on whatever account the payment belongs to (a formation filing fee is an organization cost). The cash flow statement then shows it among other operating cash payments, not cash paid to suppliers (ASC 230-10-45-17(c)); one payment shared with a supplier (a registered agent's fee plus the state's), name both with their amounts and it is split. A government selling goods or services (postage, a park fee) is a supplier: add such a payee as a vendor (books.add_vendor), never with the government_agency role. Payroll tax remittances from the payroll payables stay with pay to employees, whoever is named. If the fee went on a credit card, its cash leaves in the card payment, whose payee is the bank: it stays with suppliers unless the card is a lender's credit (in the financing_accounts setting, D-42).
- An expense or purchase a member pays personally goes to the expense or asset against the members' financing account (Due to Members), the vendor named as the party; the cash flow statement shows it as borrowed from the member and spent. What the company owes a member for their own work or property (rent, a guaranteed payment) goes to a payable instead.
+12 -2
View File
@@ -552,7 +552,13 @@ every tool over every book in ledger-store's `tests/fixtures/old-books`.
and kind: `{ entry, date, label, kind, amount, on_behalf?, accounts,
reverses?, class? }`; every transaction is listed (D-35), each under its
kind. An entry's interest is its own operating class, (b) or (e), apart
from the rest of its operating cash. What a member or a lender paid or
from the rest of its operating cash. A payment to a party with the
`government_agency` or `tax_agency` role is `other_payments`, (g),
on any account (rules.md B-14 rule 5; payroll remittances excepted);
one shared between such a party and a supplier gives two operating
lines for the one entry, `other_payments` and
`to_suppliers_and_employees`, split by the parties' shares and
summing to the payment. What a member or a lender paid or
received for the company with no cash account moving is cash borrowed
(or contributed) and spent, on the entry's date (D-42, rules.md B-14
rule 11): an expense a member paid personally through Due to Members
@@ -1192,7 +1198,11 @@ refused, never guessed.
`payout_platform`, `tax_agency`, `government_agency` (regulators and
registries that don't tax: a division of corporations, a licensing board),
`financial_institution`, `landlord`.
Vendor and customer follow the records and are never set by hand.
Vendor and customer follow the records and are never set by hand. A
`tax_agency` or `government_agency` payee also classes the company's
payments to it on the cash flow statement: other operating cash
payments, never cash paid to suppliers (`books.cash_flow`, rules.md B-14
rule 5).
**Rules the engine enforces** (each refusal changes nothing, the journal
byte for byte): no party field holds a full TIN (a run of nine digits,
+66 -5
View File
@@ -444,10 +444,63 @@ in public sources that reproduce the Codification, `sources/fasb-asc.md`):
operating cash, by its other operating lines: a receipt is (a) if a
line is revenue or the receivables control, or the entry's party is a
customer, else (c); a payment is (g) if every such line is on an
account whose tax category is `taxes_licenses` (payments to
governments for taxes and fees are 45-17(c), apart from suppliers'
45-17(b); 45-25 names only income taxes, (f)), else (d) if a line is
an expense, a liability or a non-receivables asset, else (g). (Interest paid back out
account whose tax category is `taxes_licenses`, else (d) if a line is
an expense, a liability or a non-receivables asset, else (g); then,
of a payment that is (d), what went to a government is (g): the
entry names a party with the `government_agency` or `tax_agency`
role (its chain root's parties, for a reversal), on any account. ASC
230-10-45-17 lists "Cash payments to governments for taxes, duties,
fines, and other fees or penalties" (c) apart from "Cash payments to
other suppliers and employees for other goods or services" (b) (EY FRD
p. 43, which reproduces the paragraph), and 45-25 names only income
taxes, (f), among them, so the rest are other operating cash payments.
The payee decides because the account need not: a state's filing fee
for forming the company is an organization cost (tax category
`organization_startup`), still paid to a government. Every party a
government: the whole payment is (g). A government and others named
together: the payment is split by their resolved shares (B-35), the
government's part (g), the rest (d), because 45-22 has "each
separately identifiable source or each separately identifiable use"
classed "on the basis of the nature of the underlying cash flows" (EY
p. 44; it speaks of the three activities, read here for the classes
too, CONVENTION), and the shares identify each part; the government's
part is the payment × its share ÷ the whole, rounded half to even to
the base currency's places, the rest exact, so the two always sum to
the payment and a reversal mirrors them (CONVENTION). Shares that
don't resolve leave the whole payment (d). Not a government's: a
government selling goods or services (postage, a park's entry fee) is
45-17(b)'s supplier "for other goods or services", so such a payee
is added as a vendor (the `vendor` role, books.add_vendor), never
given `government_agency` (the books can't tell what a payment
bought; the role says it). Payroll keeps its
structure (CONVENTION): an entry with a line on the salary expense or
a payroll payable control account (B-23's roles: `salary_expense`,
`federal_withholding`, `state_withholding`, `social_security_payable`,
`medicare_payable`, `other_deductions_payable`), or on an account of
a pay tax category (`salaries_wages`, `officer_compensation`,
`employee_benefits`, `pension_profit_sharing`), stays (d) whoever it
names, because taxes withheld and remitted are the employees' pay
(45-17(b), "to ... employees") and the payables mix them with the
employer's own taxes, which the remittance can't tell apart; the
employer's payroll tax paid on its own expense account is a tax, (g).
As before this change, a remittance from the payables is (d); a
payroll liability kept at a code of the chart's own needs its role
(books.assign_role) to be read so. P3 cutover review, open point 2,
2026-10-22: before, only the tax category counted, so a filing fee on
an organization-costs account read as cash paid to suppliers
(`a_payment_to_a_government_is_other_operating`,
`a_government_payee_moves_a_fee_on_any_account`,
`a_shared_government_payment_splits_to_the_cent`,
`a_payroll_remittance_to_a_tax_agency_stays_pay`,
`a_government_selling_services_is_a_supplier`,
`a_reversal_takes_its_originals_payee`,
`a_shared_government_payment_a_member_paid_splits_both_ways`, all in
ledger-core `tests/spec_cash_flow.rs`;
`a_government_part_rounds_half_to_even` and
`a_government_part_sums_and_mirrors` in ledger-core
`cashflow::tests`; bookspec `cash-flow.government-fees`). A
government's payment a member paid on the company's behalf (rule 11)
is classed and split the same way, its `on_behalf` part with it. (Interest paid back out
is (g); interest refunded to the company, (c).) The operating cash is
split among these kinds as a section's among its kinds (rule 3), in
the order: the rest, interest received, interest paid (CONVENTION).
@@ -665,7 +718,8 @@ shapes), `prop_pure_entries_sections_are_movements`; regressions
`defect1_reversed_contributions_foot_regression`,
`defect1_two_member_llc_first_year_regression`,
`defect1_two_member_llc_as_of_march_18_regression` (and bookspec
`cash-flow.*`, `cash-flow.paid-on-the-companys-behalf` for rule 11);
`cash-flow.*`, `cash-flow.paid-on-the-companys-behalf` for rule 11,
`cash-flow.government-fees` for rule 5's governments);
`disposal_proceeds_all_investing`,
`an_expense_a_member_paid_is_borrowed_and_spent`,
`reimbursing_a_member_is_a_repayment_of_borrowing`,
@@ -679,6 +733,13 @@ shapes), `prop_pure_entries_sections_are_movements`; regressions
`interest_and_guaranteed_payments_charged_to_a_creditor_stay_noncash`,
`a_fee_withheld_from_a_loan_follows_its_account`,
`odd_shapes_foot_and_net`, `operating_classes_follow_asc_230_10_45_25`,
`a_payment_to_a_government_is_other_operating`,
`a_government_payee_moves_a_fee_on_any_account`,
`a_shared_government_payment_splits_to_the_cent`,
`a_payroll_remittance_to_a_tax_agency_stays_pay`,
`a_government_selling_services_is_a_supplier`,
`a_reversal_takes_its_originals_payee`,
`a_shared_government_payment_a_member_paid_splits_both_ways`,
`a_reversal_of_a_reversal_is_shown_and_foots`,
`a_reversal_three_levels_down_is_labelled_a_reversal`,
`operating_cash_is_classed_kind_by_kind`,
+20 -3
View File
@@ -104,10 +104,27 @@ Quotes are short; the rest is paraphrase.
dividends received. 45-16(c): all other operating receipts. → rule 5,
rule 4's exception.
- **230-10-45-17** (EY pp. 43-44): operating outflows: payments for goods
for resale (a), to "other suppliers and employees" (b), to governments
for taxes, duties, fines and other fees (c), interest to lenders and
for resale (a), "Cash payments to other suppliers and employees for
other goods or services" (b), "Cash payments to governments for taxes,
duties, fines, and other fees or penalties" (c), interest to lenders and
other creditors (d), and others ((e), (ee), (f)). (Income taxes fall
under (c); 45-25 names them a class.)
under (c); 45-25 names them a class.) Quotes (b) and (c) checked
2026-10-22 against EY's guide as retrieved that day (SHA-256
`de9224a9e3ec7ef771e7ee076f99471ee727308c3dedb2df20ea1ee4e285fbdb`,
page 43). → rule 5: a payment to a government, decided by its payee or
a taxes-and-licenses account, is (g), not (d).
- **230-10-45-22, 45-22A** (EY pp. 44-45, the same copy): a receipt or
payment with "aspects of more than one class of cash flows" is
classed first by specific guidance; without it, "a reporting entity
shall determine each separately identifiable source or each separately
identifiable use within the cash receipts and cash payments on the
basis of the nature of the underlying cash flows" and classify each
"in financing, investing, or operating activities"; only what "cannot
be separated by source or use" goes by the predominant source or use
(45-22A). → rule 5: a payment shared between a government and a
supplier is split by the parties' resolved shares, each part classed
by its payee (the paragraph names the three activities; reading it for
45-25's classes too is a CONVENTION).
- **230-10-45-8, 45-9** (EY p. 12): only items whose "turnover is quick,
the amounts are large, and the maturities are short" (original maturity
"three months or less"; amounts due on demand count) may be reported
+42
View File
@@ -2178,6 +2178,48 @@ and the property. The property found (seed in
name yield to a changed one ("Café" twice beside "Cafe (1)" and "Cafe (1)
(1)"); the cap is gone, a tag that changes nothing is skipped instead.
**Point 2, payments to governments** (rules.md B-14 rule 5): `ledger-core`
`tests/spec_cash_flow.rs` `a_payment_to_a_government_is_other_operating`
(a filing fee on an organization-costs account paid to a government
agency, and a penalty on supplies paid to a tax agency, are (g); the
same account paid to a lawyer, or with no payee named, stays (d); its
reversal next month keeps (g); a refund from the state stays (c)),
`a_government_payee_moves_a_fee_on_any_account` (a `taxes_licenses`
license with no party is still (g); a payment shared half and half
between a government and a printer is split, 50.00 (g) and 50.00 (d),
and named to the government alone is all (g)),
`a_shared_government_payment_splits_to_the_cent` (a third by percent of
100.01 is 33.34 (g) and 66.67 (d), shares by amount split as given, the
reversal mirrors to the cent), `a_payroll_remittance_to_a_tax_agency_stays_pay`
(withheld taxes remitted from a payable holding its payroll role, and pay
on a `salaries_wages` account, stay (d) with the tax agency named; the
employer's tax on its own expense account is (g)) and
`a_government_selling_services_is_a_supplier` (postage to a payee with
the vendor role stays (d); given `government_agency`, it reads (g), the
limit the handbook guards); from the P4 review (2026-10-22),
`a_reversal_takes_its_originals_payee` (a state's fee and its reversal
next month are (g) both; tagging the reversal tags the chain, so both
move to (d), and untagged both stay (d): the books keep a chain's
parties one, Ledger::set_entry_parties INVARIANT 5, so reading the
reversal's own parties instead of the chain root's is an equivalent
mutant) and `a_shared_government_payment_a_member_paid_splits_both_ways`
(a state-and-agent payment a member paid, 37.00 of 137.00 by amount and
a third of 100.01 by percent: each operating part carries its own
on_behalf, financing the whole; it fails with the on-behalf part left
unsplit); `src/cashflow.rs` `tests`
`a_government_part_rounds_half_to_even` and the proptest
`a_government_part_sums_and_mirrors` (the two parts sum to the payment,
the government's within half a cent of exact, a reversal mirrors);
the bookspec example `cash-flow.government-fees.toml`, every figure
derived by hand, including a shared annual-report payment, read through
the new `class.<a..g>` key (bookspec README). Broken to confirm: without
the payee check the five spec tests fail and the example; without the
split for mixed payees the two shared-payment tests and the example
(class.d -549.50) fail; without the payroll exclusion the payroll test
fails; rounding toward zero fails both `cashflow::tests`; without the
tax-category route `operating_classes_follow_asc_230_10_45_25`, the
second test and the example fail.
## Benchmarks (baseline 2026-10-13)
No budget, by decision (the owner, 2026-10-13: the books do little work
+40 -5
View File
@@ -188,11 +188,46 @@ its invoices. The handbook's caveat is gone for books with the feature.
`projects` but not it, so until the owner's next format upgrade
(`wanderbooks books upgrade`, D-39) their invoices and bills still take
none (the handbook says so).
- Raised by the cutover review (private change log, "Open points"): a
state filing fee is classed as cash paid to suppliers where ASC
230-10-45-17(c) suggests other operating payments; ties in the per-entry
member split all go to the first member (the owner, 2026-10-22: nothing
is posted until the year-end close, three weeks off, so it waits).
- Raised by the cutover review (private change log, "Open points"):
ties in the per-entry member split of the tax-basis K-1 all go to the
first member (the owner, 2026-10-22: nothing is posted until the
year-end close, three weeks off, so it waits).
Its second point is done (2026-10-22, B-14 rule 5): a payment to a
government (ASC 230-10-45-17(c), "Cash payments to governments for
taxes, duties, fines, and other fees or penalties") is other operating
cash payments, (g), never cash paid to suppliers, decided by its payee
(a party with the government or tax agency role) on any account, as
well as by a `taxes_licenses` account as before; a payment shared with
a supplier is split by the parties' shares (45-22); payroll
remittances from the payroll payables stay (d) as before; a government
selling goods or services is a supplier (add it as a vendor). The
first company's state filing fee, booked to its organization-costs
account, moves from "Cash paid to suppliers and employees" to "Other
operating cash payments" at every date (no total changes), provided
(1) its entry names the state's agency with the `government_agency`
role (check with `books.party`, give the role with
`books.set_party_roles` if not), and (2) the fee's own entry is the
one whose cash the statement lists: paid from the bank; charged to a
card that is a lender's credit (the `financing_accounts` setting, D-42,
borrowed and spent); or paid by a member personally through the
members' account (Due to Shareholders) while that account is in
`financing_accounts` (B-14 rule 11: borrowed from the member and spent
on the fee's date, the operating part classed (g) with its
`on_behalf`, and split the same way when shared:
`a_shared_government_payment_a_member_paid_splits_both_ways`). The
first company's fee was paid the last way (the books home's goldens
change log, entry #1), so check its members' account is a financing
account. Charged to a card, or paid through a members' account, that
is an operating payable, its cash leaves in the later payment (to the
bank, or the member's reimbursement), whose payee is not the state,
and it stays with suppliers: check how it was paid (`books.get_entry`).
Expected golden change: once this ships, the cash-flow snapshots in the
books home's `goldens/` (each cutover's) differ from a fresh
run on two operating lines, "Cash paid to suppliers and employees"
smaller and "Other operating cash payments" larger by the fee, every
section total, net change and cash balance the same. That is this
rule, not a regression: diff the class lines against it at the next
cutover and refresh the snapshot then.
Its first point is done (2026-10-22, B-34 rule 8): the IIF writes each
line's project in CLASS, the projects declared first in a `!CLASS`
list, their names held to QuickBooks' 31 characters, no two alike, and