Basic framework for executing reports

This commit is contained in:
RunasSudo 2025-05-21 20:15:18 +10:00
parent 9e34360abf
commit db89814498
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
9 changed files with 134 additions and 42 deletions

7
Cargo.lock generated
View File

@ -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",
]

View File

@ -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"

View File

@ -1,3 +1,5 @@
pub mod reporting;
pub mod transaction;
pub mod util;
pub type QuantityInt = u64;

View File

@ -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<Box<dyn ReportingStep>> = 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);
}

View File

@ -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<Box<dyn ReportingStep>>,
context: ReportingContext,
context: &ReportingContext,
) -> Result<Vec<Box<dyn ReportingStep>>, ReportingCalculationError> {
let mut steps: Vec<Box<dyn ReportingStep>> = Vec::new();
let mut dependencies = ReportingGraphDependencies { vec: Vec::new() };

37
src/reporting/executor.rs Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*/
use super::types::{ReportingContext, ReportingProducts, ReportingStep};
#[derive(Debug)]
pub struct ReportingExecutionError {
message: String,
}
pub fn execute_steps(
steps: Vec<Box<dyn ReportingStep>>,
context: &ReportingContext,
) -> Result<ReportingProducts, ReportingExecutionError> {
let mut products = ReportingProducts::new();
for step in steps {
step.execute(context, &mut products)?;
}
Ok(products)
}

View File

@ -16,7 +16,43 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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<ReportingCalculationError> for ReportingError {
fn from(err: ReportingCalculationError) -> Self {
ReportingError::ReportingCalculationError(err)
}
}
impl From<ReportingExecutionError> for ReportingError {
fn from(err: ReportingExecutionError) -> Self {
ReportingError::ReportingExecutionError(err)
}
}
pub fn generate_report(
targets: Vec<Box<dyn ReportingStep>>,
context: &ReportingContext,
) -> Result<ReportingProducts, ReportingError> {
// Solve dependencies
let sorted_steps = steps_for_targets(targets, context)?;
// Execute steps
let products = execute_steps(sorted_steps, context)?;
Ok(products)
}

View File

@ -16,15 +16,22 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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)]

View File

@ -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<String, QuantityInt>,
}
/// 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<ReportingProductId, ReportingProduct>;
@ -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);
}
}