Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::finance_ledger_journal_validate::{journal_line_from_value, validate_journal, JournalLine}; ← from finance.ledger.journal-validate ^1.0.0 · built alongside by fune
use super::money_add::add_money; ← from money.add ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_to_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
const FATAL: [&str; 3] = ["invalid-account", "currency-mismatch", "negative-amount"];
/// A trial balance: each account's balance on its own side, and whether the
/// debit column equals the credit column.
///
/// # Panics
/// Panics, with journal-validate's message, on a line that cannot be totalled:
/// another currency, a negative amount or a malformed account code. An
/// unbalanced ledger is an answer, not a panic.
pub fn trial_balance(lines: &[JournalLine], currency: &str) -> TrialBalance {
let check = validate_journal(lines, currency);
for problem in &check.problems {
if FATAL.contains(&problem.code.as_str()) {
panic!("{}", problem.message);
}
}
let zero = money(0, currency);
let mut totals: Vec<(String, Money, Money)> = Vec::new();
for line in lines {
if line.debit.minor == 0 && line.credit.minor == 0 {
continue;
}
match totals.iter_mut().find(|(account, _, _)| *account == line.account) {
Some(entry) => {
entry.1 = add_money(&entry.1, &line.debit);
entry.2 = add_money(&entry.2, &line.credit);
}
None => totals.push((line.account.clone(), line.debit.clone(), line.credit.clone())),
}
}
totals.sort_by(|a, b| a.0.cmp(&b.0));
let mut rows: Vec<TrialBalanceRow> = Vec::with_capacity(totals.len());
let mut total_debit = zero.clone();
let mut total_credit = zero;
for (account, debits, credits) in totals {
let net = debits.minor - credits.minor;
let debit_balance = money(if net > 0 { net } else { 0 }, currency);
let credit_balance = money(if net < 0 { -net } else { 0 }, currency);
total_debit = add_money(&total_debit, &debit_balance);
total_credit = add_money(&total_credit, &credit_balance);
rows.push(TrialBalanceRow {
account,
debits,
credits,
debit_balance,
credit_balance,
});
}
let balanced = total_debit.minor == total_credit.minor;
TrialBalance {
rows,
total_debit,
total_credit,
balanced,
}
}
pub fn trial_balance_to_value(tb: &TrialBalance) -> Value {
Value::obj(vec![
(
"rows",
Value::Arr(
tb.rows
.iter()
.map(|r| {
Value::obj(vec![
("account", Value::str(&r.account)),
("debits", money_to_value(&r.debits)),
("credits", money_to_value(&r.credits)),
("debitBalance", money_to_value(&r.debit_balance)),
("creditBalance", money_to_value(&r.credit_balance)),
])
})
.collect(),
),
),
("totalDebit", money_to_value(&tb.total_debit)),
("totalCredit", money_to_value(&tb.total_credit)),
("balanced", Value::Bool(tb.balanced)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let lines: Vec<JournalLine> = args[0].as_arr().iter().map(journal_line_from_value).collect();
trial_balance_to_value(&trial_balance(&lines, args[1].as_str()))
}