Refactoring and documentation
This commit is contained in:
parent
d422e83275
commit
9e34360abf
14
src/main.rs
14
src/main.rs
@ -17,13 +17,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use libdrcr::reporting::{
|
use libdrcr::reporting::builders::register_dynamic_builders;
|
||||||
builders::register_dynamic_builders,
|
use libdrcr::reporting::calculator::steps_for_targets;
|
||||||
calculator::solve_for,
|
use libdrcr::reporting::steps::{
|
||||||
steps::{
|
|
||||||
register_lookup_fns, AllTransactionsExceptRetainedEarnings,
|
register_lookup_fns, AllTransactionsExceptRetainedEarnings,
|
||||||
AllTransactionsIncludingRetainedEarnings, CalculateIncomeTax,
|
AllTransactionsIncludingRetainedEarnings, CalculateIncomeTax,
|
||||||
},
|
};
|
||||||
|
use libdrcr::reporting::types::{
|
||||||
DateArgs, DateStartDateEndArgs, ReportingContext, ReportingProductKind, ReportingStep,
|
DateArgs, DateStartDateEndArgs, ReportingContext, ReportingProductKind, ReportingStep,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ fn main() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
println!("For income statement:");
|
println!("For income statement:");
|
||||||
match solve_for(targets, context) {
|
match steps_for_targets(targets, context) {
|
||||||
Ok(steps) => {
|
Ok(steps) => {
|
||||||
for step in steps {
|
for step in steps {
|
||||||
println!("- {}", step);
|
println!("- {}", step);
|
||||||
@ -67,7 +67,7 @@ fn main() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
println!("For balance sheet:");
|
println!("For balance sheet:");
|
||||||
match solve_for(targets, context) {
|
match steps_for_targets(targets, context) {
|
||||||
Ok(steps) => {
|
Ok(steps) => {
|
||||||
for step in steps {
|
for step in steps {
|
||||||
println!("- {}", step);
|
println!("- {}", step);
|
||||||
|
@ -18,8 +18,8 @@
|
|||||||
|
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use super::{
|
use super::calculator::{has_step_or_can_build, HasStepOrCanBuild, ReportingGraphDependencies};
|
||||||
calculator::{has_step_or_can_build, HasStepOrCanBuild, ReportingGraphDependencies},
|
use super::types::{
|
||||||
DateArgs, DateStartDateEndArgs, ReportingContext, ReportingProductId, ReportingProductKind,
|
DateArgs, DateStartDateEndArgs, ReportingContext, ReportingProductId, ReportingProductKind,
|
||||||
ReportingStep, ReportingStepArgs, ReportingStepDynamicBuilder, ReportingStepId,
|
ReportingStep, ReportingStepArgs, ReportingStepDynamicBuilder, ReportingStepId,
|
||||||
};
|
};
|
||||||
|
@ -16,50 +16,37 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::{
|
//! This module implements the dependency resolution for [ReportingStep]s
|
||||||
|
|
||||||
|
use super::types::{
|
||||||
ReportingContext, ReportingProductId, ReportingStep, ReportingStepDynamicBuilder,
|
ReportingContext, ReportingProductId, ReportingStep, ReportingStepDynamicBuilder,
|
||||||
ReportingStepFromArgsFn, ReportingStepId,
|
ReportingStepFromArgsFn, ReportingStepId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// List of dependencies between [ReportingStep]s and [ReportingProduct]s
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ReportingGraphDependencies {
|
pub struct ReportingGraphDependencies {
|
||||||
vec: Vec<Dependency>,
|
vec: Vec<Dependency>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReportingGraphDependencies {
|
impl ReportingGraphDependencies {
|
||||||
|
/// Get the list of [Dependency]s
|
||||||
pub fn vec(&self) -> &Vec<Dependency> {
|
pub fn vec(&self) -> &Vec<Dependency> {
|
||||||
&self.vec
|
&self.vec
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_dependency(&mut self, step: ReportingStepId, dependency: ReportingProductId) {
|
/// Record that the [ReportingStep] depends on the [ReportingProduct]
|
||||||
|
pub fn add_dependency(&mut self, step: ReportingStepId, product: ReportingProductId) {
|
||||||
if !self
|
if !self
|
||||||
.vec
|
.vec
|
||||||
.iter()
|
.iter()
|
||||||
.any(|d| d.step == step && d.product == dependency)
|
.any(|d| d.step == step && d.product == product)
|
||||||
{
|
{
|
||||||
self.vec.push(Dependency { step, product: dependency });
|
self.vec.push(Dependency { step, product });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*pub fn add_target_dependency(&mut self, target: ReportingStepId, dependency: ReportingStepId) {
|
/// Get the [Dependency]s for the given [ReportingStep]
|
||||||
for kind in target.product_kinds {
|
|
||||||
match kind {
|
|
||||||
ReportingProductKind::Transactions | ReportingProductKind::BalancesBetween => {
|
|
||||||
self.add_dependency(
|
|
||||||
target.clone(),
|
|
||||||
ReportingProductId {
|
|
||||||
name: dependency.name,
|
|
||||||
kind: *kind,
|
|
||||||
args: target.args.clone(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
ReportingProductKind::BalancesAt => todo!(),
|
|
||||||
ReportingProductKind::Generic => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
pub fn dependencies_for_step(&self, step: &ReportingStepId) -> Vec<&Dependency> {
|
pub fn dependencies_for_step(&self, step: &ReportingStepId) -> Vec<&Dependency> {
|
||||||
return self.vec.iter().filter(|d| d.step == *step).collect();
|
return self.vec.iter().filter(|d| d.step == *step).collect();
|
||||||
}
|
}
|
||||||
@ -72,6 +59,7 @@ pub struct Dependency {
|
|||||||
pub product: ReportingProductId,
|
pub product: ReportingProductId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Indicates an error during dependency resolution in [steps_for_targets]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ReportingCalculationError {
|
pub enum ReportingCalculationError {
|
||||||
UnknownStep { message: String },
|
UnknownStep { message: String },
|
||||||
@ -86,6 +74,7 @@ pub enum HasStepOrCanBuild<'a, 'b> {
|
|||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determines whether the [ReportingProduct] is generated by a known step, or can be generated by a lookup function or dynamic builder
|
||||||
pub fn has_step_or_can_build<'a, 'b>(
|
pub fn has_step_or_can_build<'a, 'b>(
|
||||||
product: &ReportingProductId,
|
product: &ReportingProductId,
|
||||||
steps: &'a Vec<Box<dyn ReportingStep>>,
|
steps: &'a Vec<Box<dyn ReportingStep>>,
|
||||||
@ -129,23 +118,15 @@ pub fn has_step_or_can_build<'a, 'b>(
|
|||||||
return HasStepOrCanBuild::None;
|
return HasStepOrCanBuild::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check whether the [ReportingStep] would be ready to execute, if the given previous steps have already completed
|
||||||
fn would_be_ready_to_execute(
|
fn would_be_ready_to_execute(
|
||||||
step: &Box<dyn ReportingStep>,
|
step: &Box<dyn ReportingStep>,
|
||||||
steps: &Vec<Box<dyn ReportingStep>>,
|
steps: &Vec<Box<dyn ReportingStep>>,
|
||||||
dependencies: &ReportingGraphDependencies,
|
dependencies: &ReportingGraphDependencies,
|
||||||
previous_steps: &Vec<usize>,
|
previous_steps: &Vec<usize>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
//println!(
|
|
||||||
// "- would_be_ready_to_execute: {}, {:?}",
|
|
||||||
// step.id(),
|
|
||||||
// previous_steps
|
|
||||||
//);
|
|
||||||
|
|
||||||
// Check whether the step would be ready to execute, if the previous steps have already completed
|
|
||||||
'check_each_dependency: for dependency in dependencies.vec.iter() {
|
'check_each_dependency: for dependency in dependencies.vec.iter() {
|
||||||
if dependency.step == step.id() {
|
if dependency.step == step.id() {
|
||||||
//println!("-- {}", dependency.dependency);
|
|
||||||
|
|
||||||
// Check if the dependency has been produced by a previous step
|
// Check if the dependency has been produced by a previous step
|
||||||
for previous_step in previous_steps {
|
for previous_step in previous_steps {
|
||||||
if steps[*previous_step].id().name == dependency.product.name
|
if steps[*previous_step].id().name == dependency.product.name
|
||||||
@ -166,7 +147,8 @@ fn would_be_ready_to_execute(
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn solve_for(
|
/// Recursively resolve the dependencies of the target [ReportingStep]s and return a sorted [Vec] of [ReportingStep]s
|
||||||
|
pub fn steps_for_targets(
|
||||||
targets: Vec<Box<dyn ReportingStep>>,
|
targets: Vec<Box<dyn ReportingStep>>,
|
||||||
context: ReportingContext,
|
context: ReportingContext,
|
||||||
) -> Result<Vec<Box<dyn ReportingStep>>, ReportingCalculationError> {
|
) -> Result<Vec<Box<dyn ReportingStep>>, ReportingCalculationError> {
|
||||||
@ -180,12 +162,15 @@ pub fn solve_for(
|
|||||||
for dependency in target.requires(&context) {
|
for dependency in target.requires(&context) {
|
||||||
dependencies.add_dependency(target.id(), dependency);
|
dependencies.add_dependency(target.id(), dependency);
|
||||||
}
|
}
|
||||||
target.as_ref().init_graph(&steps, &mut dependencies, &context);
|
target
|
||||||
|
.as_ref()
|
||||||
|
.init_graph(&steps, &mut dependencies, &context);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call after_init_graph on targets
|
// Call after_init_graph on targets
|
||||||
for step in steps.iter() {
|
for step in steps.iter() {
|
||||||
step.as_ref().after_init_graph(&steps, &mut dependencies, &context);
|
step.as_ref()
|
||||||
|
.after_init_graph(&steps, &mut dependencies, &context);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process dependencies
|
// Process dependencies
|
||||||
@ -204,8 +189,7 @@ pub fn solve_for(
|
|||||||
}) {
|
}) {
|
||||||
// Try lookup function
|
// Try lookup function
|
||||||
if let Some(lookup_key) = context.step_lookup_fn.keys().find(|(name, kinds)| {
|
if let Some(lookup_key) = context.step_lookup_fn.keys().find(|(name, kinds)| {
|
||||||
*name == dependency.product.name
|
*name == dependency.product.name && kinds.contains(&dependency.product.kind)
|
||||||
&& kinds.contains(&dependency.product.kind)
|
|
||||||
}) {
|
}) {
|
||||||
let (takes_args_fn, from_args_fn) =
|
let (takes_args_fn, from_args_fn) =
|
||||||
context.step_lookup_fn.get(lookup_key).unwrap();
|
context.step_lookup_fn.get(lookup_key).unwrap();
|
||||||
@ -269,12 +253,15 @@ pub fn solve_for(
|
|||||||
for dependency in new_step.requires(&context) {
|
for dependency in new_step.requires(&context) {
|
||||||
dependencies.add_dependency(new_step.id(), dependency);
|
dependencies.add_dependency(new_step.id(), dependency);
|
||||||
}
|
}
|
||||||
new_step.as_ref().init_graph(&steps, &mut dependencies, &context);
|
new_step
|
||||||
|
.as_ref()
|
||||||
|
.init_graph(&steps, &mut dependencies, &context);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call after_init_graph on all steps
|
// Call after_init_graph on all steps
|
||||||
for step in steps.iter() {
|
for step in steps.iter() {
|
||||||
step.as_ref().after_init_graph(&steps, &mut dependencies, &context);
|
step.as_ref()
|
||||||
|
.after_init_graph(&steps, &mut dependencies, &context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,206 +16,7 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::{collections::HashMap, fmt::Display};
|
|
||||||
|
|
||||||
use calculator::ReportingGraphDependencies;
|
|
||||||
use chrono::NaiveDate;
|
|
||||||
use downcast_rs::Downcast;
|
|
||||||
use dyn_clone::DynClone;
|
|
||||||
use dyn_eq::DynEq;
|
|
||||||
|
|
||||||
pub mod builders;
|
pub mod builders;
|
||||||
pub mod calculator;
|
pub mod calculator;
|
||||||
pub mod steps;
|
pub mod steps;
|
||||||
|
pub mod types;
|
||||||
pub struct ReportingContext {
|
|
||||||
eofy_date: NaiveDate,
|
|
||||||
step_lookup_fn: HashMap<
|
|
||||||
(&'static str, &'static [ReportingProductKind]),
|
|
||||||
(ReportingStepTakesArgsFn, ReportingStepFromArgsFn),
|
|
||||||
>,
|
|
||||||
step_dynamic_builders: Vec<ReportingStepDynamicBuilder>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ReportingContext {
|
|
||||||
pub fn new(eofy_date: NaiveDate) -> Self {
|
|
||||||
Self {
|
|
||||||
eofy_date: eofy_date,
|
|
||||||
step_lookup_fn: HashMap::new(),
|
|
||||||
step_dynamic_builders: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_lookup_fn(
|
|
||||||
&mut self,
|
|
||||||
name: &'static str,
|
|
||||||
product_kinds: &'static [ReportingProductKind],
|
|
||||||
takes_args_fn: ReportingStepTakesArgsFn,
|
|
||||||
from_args_fn: ReportingStepFromArgsFn,
|
|
||||||
) {
|
|
||||||
self.step_lookup_fn
|
|
||||||
.insert((name, product_kinds), (takes_args_fn, from_args_fn));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_dynamic_builder(&mut self, builder: ReportingStepDynamicBuilder) {
|
|
||||||
if !self
|
|
||||||
.step_dynamic_builders
|
|
||||||
.iter()
|
|
||||||
.any(|b| b.name == builder.name)
|
|
||||||
{
|
|
||||||
self.step_dynamic_builders.push(builder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
|
||||||
pub struct ReportingProductId {
|
|
||||||
name: &'static str,
|
|
||||||
kind: ReportingProductKind,
|
|
||||||
args: Box<dyn ReportingStepArgs>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ReportingProductId {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_fmt(format_args!("{}.{:?}({})", self.name, self.kind, self.args))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
||||||
pub enum ReportingProductKind {
|
|
||||||
Transactions,
|
|
||||||
BalancesAt,
|
|
||||||
BalancesBetween,
|
|
||||||
Generic,
|
|
||||||
}
|
|
||||||
|
|
||||||
//enum ReportingProduct {
|
|
||||||
// Transactions(Transactions),
|
|
||||||
// BalancesAt(BalancesAt),
|
|
||||||
// BalancesBetween(BalancesBetween),
|
|
||||||
// Generic(Box<dyn GenericReportingProduct>),
|
|
||||||
//}
|
|
||||||
|
|
||||||
//struct Transactions {}
|
|
||||||
//struct BalancesAt {}
|
|
||||||
//struct BalancesBetween {}
|
|
||||||
|
|
||||||
//trait GenericReportingProduct {}
|
|
||||||
|
|
||||||
//type ReportingProducts = HashMap<ReportingProductId, ReportingProduct>;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub struct ReportingStepId {
|
|
||||||
pub name: &'static str,
|
|
||||||
pub product_kinds: &'static [ReportingProductKind],
|
|
||||||
pub args: Box<dyn ReportingStepArgs>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ReportingStepId {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_fmt(format_args!(
|
|
||||||
"{}{:?}({})",
|
|
||||||
self.name, self.product_kinds, self.args
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ReportingStep: Debug + Display + Downcast {
|
|
||||||
// Info
|
|
||||||
fn id(&self) -> ReportingStepId;
|
|
||||||
|
|
||||||
// Methods
|
|
||||||
fn requires(&self, _context: &ReportingContext) -> Vec<ReportingProductId> {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_graph(
|
|
||||||
&self,
|
|
||||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
|
||||||
_dependencies: &mut ReportingGraphDependencies,
|
|
||||||
_context: &ReportingContext,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
fn after_init_graph(
|
|
||||||
&self,
|
|
||||||
_steps: &Vec<Box<dyn ReportingStep>>,
|
|
||||||
_dependencies: &mut ReportingGraphDependencies,
|
|
||||||
_context: &ReportingContext,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
//fn execute(&self, _context: &ReportingContext, _products: &mut ReportingProducts) {
|
|
||||||
// todo!();
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
downcast_rs::impl_downcast!(ReportingStep);
|
|
||||||
|
|
||||||
pub trait ReportingStepArgs: Debug + Display + Downcast + DynClone + DynEq {}
|
|
||||||
|
|
||||||
downcast_rs::impl_downcast!(ReportingStepArgs);
|
|
||||||
dyn_clone::clone_trait_object!(ReportingStepArgs);
|
|
||||||
dyn_eq::eq_trait_object!(ReportingStepArgs);
|
|
||||||
|
|
||||||
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 VoidArgs {}
|
|
||||||
|
|
||||||
impl ReportingStepArgs for VoidArgs {}
|
|
||||||
|
|
||||||
impl Display for VoidArgs {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_fmt(format_args!(""))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub struct DateArgs {
|
|
||||||
pub date: NaiveDate,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ReportingStepArgs for DateArgs {}
|
|
||||||
|
|
||||||
impl Display for DateArgs {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_fmt(format_args!("{}", self.date))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub struct DateStartDateEndArgs {
|
|
||||||
pub date_start: NaiveDate,
|
|
||||||
pub date_end: NaiveDate,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ReportingStepArgs for DateStartDateEndArgs {}
|
|
||||||
|
|
||||||
impl Display for DateStartDateEndArgs {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_fmt(format_args!("{}, {}", self.date_start, self.date_end))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ReportingStepDynamicBuilder {
|
|
||||||
name: &'static str,
|
|
||||||
can_build: fn(
|
|
||||||
name: &'static str,
|
|
||||||
kind: ReportingProductKind,
|
|
||||||
args: &Box<dyn ReportingStepArgs>,
|
|
||||||
steps: &Vec<Box<dyn ReportingStep>>,
|
|
||||||
dependencies: &ReportingGraphDependencies,
|
|
||||||
context: &ReportingContext,
|
|
||||||
) -> bool,
|
|
||||||
build: fn(
|
|
||||||
name: &'static str,
|
|
||||||
kind: ReportingProductKind,
|
|
||||||
args: Box<dyn ReportingStepArgs>,
|
|
||||||
steps: &Vec<Box<dyn ReportingStep>>,
|
|
||||||
dependencies: &ReportingGraphDependencies,
|
|
||||||
context: &ReportingContext,
|
|
||||||
) -> Box<dyn ReportingStep>,
|
|
||||||
}
|
|
||||||
|
@ -20,13 +20,11 @@ use std::fmt::Display;
|
|||||||
|
|
||||||
use chrono::Datelike;
|
use chrono::Datelike;
|
||||||
|
|
||||||
|
use crate::reporting::types::{BalancesAt, DateStartDateEndArgs, ReportingProduct, ReportingProductId};
|
||||||
use crate::util::sofy_from_eofy;
|
use crate::util::sofy_from_eofy;
|
||||||
|
|
||||||
use super::{
|
use super:: calculator::ReportingGraphDependencies;
|
||||||
calculator::ReportingGraphDependencies, DateArgs, DateStartDateEndArgs, ReportingContext,
|
use super::types::{DateArgs, ReportingContext, ReportingProductKind, ReportingProducts, ReportingStep, ReportingStepArgs, ReportingStepId, VoidArgs};
|
||||||
ReportingProductId, ReportingProductKind, ReportingStep, ReportingStepArgs, ReportingStepId,
|
|
||||||
VoidArgs,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn register_lookup_fns(context: &mut ReportingContext) {
|
pub fn register_lookup_fns(context: &mut ReportingContext) {
|
||||||
context.register_lookup_fn(
|
context.register_lookup_fn(
|
||||||
|
282
src/reporting/types.rs
Normal file
282
src/reporting/types.rs
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
/*
|
||||||
|
DrCr: Web-based double-entry bookkeeping framework
|
||||||
|
Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::{Debug, Display};
|
||||||
|
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use downcast_rs::Downcast;
|
||||||
|
use dyn_clone::DynClone;
|
||||||
|
use dyn_eq::DynEq;
|
||||||
|
use dyn_hash::DynHash;
|
||||||
|
|
||||||
|
use crate::QuantityInt;
|
||||||
|
|
||||||
|
use super::calculator::ReportingGraphDependencies;
|
||||||
|
|
||||||
|
// -----------------
|
||||||
|
// REPORTING CONTEXT
|
||||||
|
|
||||||
|
/// Records the context for a single reporting job
|
||||||
|
pub struct ReportingContext {
|
||||||
|
pub eofy_date: NaiveDate,
|
||||||
|
pub(crate) step_lookup_fn: HashMap<
|
||||||
|
(&'static str, &'static [ReportingProductKind]),
|
||||||
|
(ReportingStepTakesArgsFn, ReportingStepFromArgsFn),
|
||||||
|
>,
|
||||||
|
pub(crate) step_dynamic_builders: Vec<ReportingStepDynamicBuilder>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReportingContext {
|
||||||
|
/// Initialise a new [ReportingContext]
|
||||||
|
pub fn new(eofy_date: NaiveDate) -> Self {
|
||||||
|
Self {
|
||||||
|
eofy_date: eofy_date,
|
||||||
|
step_lookup_fn: HashMap::new(),
|
||||||
|
step_dynamic_builders: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a lookup function
|
||||||
|
///
|
||||||
|
/// A lookup function generates concrete [ReportingStep]s from a [ReportingStepId].
|
||||||
|
pub fn register_lookup_fn(
|
||||||
|
&mut self,
|
||||||
|
name: &'static str,
|
||||||
|
product_kinds: &'static [ReportingProductKind],
|
||||||
|
takes_args_fn: ReportingStepTakesArgsFn,
|
||||||
|
from_args_fn: ReportingStepFromArgsFn,
|
||||||
|
) {
|
||||||
|
self.step_lookup_fn
|
||||||
|
.insert((name, product_kinds), (takes_args_fn, from_args_fn));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a dynamic builder
|
||||||
|
///
|
||||||
|
/// Dynamic builders are called when no concrete [ReportingStep] is implemented, and can dynamically generate a [ReportingStep]. Dynamic builders are implemented in [super::builders].
|
||||||
|
pub fn register_dynamic_builder(&mut self, builder: ReportingStepDynamicBuilder) {
|
||||||
|
if !self
|
||||||
|
.step_dynamic_builders
|
||||||
|
.iter()
|
||||||
|
.any(|b| b.name == builder.name)
|
||||||
|
{
|
||||||
|
self.step_dynamic_builders.push(builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Function which determines whether the [ReportingStepArgs] are valid arguments for a given [ReportingStep]
|
||||||
|
///
|
||||||
|
/// See [ReportingContext::register_lookup_fn].
|
||||||
|
pub type ReportingStepTakesArgsFn = fn(args: &Box<dyn ReportingStepArgs>) -> bool;
|
||||||
|
|
||||||
|
/// Function which builds a concrete [ReportingStep] from the given [ReportingStepArgs]
|
||||||
|
///
|
||||||
|
/// See [ReportingContext::register_lookup_fn].
|
||||||
|
pub type ReportingStepFromArgsFn = fn(args: Box<dyn ReportingStepArgs>) -> Box<dyn ReportingStep>;
|
||||||
|
|
||||||
|
// -------------------------------
|
||||||
|
// REPORTING STEP DYNAMIC BUILDERS
|
||||||
|
|
||||||
|
/// Represents a reporting step dynamic builder
|
||||||
|
///
|
||||||
|
/// See [ReportingContext::register_dynamic_builder].
|
||||||
|
pub struct ReportingStepDynamicBuilder {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub can_build: fn(
|
||||||
|
name: &'static str,
|
||||||
|
kind: ReportingProductKind,
|
||||||
|
args: &Box<dyn ReportingStepArgs>,
|
||||||
|
steps: &Vec<Box<dyn ReportingStep>>,
|
||||||
|
dependencies: &ReportingGraphDependencies,
|
||||||
|
context: &ReportingContext,
|
||||||
|
) -> bool,
|
||||||
|
pub build: fn(
|
||||||
|
name: &'static str,
|
||||||
|
kind: ReportingProductKind,
|
||||||
|
args: Box<dyn ReportingStepArgs>,
|
||||||
|
steps: &Vec<Box<dyn ReportingStep>>,
|
||||||
|
dependencies: &ReportingGraphDependencies,
|
||||||
|
context: &ReportingContext,
|
||||||
|
) -> Box<dyn ReportingStep>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------
|
||||||
|
// REPORTING PRODUCTS
|
||||||
|
|
||||||
|
/// Identifies a [ReportingProduct]
|
||||||
|
#[derive(Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub struct ReportingProductId {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub kind: ReportingProductKind,
|
||||||
|
pub args: Box<dyn ReportingStepArgs>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ReportingProductId {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_fmt(format_args!("{}.{:?}({})", self.name, self.kind, self.args))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Identifies a type of [ReportingProduct]
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub enum ReportingProductKind {
|
||||||
|
Transactions,
|
||||||
|
BalancesAt,
|
||||||
|
BalancesBetween,
|
||||||
|
Generic,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the result of a [ReportingStep]
|
||||||
|
pub enum ReportingProduct {
|
||||||
|
Transactions(Transactions),
|
||||||
|
BalancesAt(BalancesAt),
|
||||||
|
BalancesBetween(BalancesBetween),
|
||||||
|
Generic(Box<dyn GenericReportingProduct>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Records a list of transactions generated by a [ReportingStep]
|
||||||
|
pub struct Transactions {}
|
||||||
|
|
||||||
|
/// Records cumulative account balances at a particular point in time
|
||||||
|
pub struct BalancesAt {
|
||||||
|
pub balances: HashMap<String, QuantityInt>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Records the total value of transactions in each account between two points in time
|
||||||
|
pub struct BalancesBetween {}
|
||||||
|
|
||||||
|
/// Represents a custom [ReportingProduct] generated by a [ReportingStep]
|
||||||
|
pub trait GenericReportingProduct {}
|
||||||
|
|
||||||
|
/// Convenience type mapping [ReportingProductId] to [ReportingProduct]
|
||||||
|
pub type ReportingProducts = HashMap<ReportingProductId, ReportingProduct>;
|
||||||
|
|
||||||
|
// ---------------
|
||||||
|
// REPORTING STEPS
|
||||||
|
|
||||||
|
/// Identifies a [ReportingStep]
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct ReportingStepId {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub product_kinds: &'static [ReportingProductKind],
|
||||||
|
pub args: Box<dyn ReportingStepArgs>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ReportingStepId {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_fmt(format_args!(
|
||||||
|
"{}{:?}({})",
|
||||||
|
self.name, self.product_kinds, self.args
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a step in a reporting job
|
||||||
|
pub trait ReportingStep: Debug + Display + Downcast {
|
||||||
|
/// Get the [ReportingStepId] for this [ReportingStep]
|
||||||
|
fn id(&self) -> ReportingStepId;
|
||||||
|
|
||||||
|
/// Return a list of statically defined dependencies for this [ReportingStep]
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn requires(&self, context: &ReportingContext) -> Vec<ReportingProductId> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when the [ReportingStep] is initialised in [super::calculator::steps_for_targets]
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn init_graph(
|
||||||
|
&self,
|
||||||
|
steps: &Vec<Box<dyn ReportingStep>>,
|
||||||
|
dependencies: &mut ReportingGraphDependencies,
|
||||||
|
context: &ReportingContext,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when new [ReportingStep]s are initialised in [super::calculator::steps_for_targets]
|
||||||
|
///
|
||||||
|
/// This callback can be used to dynamically declare dependencies between [ReportingStep]s that are not known at initialisation.
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn after_init_graph(
|
||||||
|
&self,
|
||||||
|
steps: &Vec<Box<dyn ReportingStep>>,
|
||||||
|
dependencies: &mut ReportingGraphDependencies,
|
||||||
|
context: &ReportingContext,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called to generate the [ReportingProduct] for this [ReportingStep]
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn execute(&self, context: &ReportingContext, products: &mut ReportingProducts) {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downcast_rs::impl_downcast!(ReportingStep);
|
||||||
|
|
||||||
|
// ------------------------
|
||||||
|
// REPORTING STEP ARGUMENTS
|
||||||
|
|
||||||
|
/// Represents arguments to a [ReportingStep]
|
||||||
|
pub trait ReportingStepArgs: Debug + Display + Downcast + DynClone + DynEq + DynHash {}
|
||||||
|
|
||||||
|
downcast_rs::impl_downcast!(ReportingStepArgs);
|
||||||
|
dyn_clone::clone_trait_object!(ReportingStepArgs);
|
||||||
|
dyn_eq::eq_trait_object!(ReportingStepArgs);
|
||||||
|
dyn_hash::hash_trait_object!(ReportingStepArgs);
|
||||||
|
|
||||||
|
/// [ReportingStepArgs] implementation which takes no arguments
|
||||||
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub struct VoidArgs {}
|
||||||
|
|
||||||
|
impl ReportingStepArgs for VoidArgs {}
|
||||||
|
|
||||||
|
impl Display for VoidArgs {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_fmt(format_args!(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [ReportingStepArgs] implementation which takes a single date
|
||||||
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub struct DateArgs {
|
||||||
|
pub date: NaiveDate,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReportingStepArgs for DateArgs {}
|
||||||
|
|
||||||
|
impl Display for DateArgs {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_fmt(format_args!("{}", self.date))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [ReportingStepArgs] implementation which takes a date range
|
||||||
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub struct DateStartDateEndArgs {
|
||||||
|
pub date_start: NaiveDate,
|
||||||
|
pub date_end: NaiveDate,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReportingStepArgs for DateStartDateEndArgs {}
|
||||||
|
|
||||||
|
impl Display for DateStartDateEndArgs {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_fmt(format_args!("{}, {}", self.date_start, self.date_end))
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user