Stub implementations for all steps
This commit is contained in:
parent
71c3629898
commit
40b0afe492
30
Cargo.lock
generated
30
Cargo.lock
generated
@ -88,6 +88,18 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15401da73a9ed8c80e3b2d4dc05fe10e7b72d7243b9f614e516a44fa99986e88"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.63"
|
||||
@ -112,6 +124,16 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
@ -137,7 +159,7 @@ dependencies = [
|
||||
"dyn-clone",
|
||||
"dyn-eq",
|
||||
"dyn-hash",
|
||||
"solvent",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -191,12 +213,6 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "solvent"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14a50198e546f29eb0a4f977763c8277ec2184b801923c3be71eeaec05471f16"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.101"
|
||||
|
@ -9,4 +9,4 @@ downcast-rs = "2.0.1"
|
||||
dyn-clone = "1.0.19"
|
||||
dyn-eq = "0.1.3"
|
||||
dyn-hash = "0.2.2"
|
||||
solvent = "0.8.3"
|
||||
indexmap = "2.9.0"
|
||||
|
46
src/main.rs
46
src/main.rs
@ -20,10 +20,12 @@ use chrono::NaiveDate;
|
||||
use libdrcr::reporting::builders::register_dynamic_builders;
|
||||
use libdrcr::reporting::generate_report;
|
||||
use libdrcr::reporting::steps::{
|
||||
register_lookup_fns, AllTransactionsExceptRetainedEarnings, CalculateIncomeTax,
|
||||
register_lookup_fns, AllTransactionsExceptRetainedEarnings,
|
||||
AllTransactionsIncludingRetainedEarnings, CalculateIncomeTax,
|
||||
};
|
||||
use libdrcr::reporting::types::{
|
||||
DateStartDateEndArgs, ReportingContext, ReportingProductKind, ReportingStep,
|
||||
DateArgs, DateStartDateEndArgs, ReportingContext, ReportingProductId, ReportingProductKind,
|
||||
ReportingStep,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
@ -31,6 +33,8 @@ fn main() {
|
||||
register_lookup_fns(&mut context);
|
||||
register_dynamic_builders(&mut context);
|
||||
|
||||
// Get income statement
|
||||
|
||||
let targets: Vec<Box<dyn ReportingStep>> = vec![
|
||||
Box::new(CalculateIncomeTax {}),
|
||||
Box::new(AllTransactionsExceptRetainedEarnings {
|
||||
@ -42,7 +46,41 @@ fn main() {
|
||||
}),
|
||||
];
|
||||
|
||||
let products = generate_report(targets, &context);
|
||||
let products = generate_report(targets, &context).unwrap();
|
||||
let result = products
|
||||
.get_or_err(&ReportingProductId {
|
||||
name: "AllTransactionsExceptRetainedEarnings",
|
||||
kind: ReportingProductKind::BalancesBetween,
|
||||
args: Box::new(DateStartDateEndArgs {
|
||||
date_start: NaiveDate::from_ymd_opt(2024, 7, 1).unwrap(),
|
||||
date_end: NaiveDate::from_ymd_opt(2025, 6, 30).unwrap(),
|
||||
}),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
println!("{:?}", products);
|
||||
println!("{:?}", result);
|
||||
|
||||
// Get balance sheet
|
||||
|
||||
let targets: Vec<Box<dyn ReportingStep>> = vec![
|
||||
Box::new(CalculateIncomeTax {}),
|
||||
Box::new(AllTransactionsIncludingRetainedEarnings {
|
||||
args: DateArgs {
|
||||
date: NaiveDate::from_ymd_opt(2025, 6, 30).unwrap(),
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
let products = generate_report(targets, &context).unwrap();
|
||||
let result = products
|
||||
.get_or_err(&ReportingProductId {
|
||||
name: "AllTransactionsIncludingRetainedEarnings",
|
||||
kind: ReportingProductKind::BalancesAt,
|
||||
args: Box::new(DateArgs {
|
||||
date: NaiveDate::from_ymd_opt(2025, 6, 30).unwrap(),
|
||||
}),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
println!("{:?}", result);
|
||||
}
|
||||
|
@ -16,12 +16,15 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
|
||||
use super::calculator::{has_step_or_can_build, HasStepOrCanBuild, ReportingGraphDependencies};
|
||||
use super::executor::ReportingExecutionError;
|
||||
use super::types::{
|
||||
DateArgs, DateStartDateEndArgs, ReportingContext, ReportingProductId, ReportingProductKind,
|
||||
ReportingStep, ReportingStepArgs, ReportingStepDynamicBuilder, ReportingStepId,
|
||||
BalancesAt, BalancesBetween, DateArgs, DateStartDateEndArgs, ReportingContext,
|
||||
ReportingProductId, ReportingProductKind, ReportingProducts, ReportingStep, ReportingStepArgs,
|
||||
ReportingStepDynamicBuilder, ReportingStepId, Transactions,
|
||||
};
|
||||
|
||||
/// Call [ReportingContext::register_dynamic_builder] for all dynamic builders provided by this module
|
||||
@ -40,6 +43,7 @@ pub struct BalancesAtToBalancesBetween {
|
||||
args: DateStartDateEndArgs,
|
||||
}
|
||||
|
||||
/// This dynamic builder automatically generates a [BalancesBetween] by subtracting [BalancesAt] between two dates
|
||||
impl BalancesAtToBalancesBetween {
|
||||
// Implements BalancesAt, BalancesAt -> BalancesBetween
|
||||
|
||||
@ -137,13 +141,69 @@ impl ReportingStep for BalancesAtToBalancesBetween {
|
||||
name: self.step_name,
|
||||
kind: ReportingProductKind::BalancesAt,
|
||||
args: Box::new(DateArgs {
|
||||
date: self.args.date_end.clone(),
|
||||
date: self.args.date_end,
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
_dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
// Get balances at dates
|
||||
let balances_start = &products
|
||||
.get_or_err(&ReportingProductId {
|
||||
name: self.step_name,
|
||||
kind: ReportingProductKind::BalancesAt,
|
||||
args: Box::new(DateArgs {
|
||||
date: self.args.date_start.pred_opt().unwrap(), // Opening balance is the closing balance of the preceding day
|
||||
}),
|
||||
})?
|
||||
.downcast_ref::<BalancesAt>()
|
||||
.unwrap()
|
||||
.balances;
|
||||
|
||||
let balances_end = &products
|
||||
.get_or_err(&ReportingProductId {
|
||||
name: self.step_name,
|
||||
kind: ReportingProductKind::BalancesAt,
|
||||
args: Box::new(DateArgs {
|
||||
date: self.args.date_end,
|
||||
}),
|
||||
})?
|
||||
.downcast_ref::<BalancesAt>()
|
||||
.unwrap()
|
||||
.balances;
|
||||
|
||||
// Compute balances_end - balances_start
|
||||
let mut balances = BalancesBetween {
|
||||
balances: balances_end.clone(),
|
||||
};
|
||||
|
||||
for (account, balance) in balances_start.iter() {
|
||||
let running_balance = balances.balances.get(account).unwrap_or(&0) - balance;
|
||||
balances.balances.insert(account.clone(), running_balance);
|
||||
}
|
||||
|
||||
// Store result
|
||||
products.insert(
|
||||
ReportingProductId {
|
||||
name: self.id().name,
|
||||
kind: ReportingProductKind::BalancesBetween,
|
||||
args: Box::new(self.args.clone()),
|
||||
},
|
||||
Box::new(balances),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This dynamic builder automatically generates a [BalancesAt] from a step which has no dependencies and generates [Transactions] (e.g. [super::steps::PostUnreconciledStatementLines])
|
||||
#[derive(Debug)]
|
||||
pub struct GenerateBalances {
|
||||
step_name: &'static str,
|
||||
@ -151,8 +211,6 @@ pub struct GenerateBalances {
|
||||
}
|
||||
|
||||
impl GenerateBalances {
|
||||
// Implements (() -> Transactions) -> BalancesAt
|
||||
|
||||
fn register_dynamic_builder(context: &mut ReportingContext) {
|
||||
context.register_dynamic_builder(ReportingStepDynamicBuilder {
|
||||
name: "GenerateBalances",
|
||||
@ -238,8 +296,58 @@ impl ReportingStep for GenerateBalances {
|
||||
args: Box::new(self.args.clone()),
|
||||
}]
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
_dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
// Get the transactions
|
||||
let transactions = &products
|
||||
.get_or_err(&ReportingProductId {
|
||||
name: self.step_name,
|
||||
kind: ReportingProductKind::Transactions,
|
||||
args: Box::new(self.args.clone()),
|
||||
})?
|
||||
.downcast_ref::<Transactions>()
|
||||
.unwrap()
|
||||
.transactions;
|
||||
|
||||
// Sum balances
|
||||
let mut balances = BalancesAt {
|
||||
balances: HashMap::new(),
|
||||
};
|
||||
|
||||
for transaction in transactions.iter() {
|
||||
for posting in transaction.postings.iter() {
|
||||
// FIXME: Do currency conversion
|
||||
let running_balance =
|
||||
balances.balances.get(&posting.account).unwrap_or(&0) + posting.quantity;
|
||||
balances
|
||||
.balances
|
||||
.insert(posting.account.clone(), running_balance);
|
||||
}
|
||||
}
|
||||
|
||||
// Store result
|
||||
products.insert(
|
||||
ReportingProductId {
|
||||
name: self.step_name,
|
||||
kind: ReportingProductKind::BalancesAt,
|
||||
args: Box::new(self.args.clone()),
|
||||
},
|
||||
Box::new(balances),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This dynamic builder automatically generates a [BalancesAt] from:
|
||||
/// - a step which generates [Transactions] from [BalancesAt], or
|
||||
/// - a step which generates [Transactions] from [BalancesBetween], and for which a [BalancesAt] is also available
|
||||
#[derive(Debug)]
|
||||
pub struct UpdateBalancesAt {
|
||||
step_name: &'static str,
|
||||
@ -374,8 +482,97 @@ impl ReportingStep for UpdateBalancesAt {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
steps: &Vec<Box<dyn ReportingStep>>,
|
||||
dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
// Look up the parent step, so we can extract the appropriate args
|
||||
let parent_step = steps
|
||||
.iter()
|
||||
.find(|s| {
|
||||
s.id().name == self.step_name
|
||||
&& s.id()
|
||||
.product_kinds
|
||||
.contains(&ReportingProductKind::Transactions)
|
||||
})
|
||||
.unwrap(); // Existence is checked in can_build
|
||||
|
||||
// Get transactions
|
||||
let transactions = &products
|
||||
.get_or_err(&ReportingProductId {
|
||||
name: self.step_name,
|
||||
kind: ReportingProductKind::Transactions,
|
||||
args: parent_step.id().args,
|
||||
})?
|
||||
.downcast_ref::<Transactions>()
|
||||
.unwrap()
|
||||
.transactions;
|
||||
|
||||
// Look up the BalancesAt step
|
||||
let dependencies_for_step = dependencies.dependencies_for_step(&parent_step.id());
|
||||
let dependency = &dependencies_for_step[0].product; // Existence and uniqueness checked in can_build
|
||||
|
||||
let opening_balances_at;
|
||||
|
||||
if dependency.kind == ReportingProductKind::BalancesAt {
|
||||
// Directly depends on BalancesAt -> Transaction
|
||||
opening_balances_at = products
|
||||
.get_or_err(&dependency)?
|
||||
.downcast_ref::<BalancesAt>()
|
||||
.unwrap();
|
||||
} else {
|
||||
// As checked in can_build, must depend on BalancesBetween -> Transaction with a BalancesAt available
|
||||
let date_end = dependency
|
||||
.args
|
||||
.downcast_ref::<DateStartDateEndArgs>()
|
||||
.unwrap()
|
||||
.date_end;
|
||||
|
||||
opening_balances_at = products
|
||||
.get_or_err(&ReportingProductId {
|
||||
name: dependency.name,
|
||||
kind: ReportingProductKind::BalancesAt,
|
||||
args: Box::new(DateArgs { date: date_end }),
|
||||
})?
|
||||
.downcast_ref()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Sum balances
|
||||
let mut balances = BalancesAt {
|
||||
balances: opening_balances_at.balances.clone(),
|
||||
};
|
||||
|
||||
for transaction in transactions.iter() {
|
||||
for posting in transaction.postings.iter() {
|
||||
// FIXME: Do currency conversion
|
||||
let running_balance =
|
||||
balances.balances.get(&posting.account).unwrap_or(&0) + posting.quantity;
|
||||
balances
|
||||
.balances
|
||||
.insert(posting.account.clone(), running_balance);
|
||||
}
|
||||
}
|
||||
|
||||
// Store result
|
||||
products.insert(
|
||||
ReportingProductId {
|
||||
name: self.step_name,
|
||||
kind: ReportingProductKind::BalancesBetween,
|
||||
args: Box::new(self.args.clone()),
|
||||
},
|
||||
Box::new(balances),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This dynamic builder automatically generates a [BalancesBetween] from a step which generates [Transactions] from [BalancesBetween]
|
||||
#[derive(Debug)]
|
||||
pub struct UpdateBalancesBetween {
|
||||
step_name: &'static str,
|
||||
@ -383,8 +580,6 @@ pub struct UpdateBalancesBetween {
|
||||
}
|
||||
|
||||
impl UpdateBalancesBetween {
|
||||
// Implements (BalancesBetween -> Transactions) -> BalancesBetween
|
||||
|
||||
fn register_dynamic_builder(context: &mut ReportingContext) {
|
||||
context.register_dynamic_builder(ReportingStepDynamicBuilder {
|
||||
name: "UpdateBalancesBetween",
|
||||
@ -419,23 +614,6 @@ impl UpdateBalancesBetween {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check lookup or builder - with args
|
||||
/*match has_step_or_can_build(
|
||||
&ReportingProductId {
|
||||
name,
|
||||
kind: ReportingProductKind::Transactions,
|
||||
args: args.clone(),
|
||||
},
|
||||
steps,
|
||||
dependencies,
|
||||
context,
|
||||
) {
|
||||
HasStepOrCanBuild::HasStep(step) => unreachable!(),
|
||||
HasStepOrCanBuild::CanLookup(_)
|
||||
| HasStepOrCanBuild::CanBuild(_)
|
||||
| HasStepOrCanBuild::None => {}
|
||||
}*/
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -493,8 +671,77 @@ impl ReportingStep for UpdateBalancesBetween {
|
||||
ReportingProductId {
|
||||
name: self.step_name,
|
||||
kind: ReportingProductKind::Transactions,
|
||||
args: parent_step.id().args.clone(),
|
||||
args: parent_step.id().args,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
steps: &Vec<Box<dyn ReportingStep>>,
|
||||
dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
// Look up the parent step, so we can extract the appropriate args
|
||||
let parent_step = steps
|
||||
.iter()
|
||||
.find(|s| {
|
||||
s.id().name == self.step_name
|
||||
&& s.id()
|
||||
.product_kinds
|
||||
.contains(&ReportingProductKind::Transactions)
|
||||
})
|
||||
.unwrap(); // Existence is checked in can_build
|
||||
|
||||
// Get transactions
|
||||
let transactions = &products
|
||||
.get_or_err(&ReportingProductId {
|
||||
name: self.step_name,
|
||||
kind: ReportingProductKind::Transactions,
|
||||
args: parent_step.id().args,
|
||||
})?
|
||||
.downcast_ref::<Transactions>()
|
||||
.unwrap()
|
||||
.transactions;
|
||||
|
||||
// Look up the BalancesBetween step
|
||||
let dependencies_for_step = dependencies.dependencies_for_step(&parent_step.id());
|
||||
let balances_between_product = &dependencies_for_step[0].product; // Existence and uniqueness is checked in can_build
|
||||
|
||||
// Get opening balances
|
||||
let opening_balances = &products
|
||||
.get_or_err(balances_between_product)?
|
||||
.downcast_ref::<BalancesBetween>()
|
||||
.unwrap()
|
||||
.balances;
|
||||
|
||||
// Sum balances
|
||||
let mut balances = BalancesBetween {
|
||||
balances: opening_balances.clone(),
|
||||
};
|
||||
|
||||
for transaction in transactions.iter() {
|
||||
for posting in transaction.postings.iter() {
|
||||
// FIXME: Do currency conversion
|
||||
let running_balance =
|
||||
balances.balances.get(&posting.account).unwrap_or(&0) + posting.quantity;
|
||||
balances
|
||||
.balances
|
||||
.insert(posting.account.clone(), running_balance);
|
||||
}
|
||||
}
|
||||
|
||||
// Store result
|
||||
products.insert(
|
||||
ReportingProductId {
|
||||
name: self.step_name,
|
||||
kind: ReportingProductKind::BalancesBetween,
|
||||
args: Box::new(self.args.clone()),
|
||||
},
|
||||
Box::new(balances),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ fn would_be_ready_to_execute(
|
||||
pub fn steps_for_targets(
|
||||
targets: Vec<Box<dyn ReportingStep>>,
|
||||
context: &ReportingContext,
|
||||
) -> Result<Vec<Box<dyn ReportingStep>>, ReportingCalculationError> {
|
||||
) -> Result<(Vec<Box<dyn ReportingStep>>, ReportingGraphDependencies), ReportingCalculationError> {
|
||||
let mut steps: Vec<Box<dyn ReportingStep>> = Vec::new();
|
||||
let mut dependencies = ReportingGraphDependencies { vec: Vec::new() };
|
||||
|
||||
@ -319,5 +319,5 @@ pub fn steps_for_targets(
|
||||
.map(|(s, _idx)| s)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(sorted_steps)
|
||||
Ok((sorted_steps, dependencies))
|
||||
}
|
||||
|
@ -16,21 +16,22 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use super::types::{ReportingContext, ReportingProducts, ReportingStep};
|
||||
use super::{calculator::ReportingGraphDependencies, types::{ReportingContext, ReportingProducts, ReportingStep}};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ReportingExecutionError {
|
||||
message: String,
|
||||
pub enum ReportingExecutionError {
|
||||
DependencyNotAvailable { message: String }
|
||||
}
|
||||
|
||||
pub fn execute_steps(
|
||||
steps: Vec<Box<dyn ReportingStep>>,
|
||||
dependencies: ReportingGraphDependencies,
|
||||
context: &ReportingContext,
|
||||
) -> Result<ReportingProducts, ReportingExecutionError> {
|
||||
let mut products = ReportingProducts::new();
|
||||
|
||||
for step in steps {
|
||||
step.execute(context, &mut products)?;
|
||||
for step in steps.iter() {
|
||||
step.execute(context, &steps, &dependencies, &mut products)?;
|
||||
}
|
||||
|
||||
Ok(products)
|
||||
|
@ -49,10 +49,10 @@ pub fn generate_report(
|
||||
context: &ReportingContext,
|
||||
) -> Result<ReportingProducts, ReportingError> {
|
||||
// Solve dependencies
|
||||
let sorted_steps = steps_for_targets(targets, context)?;
|
||||
let (sorted_steps, dependencies) = steps_for_targets(targets, context)?;
|
||||
|
||||
// Execute steps
|
||||
let products = execute_steps(sorted_steps, context)?;
|
||||
let products = execute_steps(sorted_steps, dependencies, context)?;
|
||||
|
||||
Ok(products)
|
||||
}
|
||||
|
@ -16,21 +16,21 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! This module contains concrete [ReportingStep] implementations
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
|
||||
use chrono::Datelike;
|
||||
|
||||
use crate::reporting::types::{
|
||||
BalancesAt, DateStartDateEndArgs, ReportingProduct, ReportingProductId,
|
||||
};
|
||||
use crate::reporting::types::{BalancesAt, DateStartDateEndArgs, ReportingProductId, Transactions};
|
||||
use crate::util::sofy_from_eofy;
|
||||
|
||||
use super::calculator::ReportingGraphDependencies;
|
||||
use super::executor::ReportingExecutionError;
|
||||
use super::types::{
|
||||
DateArgs, ReportingContext, ReportingProductKind, ReportingProducts, ReportingStep,
|
||||
ReportingStepArgs, ReportingStepId, VoidArgs,
|
||||
BalancesBetween, DateArgs, ReportingContext, ReportingProduct, ReportingProductKind,
|
||||
ReportingProducts, ReportingStep, ReportingStepArgs, ReportingStepId, VoidArgs,
|
||||
};
|
||||
|
||||
/// Call [ReportingContext::register_lookup_fn] for all steps provided by this module
|
||||
@ -96,6 +96,64 @@ impl ReportingStep for AllTransactionsExceptRetainedEarnings {
|
||||
args: self.args.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
// Get all dependencies
|
||||
let step_dependencies = dependencies.dependencies_for_step(&self.id());
|
||||
|
||||
// Identify the product_kinds dependency most recently generated
|
||||
if self.product_kinds.len() != 1 {
|
||||
panic!("AllTransactionsExceptRetainedEarnings.product_kinds.len() != 1");
|
||||
}
|
||||
let product_kind = self.product_kinds[0];
|
||||
|
||||
for (product_id, product) in products.map().iter().rev() {
|
||||
if step_dependencies.iter().any(|d| d.product == *product_id) {
|
||||
// Store the result
|
||||
products.insert(
|
||||
ReportingProductId {
|
||||
name: self.id().name,
|
||||
kind: product_kind,
|
||||
args: self.args.clone(),
|
||||
},
|
||||
product.clone(),
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// No dependencies?! - store empty result
|
||||
let product: Box<dyn ReportingProduct> = match self.product_kinds[0] {
|
||||
ReportingProductKind::Transactions => Box::new(Transactions {
|
||||
transactions: Vec::new(),
|
||||
}),
|
||||
ReportingProductKind::BalancesAt => Box::new(BalancesAt {
|
||||
balances: HashMap::new(),
|
||||
}),
|
||||
ReportingProductKind::BalancesBetween => Box::new(BalancesBetween {
|
||||
balances: HashMap::new(),
|
||||
}),
|
||||
ReportingProductKind::Generic => panic!("Requested AllTransactionsExceptRetainedEarnings.Generic but no available dependencies to provide it"),
|
||||
};
|
||||
|
||||
products.insert(
|
||||
ReportingProductId {
|
||||
name: self.id().name,
|
||||
kind: product_kind,
|
||||
args: self.args.clone(),
|
||||
},
|
||||
product,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -155,6 +213,62 @@ impl ReportingStep for AllTransactionsIncludingRetainedEarnings {
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
_dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
// Get opening balances from AllTransactionsExceptRetainedEarnings
|
||||
let opening_balances = products
|
||||
.get_or_err(&ReportingProductId {
|
||||
name: "AllTransactionsExceptRetainedEarnings",
|
||||
kind: ReportingProductKind::BalancesAt,
|
||||
args: Box::new(self.args.clone()),
|
||||
})?
|
||||
.downcast_ref::<BalancesAt>()
|
||||
.unwrap();
|
||||
|
||||
// Get RetainedEarningsToEquity transactions
|
||||
let transactions = products
|
||||
.get_or_err(&ReportingProductId {
|
||||
name: "RetainedEarningsToEquity",
|
||||
kind: ReportingProductKind::Transactions,
|
||||
args: Box::new(self.args.clone()),
|
||||
})?
|
||||
.downcast_ref::<Transactions>()
|
||||
.unwrap();
|
||||
|
||||
// Update balances
|
||||
let mut balances = BalancesAt {
|
||||
balances: opening_balances.balances.clone(),
|
||||
};
|
||||
|
||||
for transaction in transactions.transactions.iter() {
|
||||
for posting in transaction.postings.iter() {
|
||||
// FIXME: Do currency conversion
|
||||
let running_balance =
|
||||
balances.balances.get(&posting.account).unwrap_or(&0) + posting.quantity;
|
||||
balances
|
||||
.balances
|
||||
.insert(posting.account.clone(), running_balance);
|
||||
}
|
||||
}
|
||||
|
||||
// Store result
|
||||
products.insert(
|
||||
ReportingProductId {
|
||||
name: self.id().name,
|
||||
kind: ReportingProductKind::BalancesAt,
|
||||
args: Box::new(self.args.clone()),
|
||||
},
|
||||
Box::new(balances),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -226,6 +340,31 @@ impl ReportingStep for CalculateIncomeTax {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
_dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
eprintln!("Stub: CalculateIncomeTax.execute");
|
||||
|
||||
let transactions = Transactions {
|
||||
transactions: Vec::new(),
|
||||
};
|
||||
|
||||
products.insert(
|
||||
ReportingProductId {
|
||||
name: self.id().name,
|
||||
kind: ReportingProductKind::Transactions,
|
||||
args: Box::new(VoidArgs {}),
|
||||
},
|
||||
Box::new(transactions),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -285,6 +424,44 @@ impl ReportingStep for CombineOrdinaryTransactions {
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
// Sum balances of all dependencies
|
||||
|
||||
let mut balances = BalancesAt {
|
||||
balances: HashMap::new(),
|
||||
};
|
||||
|
||||
for dependency in dependencies.dependencies_for_step(&self.id()) {
|
||||
let dependency_balances = &products
|
||||
.get_or_err(&dependency.product)?
|
||||
.downcast_ref::<BalancesAt>()
|
||||
.unwrap()
|
||||
.balances;
|
||||
for (account, balance) in dependency_balances.iter() {
|
||||
let running_balance = balances.balances.get(account).unwrap_or(&0) + balance;
|
||||
balances.balances.insert(account.clone(), running_balance);
|
||||
}
|
||||
}
|
||||
|
||||
// Store result
|
||||
products.insert(
|
||||
ReportingProductId {
|
||||
name: self.id().name,
|
||||
kind: ReportingProductKind::BalancesAt,
|
||||
args: Box::new(self.args.clone()),
|
||||
},
|
||||
Box::new(balances),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -331,6 +508,8 @@ impl ReportingStep for DBBalances {
|
||||
fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
_dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
eprintln!("Stub: DBBalances.execute");
|
||||
@ -345,7 +524,7 @@ impl ReportingStep for DBBalances {
|
||||
kind: ReportingProductKind::BalancesAt,
|
||||
args: Box::new(self.args.clone()),
|
||||
},
|
||||
ReportingProduct::BalancesAt(balances),
|
||||
Box::new(balances),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@ -392,6 +571,31 @@ impl ReportingStep for PostUnreconciledStatementLines {
|
||||
args: Box::new(self.args.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
_dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
eprintln!("Stub: PostUnreconciledStatementLines.execute");
|
||||
|
||||
let transactions = Transactions {
|
||||
transactions: Vec::new(),
|
||||
};
|
||||
|
||||
products.insert(
|
||||
ReportingProductId {
|
||||
name: self.id().name,
|
||||
kind: ReportingProductKind::Transactions,
|
||||
args: Box::new(self.args.clone()),
|
||||
},
|
||||
Box::new(transactions),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -448,4 +652,29 @@ impl ReportingStep for RetainedEarningsToEquity {
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
_context: &ReportingContext,
|
||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
||||
_dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
eprintln!("Stub: RetainedEarningsToEquity.execute");
|
||||
|
||||
let transactions = Transactions {
|
||||
transactions: Vec::new(),
|
||||
};
|
||||
|
||||
products.insert(
|
||||
ReportingProductId {
|
||||
name: self.id().name,
|
||||
kind: ReportingProductKind::Transactions,
|
||||
args: Box::new(self.args.clone()),
|
||||
},
|
||||
Box::new(transactions),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,9 @@ use downcast_rs::Downcast;
|
||||
use dyn_clone::DynClone;
|
||||
use dyn_eq::DynEq;
|
||||
use dyn_hash::DynHash;
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use crate::transaction::TransactionWithPostings;
|
||||
use crate::QuantityInt;
|
||||
|
||||
use super::calculator::ReportingGraphDependencies;
|
||||
@ -121,7 +123,7 @@ pub struct ReportingStepDynamicBuilder {
|
||||
// REPORTING PRODUCTS
|
||||
|
||||
/// Identifies a [ReportingProduct]
|
||||
#[derive(Debug, Eq, Hash, PartialEq)]
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct ReportingProductId {
|
||||
pub name: &'static str,
|
||||
pub kind: ReportingProductKind,
|
||||
@ -144,33 +146,84 @@ pub enum ReportingProductKind {
|
||||
}
|
||||
|
||||
/// Represents the result of a [ReportingStep]
|
||||
#[derive(Debug)]
|
||||
pub enum ReportingProduct {
|
||||
Transactions(Transactions),
|
||||
BalancesAt(BalancesAt),
|
||||
BalancesBetween(BalancesBetween),
|
||||
Generic(Box<dyn GenericReportingProduct>),
|
||||
}
|
||||
pub trait ReportingProduct: Debug + Downcast + DynClone {}
|
||||
|
||||
downcast_rs::impl_downcast!(ReportingProduct);
|
||||
dyn_clone::clone_trait_object!(ReportingProduct);
|
||||
|
||||
/// Records a list of transactions generated by a [ReportingStep]
|
||||
#[derive(Debug)]
|
||||
pub struct Transactions {}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Transactions {
|
||||
pub transactions: Vec<TransactionWithPostings>,
|
||||
}
|
||||
|
||||
impl ReportingProduct for Transactions {}
|
||||
|
||||
/// Records cumulative account balances at a particular point in time
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BalancesAt {
|
||||
pub balances: HashMap<String, QuantityInt>,
|
||||
}
|
||||
|
||||
impl ReportingProduct for BalancesAt {}
|
||||
|
||||
/// Records the total value of transactions in each account between two points in time
|
||||
#[derive(Debug)]
|
||||
pub struct BalancesBetween {}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BalancesBetween {
|
||||
pub balances: HashMap<String, QuantityInt>,
|
||||
}
|
||||
|
||||
impl ReportingProduct for BalancesBetween {}
|
||||
|
||||
/// Represents a custom [ReportingProduct] generated by a [ReportingStep]
|
||||
pub trait GenericReportingProduct: Debug {}
|
||||
pub trait GenericReportingProduct: Debug + ReportingProduct {}
|
||||
|
||||
/// Convenience type mapping [ReportingProductId] to [ReportingProduct]
|
||||
pub type ReportingProducts = HashMap<ReportingProductId, ReportingProduct>;
|
||||
/// Map from [ReportingProductId] to [ReportingProduct]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ReportingProducts {
|
||||
map: IndexMap<ReportingProductId, Box<dyn ReportingProduct>>,
|
||||
}
|
||||
|
||||
impl ReportingProducts {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: IndexMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map(&self) -> &IndexMap<ReportingProductId, Box<dyn ReportingProduct>> {
|
||||
&self.map
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, key: ReportingProductId, value: Box<dyn ReportingProduct>) {
|
||||
self.map.insert(key, value);
|
||||
}
|
||||
|
||||
pub fn get_or_err(
|
||||
&self,
|
||||
key: &ReportingProductId,
|
||||
) -> Result<&Box<dyn ReportingProduct>, ReportingExecutionError> {
|
||||
match self.map.get(key) {
|
||||
Some(value) => Ok(value),
|
||||
None => Err(ReportingExecutionError::DependencyNotAvailable {
|
||||
message: format!("Product {} not available when expected", key),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ReportingProducts {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!(
|
||||
"ReportingProducts {{\n{}\n}}",
|
||||
self.map
|
||||
.iter()
|
||||
.map(|(k, v)| format!(" {}: {:?}", k, v))
|
||||
.collect::<Vec<_>>()
|
||||
.join(",\n")
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------
|
||||
// REPORTING STEPS
|
||||
@ -230,6 +283,8 @@ pub trait ReportingStep: Debug + Display + Downcast {
|
||||
fn execute(
|
||||
&self,
|
||||
context: &ReportingContext,
|
||||
steps: &Vec<Box<dyn ReportingStep>>,
|
||||
dependencies: &ReportingGraphDependencies,
|
||||
products: &mut ReportingProducts,
|
||||
) -> Result<(), ReportingExecutionError> {
|
||||
todo!("{}", self);
|
||||
|
@ -18,8 +18,27 @@
|
||||
|
||||
use chrono::NaiveDateTime;
|
||||
|
||||
use crate::QuantityInt;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Transaction {
|
||||
pub id: Option<u64>,
|
||||
pub dt: NaiveDateTime,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TransactionWithPostings {
|
||||
pub transaction: Transaction,
|
||||
pub postings: Vec<Posting>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Posting {
|
||||
pub id: Option<u64>,
|
||||
pub transaction_id: Option<u64>,
|
||||
pub description: String,
|
||||
pub account: String,
|
||||
pub quantity: QuantityInt,
|
||||
pub commodity: String,
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user