Implement trial balance report

This commit is contained in:
RunasSudo 2025-05-27 22:19:36 +10:00
parent 2a2fb5764c
commit 00c7833706
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
4 changed files with 197 additions and 6 deletions

View File

@ -145,4 +145,40 @@ async fn main() {
"{}",
result.downcast_ref::<DynamicReport>().unwrap().to_json()
);
// Get trial balance
let targets = vec![
ReportingProductId {
name: "CalculateIncomeTax",
kind: ReportingProductKind::Transactions,
args: Box::new(VoidArgs {}),
},
ReportingProductId {
name: "TrialBalance",
kind: ReportingProductKind::Generic,
args: Box::new(DateArgs {
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
}),
},
];
let products = generate_report(targets, Arc::clone(&context))
.await
.unwrap();
let result = products
.get_or_err(&ReportingProductId {
name: "TrialBalance",
kind: ReportingProductKind::Generic,
args: Box::new(DateArgs {
date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(),
}),
})
.unwrap();
println!("Trial balance:");
println!(
"{}",
result.downcast_ref::<DynamicReport>().unwrap().to_json()
);
}

View File

@ -25,7 +25,7 @@ use serde::{Deserialize, Serialize};
use crate::QuantityInt;
use super::types::{GenericReportingProduct, ReportingProduct};
use super::types::ReportingProduct;
/// Represents a dynamically generated report composed of [CalculatableDynamicReportEntry]
#[derive(Clone, Debug)]
@ -184,6 +184,14 @@ pub struct DynamicReport {
}
impl DynamicReport {
pub fn new(title: String, columns: Vec<String>, entries: Vec<DynamicReportEntry>) -> Self {
Self {
title,
columns,
entries,
}
}
/// Remove all entries from the report where auto_hide is enabled and quantity is zero
pub fn auto_hide(&mut self) {
self.entries.retain_mut(|e| match e {
@ -212,7 +220,6 @@ impl DynamicReport {
}
}
impl GenericReportingProduct for DynamicReport {}
impl ReportingProduct for DynamicReport {}
#[derive(Clone, Debug)]

View File

@ -36,7 +36,7 @@ use crate::QuantityInt;
use super::calculator::ReportingGraphDependencies;
use super::dynamic_report::{
entries_for_kind, CalculatableDynamicReport, CalculatableDynamicReportEntry,
CalculatableSection, CalculatedRow, LiteralRow,
CalculatableSection, CalculatedRow, DynamicReport, DynamicReportEntry, LiteralRow,
};
use super::executor::ReportingExecutionError;
use super::types::{
@ -57,6 +57,7 @@ pub fn register_lookup_fns(context: &mut ReportingContext) {
IncomeStatement::register_lookup_fn(context);
PostUnreconciledStatementLines::register_lookup_fn(context);
RetainedEarningsToEquity::register_lookup_fn(context);
TrialBalance::register_lookup_fn(context);
}
/// Target representing all transactions except charging current year and retained earnings to equity
@ -1313,3 +1314,153 @@ impl ReportingStep for RetainedEarningsToEquity {
Ok(result)
}
}
/// Generates a trial balance [DynamicReport]
#[derive(Debug)]
pub struct TrialBalance {
pub args: DateArgs,
}
impl TrialBalance {
fn register_lookup_fn(context: &mut ReportingContext) {
context.register_lookup_fn(
"TrialBalance",
&[ReportingProductKind::Generic],
Self::takes_args,
Self::from_args,
);
}
fn takes_args(args: &Box<dyn ReportingStepArgs>) -> bool {
args.is::<DateArgs>()
}
fn from_args(args: Box<dyn ReportingStepArgs>) -> Box<dyn ReportingStep> {
Box::new(TrialBalance {
args: *args.downcast().unwrap(),
})
}
}
impl Display for TrialBalance {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{}", self.id()))
}
}
#[async_trait]
impl ReportingStep for TrialBalance {
fn id(&self) -> ReportingStepId {
ReportingStepId {
name: "TrialBalance",
product_kinds: &[ReportingProductKind::Generic],
args: Box::new(self.args.clone()),
}
}
fn requires(&self, _context: &ReportingContext) -> Vec<ReportingProductId> {
let mut result = Vec::new();
// TrialBalance depends on AllTransactionsExceptEarningsToEquity at the requested date
result.push(ReportingProductId {
name: "AllTransactionsExceptEarningsToEquity",
kind: ReportingProductKind::BalancesAt,
args: Box::new(self.args.clone()),
});
result
}
async fn execute(
&self,
_context: &ReportingContext,
_steps: &Vec<Box<dyn ReportingStep>>,
_dependencies: &ReportingGraphDependencies,
products: &RwLock<ReportingProducts>,
) -> Result<ReportingProducts, ReportingExecutionError> {
let products = products.read().await;
// Get balances for each period
let balances = &products
.get_or_err(&ReportingProductId {
name: "AllTransactionsExceptEarningsToEquity",
kind: ReportingProductKind::BalancesAt,
args: Box::new(self.args.clone()),
})?
.downcast_ref::<BalancesAt>()
.unwrap()
.balances;
// Get sorted list of accounts
let mut accounts = balances.keys().collect::<Vec<_>>();
accounts.sort();
// Get total debits and credits
let total_dr = balances.values().filter(|b| **b >= 0).sum::<i64>();
let total_cr = -balances.values().filter(|b| **b < 0).sum::<i64>();
// Init report
let mut report = DynamicReport::new(
"Trial balance".to_string(),
vec!["Dr".to_string(), "Cr".to_string()],
{
let mut entries = Vec::new();
// Entry for each account
for account in accounts {
entries.push(DynamicReportEntry::LiteralRow(LiteralRow {
text: account.clone(),
quantity: vec![
// Dr cell
if balances[account] >= 0 {
balances[account]
} else {
0
},
// Cr cell
if balances[account] < 0 {
-balances[account]
} else {
0
},
],
id: None,
visible: true,
auto_hide: true,
link: None,
heading: false,
bordered: false,
}));
}
// Total row
entries.push(DynamicReportEntry::LiteralRow(LiteralRow {
text: "Totals".to_string(),
quantity: vec![total_dr, total_cr],
id: Some("totals".to_string()),
visible: true,
auto_hide: false,
link: None,
heading: true,
bordered: true,
}));
entries
},
);
report.auto_hide();
// Store result
let mut result = ReportingProducts::new();
result.insert(
ReportingProductId {
name: "TrialBalance",
kind: ReportingProductKind::Generic,
args: Box::new(self.args.clone()),
},
Box::new(report),
);
Ok(result)
}
}

View File

@ -190,9 +190,6 @@ pub struct BalancesBetween {
impl ReportingProduct for BalancesBetween {}
/// Represents a custom [ReportingProduct] generated by a [ReportingStep]
pub trait GenericReportingProduct: Debug + ReportingProduct {}
/// Map from [ReportingProductId] to [ReportingProduct]
#[derive(Clone, Debug)]
pub struct ReportingProducts {