Fix multiple logic errors when reporting for not current year

This commit is contained in:
RunasSudo 2025-05-26 22:43:59 +10:00
parent 3cfcdf6778
commit f4c232ae35
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
4 changed files with 103 additions and 69 deletions

View File

@ -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(),
}],
}),
})

View File

@ -364,11 +364,15 @@ impl UpdateBalancesAt {
fn can_build(
name: &'static str,
kind: ReportingProductKind,
_args: &Box<dyn ReportingStepArgs>,
args: &Box<dyn ReportingStepArgs>,
steps: &Vec<Box<dyn ReportingStep>>,
dependencies: &ReportingGraphDependencies,
context: &ReportingContext,
) -> bool {
if !args.is::<DateArgs>() {
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::<DateStartDateEndArgs>()
.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::<DateArgs>().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::<DateStartDateEndArgs>()
.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(

View File

@ -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<ReportingProductId> {
// 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<dyn ReportingProduct> = 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<ReportingProductId> {
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::<BalancesBetween>()
@ -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<ReportingProductId> {
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",

View File

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