From db89814498158fd84112f250beecdae1b9962c1b Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Wed, 21 May 2025 20:15:18 +1000 Subject: [PATCH] Basic framework for executing reports --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/lib.rs | 2 ++ src/main.rs | 40 +++++-------------------------------- src/reporting/calculator.rs | 2 +- src/reporting/executor.rs | 37 ++++++++++++++++++++++++++++++++++ src/reporting/mod.rs | 36 +++++++++++++++++++++++++++++++++ src/reporting/steps.rs | 36 ++++++++++++++++++++++++++++++--- src/reporting/types.rs | 15 +++++++++++--- 9 files changed, 134 insertions(+), 42 deletions(-) create mode 100644 src/reporting/executor.rs diff --git a/Cargo.lock b/Cargo.lock index fee6985..14f6134 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,6 +82,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c2d035d21af5cde1a6f5c7b444a5bf963520a9f142e5d06931178433d7d5388" +[[package]] +name = "dyn-hash" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15401da73a9ed8c80e3b2d4dc05fe10e7b72d7243b9f614e516a44fa99986e88" + [[package]] name = "iana-time-zone" version = "0.1.63" @@ -130,6 +136,7 @@ dependencies = [ "downcast-rs", "dyn-clone", "dyn-eq", + "dyn-hash", "solvent", ] diff --git a/Cargo.toml b/Cargo.toml index b4bac49..9cd9b5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,5 @@ chrono = "0.4.41" downcast-rs = "2.0.1" dyn-clone = "1.0.19" dyn-eq = "0.1.3" +dyn-hash = "0.2.2" solvent = "0.8.3" diff --git a/src/lib.rs b/src/lib.rs index 24e0b08..6130654 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ pub mod reporting; pub mod transaction; pub mod util; + +pub type QuantityInt = u64; diff --git a/src/main.rs b/src/main.rs index 16f2c3a..65cc9e2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,13 +18,12 @@ use chrono::NaiveDate; use libdrcr::reporting::builders::register_dynamic_builders; -use libdrcr::reporting::calculator::steps_for_targets; +use libdrcr::reporting::generate_report; use libdrcr::reporting::steps::{ - register_lookup_fns, AllTransactionsExceptRetainedEarnings, - AllTransactionsIncludingRetainedEarnings, CalculateIncomeTax, + register_lookup_fns, AllTransactionsExceptRetainedEarnings, CalculateIncomeTax, }; use libdrcr::reporting::types::{ - DateArgs, DateStartDateEndArgs, ReportingContext, ReportingProductKind, ReportingStep, + DateStartDateEndArgs, ReportingContext, ReportingProductKind, ReportingStep, }; fn main() { @@ -43,36 +42,7 @@ fn main() { }), ]; - println!("For income statement:"); - match steps_for_targets(targets, context) { - Ok(steps) => { - for step in steps { - println!("- {}", step); - } - } - Err(err) => panic!("Error: {:?}", err), - } + let products = generate_report(targets, &context); - let mut context = ReportingContext::new(NaiveDate::from_ymd_opt(2025, 6, 30).unwrap()); - register_lookup_fns(&mut context); - register_dynamic_builders(&mut context); - - let targets: Vec> = vec![ - Box::new(CalculateIncomeTax {}), - Box::new(AllTransactionsIncludingRetainedEarnings { - args: DateArgs { - date: NaiveDate::from_ymd_opt(2025, 6, 30).unwrap(), - }, - }), - ]; - - println!("For balance sheet:"); - match steps_for_targets(targets, context) { - Ok(steps) => { - for step in steps { - println!("- {}", step); - } - } - Err(err) => panic!("Error: {:?}", err), - } + println!("{:?}", products); } diff --git a/src/reporting/calculator.rs b/src/reporting/calculator.rs index 47f9c0e..68cbbfb 100644 --- a/src/reporting/calculator.rs +++ b/src/reporting/calculator.rs @@ -150,7 +150,7 @@ fn would_be_ready_to_execute( /// Recursively resolve the dependencies of the target [ReportingStep]s and return a sorted [Vec] of [ReportingStep]s pub fn steps_for_targets( targets: Vec>, - context: ReportingContext, + context: &ReportingContext, ) -> Result>, ReportingCalculationError> { let mut steps: Vec> = Vec::new(); let mut dependencies = ReportingGraphDependencies { vec: Vec::new() }; diff --git a/src/reporting/executor.rs b/src/reporting/executor.rs new file mode 100644 index 0000000..bcb9976 --- /dev/null +++ b/src/reporting/executor.rs @@ -0,0 +1,37 @@ +/* + 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 . +*/ + +use super::types::{ReportingContext, ReportingProducts, ReportingStep}; + +#[derive(Debug)] +pub struct ReportingExecutionError { + message: String, +} + +pub fn execute_steps( + steps: Vec>, + context: &ReportingContext, +) -> Result { + let mut products = ReportingProducts::new(); + + for step in steps { + step.execute(context, &mut products)?; + } + + Ok(products) +} diff --git a/src/reporting/mod.rs b/src/reporting/mod.rs index bc34ccb..83a672f 100644 --- a/src/reporting/mod.rs +++ b/src/reporting/mod.rs @@ -16,7 +16,43 @@ along with this program. If not, see . */ +use calculator::{steps_for_targets, ReportingCalculationError}; +use executor::{execute_steps, ReportingExecutionError}; +use types::{ReportingContext, ReportingProducts, ReportingStep}; + pub mod builders; pub mod calculator; +pub mod executor; pub mod steps; pub mod types; + +#[derive(Debug)] +pub enum ReportingError { + ReportingCalculationError(ReportingCalculationError), + ReportingExecutionError(ReportingExecutionError), +} + +impl From for ReportingError { + fn from(err: ReportingCalculationError) -> Self { + ReportingError::ReportingCalculationError(err) + } +} + +impl From for ReportingError { + fn from(err: ReportingExecutionError) -> Self { + ReportingError::ReportingExecutionError(err) + } +} + +pub fn generate_report( + targets: Vec>, + context: &ReportingContext, +) -> Result { + // Solve dependencies + let sorted_steps = steps_for_targets(targets, context)?; + + // Execute steps + let products = execute_steps(sorted_steps, context)?; + + Ok(products) +} diff --git a/src/reporting/steps.rs b/src/reporting/steps.rs index 15482cd..9984f69 100644 --- a/src/reporting/steps.rs +++ b/src/reporting/steps.rs @@ -16,15 +16,22 @@ along with this program. If not, see . */ +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, ReportingProduct, ReportingProductId, +}; use crate::util::sofy_from_eofy; -use super:: calculator::ReportingGraphDependencies; -use super::types::{DateArgs, ReportingContext, ReportingProductKind, ReportingProducts, ReportingStep, ReportingStepArgs, ReportingStepId, VoidArgs}; +use super::calculator::ReportingGraphDependencies; +use super::executor::ReportingExecutionError; +use super::types::{ + DateArgs, ReportingContext, ReportingProductKind, ReportingProducts, ReportingStep, + ReportingStepArgs, ReportingStepId, VoidArgs, +}; pub fn register_lookup_fns(context: &mut ReportingContext) { context.register_lookup_fn( @@ -322,6 +329,29 @@ impl ReportingStep for DBBalances { args: Box::new(self.args.clone()), } } + + fn execute( + &self, + _context: &ReportingContext, + products: &mut ReportingProducts, + ) -> Result<(), ReportingExecutionError> { + eprintln!("Stub: DBBalances.execute"); + + let balances = BalancesAt { + balances: HashMap::new(), + }; + + products.insert( + ReportingProductId { + name: self.id().name, + kind: ReportingProductKind::BalancesAt, + args: Box::new(self.args.clone()), + }, + ReportingProduct::BalancesAt(balances), + ); + + Ok(()) + } } #[derive(Debug)] diff --git a/src/reporting/types.rs b/src/reporting/types.rs index e6a25bd..7272eb3 100644 --- a/src/reporting/types.rs +++ b/src/reporting/types.rs @@ -28,6 +28,7 @@ use dyn_hash::DynHash; use crate::QuantityInt; use super::calculator::ReportingGraphDependencies; +use super::executor::ReportingExecutionError; // ----------------- // REPORTING CONTEXT @@ -143,6 +144,7 @@ pub enum ReportingProductKind { } /// Represents the result of a [ReportingStep] +#[derive(Debug)] pub enum ReportingProduct { Transactions(Transactions), BalancesAt(BalancesAt), @@ -151,18 +153,21 @@ pub enum ReportingProduct { } /// Records a list of transactions generated by a [ReportingStep] +#[derive(Debug)] pub struct Transactions {} /// Records cumulative account balances at a particular point in time +#[derive(Debug)] pub struct BalancesAt { pub balances: HashMap, } /// Records the total value of transactions in each account between two points in time +#[derive(Debug)] pub struct BalancesBetween {} /// Represents a custom [ReportingProduct] generated by a [ReportingStep] -pub trait GenericReportingProduct {} +pub trait GenericReportingProduct: Debug {} /// Convenience type mapping [ReportingProductId] to [ReportingProduct] pub type ReportingProducts = HashMap; @@ -222,8 +227,12 @@ pub trait ReportingStep: Debug + Display + Downcast { /// Called to generate the [ReportingProduct] for this [ReportingStep] #[allow(unused_variables)] - fn execute(&self, context: &ReportingContext, products: &mut ReportingProducts) { - todo!(); + fn execute( + &self, + context: &ReportingContext, + products: &mut ReportingProducts, + ) -> Result<(), ReportingExecutionError> { + todo!("{}", self); } }