P3: the cash flow statement by ASC 230 (defect 1, D-35, D-42)
check / check (push) Canceled after 0s

Direct method with the operating reconciliation and noncash schedules; it
foots by construction (Lean and Kani). Every non-cash account belongs to
one section; reversals net; a member or lender paying for the company is
cash borrowed and spent (D-42, constructive receipt and disbursement);
property a member contributes stays noncash. The bookspec seed: one loader
checks every example.

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-21 09:35:32 -06:00
1 parent 2c29d48ab9
commit eb4329bb2a
64 files changed
+12128 -1567

No files matched your search

+13 -13
View File
@@ -170,19 +170,19 @@ pub fn build_package<B: PackageBooks + ?Sized>(
),
(
"cash-flow",
report::cash_flow(
&cash_flow_for(
ledger,
settings,
Some(&fy_start),
Some(&as_of_s),
req.cash_accounts,
req.financing_accounts,
req.investing_accounts,
today,
)?,
&members,
),
// The package's one list of each (INVARIANT 7); investing
// accounts are the cash flow's alone (the request's, else the
// settings').
report::cash_flow(&cash_flow_for(
ledger,
settings,
Some(&fy_start),
Some(&as_of_s),
cash_codes,
financing_codes,
req.investing_accounts,
today,
)?),
),
];
let mut k1_csv: Option<(String, Vec<u8>)> = None;
+28 -1
View File
@@ -13,7 +13,12 @@
//! refused.
//! 2. The cash flow statement needs at least one cash account, and
//! every code it is given must be in the chart; account lists a call
//! leaves empty default to the settings' roles.
//! leaves empty default to the settings' roles. It foots, or it is
//! refused (an engine error, never a figure: rules.md B-14 rule 9);
//! roles that contradict each other or the chart are refused by the
//! engine, naming the setting, so the viewer and the server refuse
//! alike. Members are named on their contributions and
//! distributions (D-35).
//! 3. The year's earnings split net income year to date from the part
//! not yet closed into equity (closes are cumulative).
//! ```
@@ -98,6 +103,9 @@ pub fn bs_statement(
/// Build the cash flow statement from account codes, as the exports need it:
/// `start` defaults to the beginning of time, `end` to `today`, and at
/// least one cash account is required (the statement reconciles to them).
/// A member whose capital account the chart lacks is left unnamed (equity
/// is then named by account), never a refusal of the statement.
#[allow(clippy::too_many_arguments)]
pub fn cash_flow_statement(
ledger: &Ledger,
start: Option<&str>,
@@ -105,6 +113,7 @@ pub fn cash_flow_statement(
cash_codes: &[String],
financing_codes: &[String],
investing_codes: &[String],
members: &[Member],
today: NaiveDate,
) -> Result<ledger_core::reports::CashFlowStatement, String> {
let resolve = |codes: &[String], what: &str| -> Result<Vec<AccountId>, String> {
@@ -129,6 +138,22 @@ pub fn cash_flow_statement(
let financing_accounts = resolve(financing_codes, "financing")?;
let investing_accounts = resolve(investing_codes, "investing")?;
let (start, end) = statement_range(ledger, start, end, today)?;
let chart = ledger.chart();
let members = members
.iter()
.filter_map(|m| {
Some(ledger_core::MemberCapital {
name: m.name.clone(),
capital_account: chart.find_by_code(&m.capital_account)?.id,
distribution_account: m
.distribution_account
.as_deref()
.and_then(|c| chart.find_by_code(c))
.map(|a| a.id),
share: m.share,
})
})
.collect();
ledger_core::cash_flow(
ledger,
&ledger_core::CashFlowParams {
@@ -137,6 +162,7 @@ pub fn cash_flow_statement(
cash_accounts,
financing_accounts,
investing_accounts,
members,
},
)
.map_err(|e| e.to_string())
@@ -169,6 +195,7 @@ pub fn cash_flow_for(
&or(cash, settings.cash_accounts),
&or(financing, settings.financing_accounts),
&or(investing, settings.investing_accounts),
&settings.company.members,
today,
)
}
+2 -2
View File
@@ -19,7 +19,7 @@ use chrono::NaiveDate;
use rust_decimal::Decimal;
/// A money amount as a decimal string with at least two places: "0.00",
/// "207.68", "1000.00". Padding only, never rounding: an exact amount with
/// "318.42", "1000.00". Padding only, never rounding: an exact amount with
/// more places (an FX-converted line) keeps them. Never "-0".
pub fn money(n: Decimal) -> String {
let mut n = if n.is_zero() { Decimal::ZERO } else { n };
@@ -89,7 +89,7 @@ mod tests {
assert_eq!(m("-0"), "0.00");
assert_eq!(m("1000"), "1000.00");
assert_eq!(m("5.8"), "5.80");
assert_eq!(m("-207.68"), "-207.68");
assert_eq!(m("-318.42"), "-318.42");
assert_eq!(m("12.3456"), "12.3456");
}