From f4c232ae35b59f26874862e71a2a06feb806af7a Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Mon, 26 May 2025 22:43:59 +1000 Subject: [PATCH] Fix multiple logic errors when reporting for not current year --- src/main.rs | 16 +++++---- src/reporting/builders.rs | 63 ++++++++++++++++++++++---------- src/reporting/steps.rs | 75 +++++++++++++++++---------------------- src/util.rs | 18 ++++++++-- 4 files changed, 103 insertions(+), 69 deletions(-) diff --git a/src/main.rs b/src/main.rs index a8430e3..27da957 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,6 +29,8 @@ use libdrcr::reporting::types::{ }; fn main() { + const YEAR: i32 = 2023; + // Connect to database let db_connection = DbConnection::connect("sqlite:drcr_testing.db"); @@ -55,7 +57,7 @@ fn main() { kind: ReportingProductKind::Generic, args: Box::new(MultipleDateArgs { dates: vec![DateArgs { - date: NaiveDate::from_ymd_opt(2025, 6, 30).unwrap(), + date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(), }], }), }, @@ -77,8 +79,8 @@ fn main() { name: "AllTransactionsExceptEarningsToEquity", 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(), + date_start: NaiveDate::from_ymd_opt(YEAR - 1, 7, 1).unwrap(), + date_end: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(), }), }, ]; @@ -89,8 +91,8 @@ fn main() { name: "AllTransactionsExceptEarningsToEquity", 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(), + date_start: NaiveDate::from_ymd_opt(YEAR - 1, 7, 1).unwrap(), + date_end: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(), }), }) .unwrap(); @@ -111,7 +113,7 @@ fn main() { kind: ReportingProductKind::Generic, args: Box::new(MultipleDateArgs { dates: vec![DateArgs { - date: NaiveDate::from_ymd_opt(2025, 6, 30).unwrap(), + date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(), }], }), }, @@ -124,7 +126,7 @@ fn main() { kind: ReportingProductKind::Generic, args: Box::new(MultipleDateArgs { dates: vec![DateArgs { - date: NaiveDate::from_ymd_opt(2025, 6, 30).unwrap(), + date: NaiveDate::from_ymd_opt(YEAR, 6, 30).unwrap(), }], }), }) diff --git a/src/reporting/builders.rs b/src/reporting/builders.rs index 5411118..d6a5e8b 100644 --- a/src/reporting/builders.rs +++ b/src/reporting/builders.rs @@ -364,11 +364,15 @@ impl UpdateBalancesAt { fn can_build( name: &'static str, kind: ReportingProductKind, - _args: &Box, + args: &Box, steps: &Vec>, dependencies: &ReportingGraphDependencies, context: &ReportingContext, ) -> bool { + if !args.is::() { + return false; + } + // Check for Transactions -> BalancesAt if kind == ReportingProductKind::BalancesAt { // Initially no need to check args @@ -391,18 +395,13 @@ impl UpdateBalancesAt { && dependencies_for_step[0].product.kind == ReportingProductKind::BalancesBetween { - let date_end = dependencies_for_step[0] - .product - .args - .downcast_ref::() - .unwrap() - .date_end; - match has_step_or_can_build( &ReportingProductId { name: dependencies_for_step[0].product.name, kind: ReportingProductKind::BalancesAt, - args: Box::new(DateArgs { date: date_end }), + args: Box::new(DateArgs { + date: args.downcast_ref::().unwrap().date, + }), }, steps, dependencies, @@ -477,6 +476,27 @@ impl ReportingStep for UpdateBalancesAt { args: parent_step.id().args.clone(), }, ); + + // 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 + + if dependency.kind == ReportingProductKind::BalancesAt { + // Directly depends on BalancesAt -> Transaction + // Do not need to add extra dependencies + } else { + // As checked in can_build, must depend on BalancesBetween -> Transaction with a BalancesAt available + dependencies.add_dependency( + self.id(), + ReportingProductId { + name: dependency.name, + kind: ReportingProductKind::BalancesAt, + args: Box::new(DateArgs { + date: self.args.date, + }), + }, + ); + } } fn execute( @@ -522,17 +542,13 @@ impl ReportingStep for UpdateBalancesAt { .unwrap(); } else { // As checked in can_build, must depend on BalancesBetween -> Transaction with a BalancesAt available - let date_end = dependency - .args - .downcast_ref::() - .unwrap() - .date_end; - opening_balances_at = products .get_or_err(&ReportingProductId { name: dependency.name, kind: ReportingProductKind::BalancesAt, - args: Box::new(DateArgs { date: date_end }), + args: Box::new(DateArgs { + date: self.args.date, + }), })? .downcast_ref() .unwrap(); @@ -542,7 +558,12 @@ impl ReportingStep for UpdateBalancesAt { let mut balances = BalancesAt { balances: opening_balances_at.balances.clone(), }; - update_balances_from_transactions(&mut balances.balances, transactions.iter()); + update_balances_from_transactions( + &mut balances.balances, + transactions + .iter() + .filter(|t| t.transaction.dt.date() <= self.args.date), + ); // Store result products.insert( @@ -706,7 +727,13 @@ impl ReportingStep for UpdateBalancesBetween { let mut balances = BalancesBetween { balances: opening_balances.clone(), }; - update_balances_from_transactions(&mut balances.balances, transactions.iter()); + update_balances_from_transactions( + &mut balances.balances, + transactions.iter().filter(|t| { + t.transaction.dt.date() >= self.args.date_start + && t.transaction.dt.date() <= self.args.date_end + }), + ); // Store result products.insert( diff --git a/src/reporting/steps.rs b/src/reporting/steps.rs index 0c2977d..a152bbe 100644 --- a/src/reporting/steps.rs +++ b/src/reporting/steps.rs @@ -28,7 +28,7 @@ use crate::reporting::types::{BalancesAt, DateStartDateEndArgs, ReportingProduct use crate::transaction::{ update_balances_from_transactions, Posting, Transaction, TransactionWithPostings, }; -use crate::util::sofy_from_eofy; +use crate::util::{get_eofy, sofy_from_eofy}; use crate::QuantityInt; use super::calculator::ReportingGraphDependencies; @@ -37,9 +37,8 @@ use super::dynamic_report::{ }; use super::executor::ReportingExecutionError; use super::types::{ - BalancesBetween, DateArgs, MultipleDateArgs, ReportingContext, ReportingProduct, - ReportingProductKind, ReportingProducts, ReportingStep, ReportingStepArgs, ReportingStepId, - VoidArgs, + BalancesBetween, DateArgs, MultipleDateArgs, ReportingContext, ReportingProductKind, + ReportingProducts, ReportingStep, ReportingStepArgs, ReportingStepId, VoidArgs, }; /// Call [ReportingContext::register_lookup_fn] for all steps provided by this module @@ -113,6 +112,15 @@ impl ReportingStep for AllTransactionsExceptEarningsToEquity { } } + fn requires(&self, _context: &ReportingContext) -> Vec { + // AllTransactionsExceptEarningsToEquity always depends on CombineOrdinaryTransactions at least + vec![ReportingProductId { + name: "CombineOrdinaryTransactions", + kind: self.product_kinds[0], + args: self.args.clone(), + }] + } + fn execute( &self, _context: &ReportingContext, @@ -142,30 +150,11 @@ impl ReportingStep for AllTransactionsExceptEarningsToEquity { } } - // No dependencies?! - store empty result - let product: Box = 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 AllTransactionsExceptEarningsToEquity.Generic but no available dependencies to provide it"), - }; - - products.insert( - ReportingProductId { - name: self.id().name, - kind: product_kind, - args: self.args.clone(), - }, - product, + // No dependencies?! - this is likely a mistake + panic!( + "Requested {:?} but no available dependencies to provide it", + self.product_kinds[0] ); - - Ok(()) } } @@ -675,7 +664,7 @@ impl ReportingStep for CombineOrdinaryTransactions { } } -/// Transfer current year balances in income and expense accounts to the current year earnings equity account +/// Transfer year-to-date balances in income and expense accounts (as at the requested date) to the current year earnings equity account #[derive(Debug)] pub struct CurrentYearEarningsToEquity { pub args: DateArgs, @@ -718,13 +707,15 @@ impl ReportingStep for CurrentYearEarningsToEquity { } fn requires(&self, context: &ReportingContext) -> Vec { + let eofy_date = get_eofy(&self.args.date, &context.eofy_date); + // CurrentYearEarningsToEquity depends on AllTransactionsExceptEarningsToEquity vec![ReportingProductId { name: "AllTransactionsExceptEarningsToEquity", kind: ReportingProductKind::BalancesBetween, args: Box::new(DateStartDateEndArgs { - date_start: sofy_from_eofy(context.eofy_date), - date_end: context.eofy_date.clone(), + date_start: sofy_from_eofy(eofy_date), + date_end: eofy_date, }), }] } @@ -736,14 +727,16 @@ impl ReportingStep for CurrentYearEarningsToEquity { _dependencies: &ReportingGraphDependencies, products: &mut ReportingProducts, ) -> Result<(), ReportingExecutionError> { + let eofy_date = get_eofy(&self.args.date, &context.eofy_date); + // Get balances for this financial year let balances = products .get_or_err(&ReportingProductId { name: "AllTransactionsExceptEarningsToEquity", kind: ReportingProductKind::BalancesBetween, args: Box::new(DateStartDateEndArgs { - date_start: sofy_from_eofy(context.eofy_date), - date_end: context.eofy_date.clone(), + date_start: sofy_from_eofy(eofy_date), + date_end: eofy_date, }), })? .downcast_ref::() @@ -767,7 +760,7 @@ impl ReportingStep for CurrentYearEarningsToEquity { transactions.transactions.push(TransactionWithPostings { transaction: Transaction { id: None, - dt: context.eofy_date.and_hms_opt(0, 0, 0).unwrap(), + dt: eofy_date.and_hms_opt(0, 0, 0).unwrap(), description: "Current year earnings".to_string(), }, postings: vec![ @@ -985,15 +978,15 @@ impl ReportingStep for RetainedEarningsToEquity { } fn requires(&self, context: &ReportingContext) -> Vec { + let eofy_date = get_eofy(&self.args.date, &context.eofy_date); + let last_eofy_date = eofy_date.with_year(eofy_date.year() - 1).unwrap(); + // RetainedEarningsToEquity depends on CombineOrdinaryTransactions for last financial year vec![ReportingProductId { name: "CombineOrdinaryTransactions", kind: ReportingProductKind::BalancesAt, args: Box::new(DateArgs { - date: context - .eofy_date - .with_year(context.eofy_date.year() - 1) - .unwrap(), + date: last_eofy_date, }), }] } @@ -1005,12 +998,10 @@ impl ReportingStep for RetainedEarningsToEquity { _dependencies: &ReportingGraphDependencies, products: &mut ReportingProducts, ) -> Result<(), ReportingExecutionError> { - // Get balances at end of last financial year - let last_eofy_date = context - .eofy_date - .with_year(context.eofy_date.year() - 1) - .unwrap(); + let eofy_date = get_eofy(&self.args.date, &context.eofy_date); + let last_eofy_date = eofy_date.with_year(eofy_date.year() - 1).unwrap(); + // Get balances at end of last financial year let balances_last_eofy = products .get_or_err(&ReportingProductId { name: "CombineOrdinaryTransactions", diff --git a/src/util.rs b/src/util.rs index 73f25a5..1d6102b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -18,9 +18,23 @@ use chrono::{Datelike, NaiveDate}; +/// Return the end date of the current financial year for the given date +pub fn get_eofy(date: &NaiveDate, eofy_date: &NaiveDate) -> NaiveDate { + let date_eofy = eofy_date.with_year(date.year()).unwrap(); + if date_eofy >= *date { + date_eofy + } else { + date_eofy.with_year(date_eofy.year() + 1).unwrap() + } +} + /// Return the start date of the financial year, given the end date of the financial year -pub fn sofy_from_eofy(date_eofy: NaiveDate) -> NaiveDate { - date_eofy.with_year(date_eofy.year() - 1).unwrap().succ_opt().unwrap() +pub fn sofy_from_eofy(eofy_date: NaiveDate) -> NaiveDate { + eofy_date + .with_year(eofy_date.year() - 1) + .unwrap() + .succ_opt() + .unwrap() } /// Format the [NaiveDate] as a string