Implement GenerateBalances dynamic builder

This commit is contained in:
RunasSudo 2025-05-21 17:11:20 +10:00
parent a7e9c74dd1
commit 4add6292a1
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
4 changed files with 198 additions and 45 deletions

View File

@ -29,6 +29,12 @@ pub fn register_dynamic_builders(context: &mut ReportingContext) {
build: BalancesAtToBalancesBetween::build,
});
context.register_dynamic_builder(ReportingStepDynamicBuilder {
name: "GenerateBalances",
can_build: GenerateBalances::can_build,
build: GenerateBalances::build,
});
context.register_dynamic_builder(ReportingStepDynamicBuilder {
name: "UpdateBalancesBetween",
can_build: UpdateBalancesBetween::can_build,
@ -55,11 +61,15 @@ impl BalancesAtToBalancesBetween {
) -> bool {
// Check for BalancesAt, BalancesAt -> BalancesBetween
if kind == ReportingProductKind::BalancesBetween {
let args = args.downcast_ref::<DateStartDateEndArgs>().unwrap();
match has_step_or_can_build(
&ReportingProductId {
name,
kind: ReportingProductKind::BalancesAt,
args: args.clone(),
args: Box::new(DateArgs {
date: args.date_start.clone(),
}),
},
steps,
dependencies,
@ -100,14 +110,9 @@ impl ReportingStep for BalancesAtToBalancesBetween {
}
}
fn init_graph(
&self,
_steps: &Vec<Box<dyn ReportingStep>>,
dependencies: &mut ReportingGraphDependencies,
) {
fn requires(&self) -> Vec<ReportingProductId> {
// BalancesAtToBalancesBetween depends on BalancesAt at both time points
dependencies.add_dependency(
self.id(),
vec![
ReportingProductId {
name: self.step_name,
kind: ReportingProductKind::BalancesAt,
@ -115,9 +120,6 @@ impl ReportingStep for BalancesAtToBalancesBetween {
date: self.args.date_start.clone(),
}),
},
);
dependencies.add_dependency(
self.id(),
ReportingProductId {
name: self.step_name,
kind: ReportingProductKind::BalancesAt,
@ -125,7 +127,89 @@ impl ReportingStep for BalancesAtToBalancesBetween {
date: self.args.date_end.clone(),
}),
},
);
]
}
}
#[derive(Debug)]
pub struct GenerateBalances {
step_name: &'static str,
args: DateArgs,
}
impl GenerateBalances {
// Implements (() -> Transactions) -> BalancesAt
fn can_build(
name: &'static str,
kind: ReportingProductKind,
args: &Box<dyn ReportingStepArgs>,
steps: &Vec<Box<dyn ReportingStep>>,
dependencies: &ReportingGraphDependencies,
context: &ReportingContext,
) -> bool {
// Check for Transactions -> BalancesAt
if kind == ReportingProductKind::BalancesAt {
match has_step_or_can_build(
&ReportingProductId {
name,
kind: ReportingProductKind::Transactions,
args: args.clone(),
},
steps,
dependencies,
context,
) {
HasStepOrCanBuild::HasStep(step) => {
// Check for () -> Transactions
if dependencies.dependencies_for_step(&step.id()).len() == 0 {
return true;
}
}
HasStepOrCanBuild::CanLookup(lookup_fn) => {
// Check for () -> Transactions
let step = lookup_fn(args.clone());
if step.requires().len() == 0 {
return true;
}
}
HasStepOrCanBuild::CanBuild(_) | HasStepOrCanBuild::None => {}
}
}
return false;
}
fn build(
name: &'static str,
_kind: ReportingProductKind,
args: Box<dyn ReportingStepArgs>,
_steps: &Vec<Box<dyn ReportingStep>>,
_dependencies: &ReportingGraphDependencies,
_context: &ReportingContext,
) -> Box<dyn ReportingStep> {
Box::new(GenerateBalances {
step_name: name,
args: *args.downcast().unwrap(),
})
}
}
impl ReportingStep for GenerateBalances {
fn id(&self) -> ReportingStepId {
ReportingStepId {
name: self.step_name,
product_kinds: &[ReportingProductKind::BalancesAt],
args: Box::new(self.args.clone()),
}
}
fn requires(&self) -> Vec<ReportingProductId> {
// GenerateBalances depends on Transactions
vec![ReportingProductId {
name: self.step_name,
kind: ReportingProductKind::Transactions,
args: Box::new(self.args.clone()),
}]
}
}

View File

@ -18,7 +18,7 @@
use super::{
ReportingContext, ReportingProductId, ReportingProductKind, ReportingStep,
ReportingStepDynamicBuilder, ReportingStepId, ReportingStepLookupFn,
ReportingStepDynamicBuilder, ReportingStepFromArgsFn, ReportingStepId,
};
#[derive(Debug)]
@ -80,7 +80,7 @@ pub enum ReportingCalculationError {
pub enum HasStepOrCanBuild<'a, 'b> {
HasStep(&'a Box<dyn ReportingStep>),
CanLookup(ReportingStepLookupFn),
CanLookup(ReportingStepFromArgsFn),
CanBuild(&'b ReportingStepDynamicBuilder),
None,
}
@ -105,7 +105,10 @@ pub fn has_step_or_can_build<'a, 'b>(
.keys()
.find(|(name, kinds)| *name == product.name && kinds.contains(&product.kind))
{
return HasStepOrCanBuild::CanLookup(*context.step_lookup_fn.get(lookup_key).unwrap());
let (takes_args_fn, from_args_fn) = context.step_lookup_fn.get(lookup_key).unwrap();
if takes_args_fn(&product.args) {
return HasStepOrCanBuild::CanLookup(*from_args_fn);
}
}
// No explicit step for product - try builders
@ -173,6 +176,9 @@ pub fn solve_for(
for target in targets {
steps.push(target);
let target = steps.last().unwrap();
for dependency in target.requires() {
dependencies.add_dependency(target.id(), dependency);
}
target.as_ref().init_graph(&steps, &mut dependencies);
}
@ -200,7 +206,7 @@ pub fn solve_for(
*name == dependency.dependency.name
&& kinds.contains(&dependency.dependency.kind)
}) {
let lookup_fn = context.step_lookup_fn.get(lookup_key).unwrap();
let (_, lookup_fn) = context.step_lookup_fn.get(lookup_key).unwrap();
let new_step = lookup_fn(dependency.dependency.args.clone());
// Check new step meets the dependency
@ -255,6 +261,9 @@ pub fn solve_for(
new_step_indexes.push(steps.len());
steps.push(new_step);
let new_step = steps.last().unwrap();
for dependency in new_step.requires() {
dependencies.add_dependency(new_step.id(), dependency);
}
new_step.as_ref().init_graph(&steps, &mut dependencies);
}

View File

@ -31,7 +31,10 @@ pub mod steps;
pub struct ReportingContext {
_eofy_date: NaiveDate,
step_lookup_fn: HashMap<(&'static str, &'static [ReportingProductKind]), ReportingStepLookupFn>,
step_lookup_fn: HashMap<
(&'static str, &'static [ReportingProductKind]),
(ReportingStepTakesArgsFn, ReportingStepFromArgsFn),
>,
step_dynamic_builders: Vec<ReportingStepDynamicBuilder>,
}
@ -48,9 +51,11 @@ impl ReportingContext {
&mut self,
name: &'static str,
product_kinds: &'static [ReportingProductKind],
builder: ReportingStepLookupFn,
takes_args_fn: ReportingStepTakesArgsFn,
from_args_fn: ReportingStepFromArgsFn,
) {
self.step_lookup_fn.insert((name, product_kinds), builder);
self.step_lookup_fn
.insert((name, product_kinds), (takes_args_fn, from_args_fn));
}
fn register_dynamic_builder(&mut self, builder: ReportingStepDynamicBuilder) {
@ -121,18 +126,24 @@ pub trait ReportingStep: Debug + Downcast {
fn id(&self) -> ReportingStepId;
// Methods
fn requires(&self) -> Vec<ReportingProductId> {
vec![]
}
fn init_graph(
&self,
_steps: &Vec<Box<dyn ReportingStep>>,
_dependencies: &mut ReportingGraphDependencies,
) {
}
fn after_init_graph(
&self,
_steps: &Vec<Box<dyn ReportingStep>>,
_dependencies: &mut ReportingGraphDependencies,
) {
}
//fn execute(&self, _context: &ReportingContext, _products: &mut ReportingProducts) {
// todo!();
//}
@ -146,7 +157,8 @@ downcast_rs::impl_downcast!(ReportingStepArgs);
dyn_clone::clone_trait_object!(ReportingStepArgs);
dyn_eq::eq_trait_object!(ReportingStepArgs);
pub type ReportingStepLookupFn = fn(args: Box<dyn ReportingStepArgs>) -> Box<dyn ReportingStep>;
pub type ReportingStepTakesArgsFn = fn(args: &Box<dyn ReportingStepArgs>) -> bool;
pub type ReportingStepFromArgsFn = fn(args: Box<dyn ReportingStepArgs>) -> Box<dyn ReportingStep>;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DateArgs {

View File

@ -28,26 +28,37 @@ pub fn register_lookup_fns(context: &mut ReportingContext) {
context.register_lookup_fn(
"AllTransactionsExceptRetainedEarnings",
&[ReportingProductKind::BalancesBetween],
AllTransactionsExceptRetainedEarnings::takes_args,
AllTransactionsExceptRetainedEarnings::from_args,
);
context.register_lookup_fn(
"CalculateIncomeTax",
&[ReportingProductKind::Transactions],
CalculateIncomeTax::takes_args,
CalculateIncomeTax::from_args,
);
context.register_lookup_fn(
"CombineOrdinaryTransactions",
&[ReportingProductKind::BalancesAt],
CombineOrdinaryTransactions::takes_args,
CombineOrdinaryTransactions::from_args,
);
context.register_lookup_fn(
"DBBalances",
&[ReportingProductKind::BalancesAt],
DBBalances::takes_args,
DBBalances::from_args,
);
context.register_lookup_fn(
"PostUnreconciledStatementLines",
&[ReportingProductKind::Transactions],
PostUnreconciledStatementLines::takes_args,
PostUnreconciledStatementLines::from_args,
);
}
#[derive(Debug)]
@ -56,6 +67,10 @@ pub struct AllTransactionsExceptRetainedEarnings {
}
impl AllTransactionsExceptRetainedEarnings {
fn takes_args(args: &Box<dyn ReportingStepArgs>) -> bool {
args.is::<DateStartDateEndArgs>()
}
fn from_args(args: Box<dyn ReportingStepArgs>) -> Box<dyn ReportingStep> {
Box::new(AllTransactionsExceptRetainedEarnings {
args: *args.downcast().unwrap(),
@ -79,6 +94,10 @@ pub struct CalculateIncomeTax {
}
impl CalculateIncomeTax {
fn takes_args(args: &Box<dyn ReportingStepArgs>) -> bool {
args.is::<DateEofyArgs>()
}
fn from_args(args: Box<dyn ReportingStepArgs>) -> Box<dyn ReportingStep> {
Box::new(CalculateIncomeTax {
args: *args.downcast().unwrap(),
@ -95,23 +114,16 @@ impl ReportingStep for CalculateIncomeTax {
}
}
fn init_graph(
&self,
_steps: &Vec<Box<dyn ReportingStep>>,
dependencies: &mut ReportingGraphDependencies,
) {
fn requires(&self) -> Vec<ReportingProductId> {
// CalculateIncomeTax depends on CombineOrdinaryTransactions
dependencies.add_dependency(
self.id(),
ReportingProductId {
name: "CombineOrdinaryTransactions",
kind: ReportingProductKind::BalancesBetween,
args: Box::new(DateStartDateEndArgs {
date_start: sofy_from_eofy(self.args.date_eofy),
date_end: self.args.date_eofy.clone(),
}),
},
);
vec![ReportingProductId {
name: "CombineOrdinaryTransactions",
kind: ReportingProductKind::BalancesBetween,
args: Box::new(DateStartDateEndArgs {
date_start: sofy_from_eofy(self.args.date_eofy),
date_end: self.args.date_eofy.clone(),
}),
}]
}
fn after_init_graph(
@ -138,6 +150,10 @@ pub struct CombineOrdinaryTransactions {
}
impl CombineOrdinaryTransactions {
fn takes_args(args: &Box<dyn ReportingStepArgs>) -> bool {
args.is::<DateArgs>()
}
fn from_args(args: Box<dyn ReportingStepArgs>) -> Box<dyn ReportingStep> {
Box::new(CombineOrdinaryTransactions {
args: *args.downcast().unwrap(),
@ -154,20 +170,21 @@ impl ReportingStep for CombineOrdinaryTransactions {
}
}
fn init_graph(
&self,
_steps: &Vec<Box<dyn ReportingStep>>,
dependencies: &mut ReportingGraphDependencies,
) {
// CombineOrdinaryTransactions depends on DBBalances
dependencies.add_dependency(
self.id(),
fn requires(&self) -> Vec<ReportingProductId> {
vec![
// CombineOrdinaryTransactions depends on DBBalances
ReportingProductId {
name: "DBBalances",
kind: ReportingProductKind::BalancesAt,
args: Box::new(self.args.clone()),
},
);
// CombineOrdinaryTransactions depends on PostUnreconciledStatementLines
ReportingProductId {
name: "PostUnreconciledStatementLines",
kind: ReportingProductKind::BalancesAt,
args: Box::new(self.args.clone()),
},
]
}
}
@ -177,6 +194,10 @@ pub struct DBBalances {
}
impl DBBalances {
fn takes_args(args: &Box<dyn ReportingStepArgs>) -> bool {
args.is::<DateArgs>()
}
fn from_args(args: Box<dyn ReportingStepArgs>) -> Box<dyn ReportingStep> {
Box::new(DBBalances {
args: *args.downcast().unwrap(),
@ -193,3 +214,30 @@ impl ReportingStep for DBBalances {
}
}
}
#[derive(Debug)]
pub struct PostUnreconciledStatementLines {
pub args: DateArgs,
}
impl PostUnreconciledStatementLines {
fn takes_args(args: &Box<dyn ReportingStepArgs>) -> bool {
args.is::<DateArgs>()
}
fn from_args(args: Box<dyn ReportingStepArgs>) -> Box<dyn ReportingStep> {
Box::new(PostUnreconciledStatementLines {
args: *args.downcast().unwrap(),
})
}
}
impl ReportingStep for PostUnreconciledStatementLines {
fn id(&self) -> ReportingStepId {
ReportingStepId {
name: "PostUnreconciledStatementLines",
product_kinds: &[ReportingProductKind::Transactions],
args: Box::new(self.args.clone()),
}
}
}