P3: the cash flow statement by ASC 230 (defect 1, D-35, D-42)
check / check (push) Canceled after 0s
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:
64 files changed
+12128
-1567
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -1734,16 +1734,23 @@ fn the_readme_names_the_entries_on_receivables_and_payables() {
|
||||
assert!(!text(&p, "README.txt").contains("customer or vendor"));
|
||||
}
|
||||
|
||||
/// An XLSX's shared strings, as text.
|
||||
/// Every text the package's XLSX `name` holds: its XML parts (the shared
|
||||
/// strings and the sheets), concatenated, `&` read as `&`.
|
||||
fn xlsx_strings(p: &Package, name: &str) -> String {
|
||||
let (_, bytes) = p.files.iter().find(|(n, _)| n == name).unwrap();
|
||||
let (_, bytes) = p
|
||||
.files
|
||||
.iter()
|
||||
.find(|(n, _)| n == name)
|
||||
.unwrap_or_else(|| panic!("{name} in {:?}", names(p)));
|
||||
let mut x = zip::ZipArchive::new(std::io::Cursor::new(bytes)).unwrap();
|
||||
let mut out = String::new();
|
||||
x.by_name("xl/sharedStrings.xml")
|
||||
.unwrap()
|
||||
.read_to_string(&mut out)
|
||||
.unwrap();
|
||||
out
|
||||
for i in 0..x.len() {
|
||||
let mut part = x.by_index(i).unwrap();
|
||||
if part.name().unwrap().ends_with(".xml") {
|
||||
part.read_to_string(&mut out).unwrap();
|
||||
}
|
||||
}
|
||||
out.replace("&", "&")
|
||||
}
|
||||
|
||||
/// Random books for [`iif_ties_to_the_trial_balance_as_of`]: entries of
|
||||
@@ -1947,3 +1954,35 @@ fn the_bookspec_iif_example_matches_the_engine() {
|
||||
}
|
||||
assert_eq!(checked, 2);
|
||||
}
|
||||
|
||||
/// The package's statement of cash flows is the direct method's (rules.md
|
||||
/// B-14, D-35): operating by ASC 230-10-45-25 class, financing by kind
|
||||
/// with each member named, and both schedules: the reconciliation of net
|
||||
/// income to operating cash (45-30) and the noncash investing and
|
||||
/// financing activities (50-3; "None" here: every entry moved cash).
|
||||
#[test]
|
||||
fn the_cash_flow_carries_both_schedules() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let b = books(dir.path());
|
||||
let company = company(true);
|
||||
let cash = cash();
|
||||
let p = build_package(&b.store, &settings(&company, &cash), &request(true, None)).unwrap();
|
||||
let text = xlsx_strings(&p, "cash-flow.xlsx");
|
||||
for m in &company.members {
|
||||
let want = format!("Contributions from members — {}", m.name);
|
||||
assert!(text.contains(&want), "{want:?} not in the cash flow");
|
||||
}
|
||||
for want in [
|
||||
"Statement of Cash Flows",
|
||||
"Cash received from customers",
|
||||
"Cash paid to suppliers and employees",
|
||||
"Members' contributions (#",
|
||||
"Reconciliation of net income (loss) to net cash from operating activities",
|
||||
"Noncash investing and financing activities",
|
||||
"None",
|
||||
] {
|
||||
assert!(text.contains(want), "{want:?} not in the cash flow");
|
||||
}
|
||||
assert!(!text.contains("Member distributions"));
|
||||
assert!(p.files.iter().any(|(n, _)| n == "cash-flow.pdf"));
|
||||
}
|
||||
Reference in new issue
Block a user