Implement trial balance report
This commit is contained in:
parent
2a2fb5764c
commit
00c7833706
36
src/main.rs
36
src/main.rs
@ -145,4 +145,40 @@ async fn main() {
|
|||||||
"{}",
|
"{}",
|
||||||
result.downcast_ref::<DynamicReport>().unwrap().to_json()
|
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()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::QuantityInt;
|
use crate::QuantityInt;
|
||||||
|
|
||||||
use super::types::{GenericReportingProduct, ReportingProduct};
|
use super::types::ReportingProduct;
|
||||||
|
|
||||||
/// Represents a dynamically generated report composed of [CalculatableDynamicReportEntry]
|
/// Represents a dynamically generated report composed of [CalculatableDynamicReportEntry]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@ -184,6 +184,14 @@ pub struct DynamicReport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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
|
/// Remove all entries from the report where auto_hide is enabled and quantity is zero
|
||||||
pub fn auto_hide(&mut self) {
|
pub fn auto_hide(&mut self) {
|
||||||
self.entries.retain_mut(|e| match e {
|
self.entries.retain_mut(|e| match e {
|
||||||
@ -212,7 +220,6 @@ impl DynamicReport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GenericReportingProduct for DynamicReport {}
|
|
||||||
impl ReportingProduct for DynamicReport {}
|
impl ReportingProduct for DynamicReport {}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -36,7 +36,7 @@ use crate::QuantityInt;
|
|||||||
use super::calculator::ReportingGraphDependencies;
|
use super::calculator::ReportingGraphDependencies;
|
||||||
use super::dynamic_report::{
|
use super::dynamic_report::{
|
||||||
entries_for_kind, CalculatableDynamicReport, CalculatableDynamicReportEntry,
|
entries_for_kind, CalculatableDynamicReport, CalculatableDynamicReportEntry,
|
||||||
CalculatableSection, CalculatedRow, LiteralRow,
|
CalculatableSection, CalculatedRow, DynamicReport, DynamicReportEntry, LiteralRow,
|
||||||
};
|
};
|
||||||
use super::executor::ReportingExecutionError;
|
use super::executor::ReportingExecutionError;
|
||||||
use super::types::{
|
use super::types::{
|
||||||
@ -57,6 +57,7 @@ pub fn register_lookup_fns(context: &mut ReportingContext) {
|
|||||||
IncomeStatement::register_lookup_fn(context);
|
IncomeStatement::register_lookup_fn(context);
|
||||||
PostUnreconciledStatementLines::register_lookup_fn(context);
|
PostUnreconciledStatementLines::register_lookup_fn(context);
|
||||||
RetainedEarningsToEquity::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
|
/// Target representing all transactions except charging current year and retained earnings to equity
|
||||||
@ -1313,3 +1314,153 @@ impl ReportingStep for RetainedEarningsToEquity {
|
|||||||
Ok(result)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -190,9 +190,6 @@ pub struct BalancesBetween {
|
|||||||
|
|
||||||
impl ReportingProduct for BalancesBetween {}
|
impl ReportingProduct for BalancesBetween {}
|
||||||
|
|
||||||
/// Represents a custom [ReportingProduct] generated by a [ReportingStep]
|
|
||||||
pub trait GenericReportingProduct: Debug + ReportingProduct {}
|
|
||||||
|
|
||||||
/// Map from [ReportingProductId] to [ReportingProduct]
|
/// Map from [ReportingProductId] to [ReportingProduct]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ReportingProducts {
|
pub struct ReportingProducts {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user