use crate::error::EvalResult;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubledgerReconciliationEvaluation {
pub ar_reconciled: bool,
pub ar_gl_balance: Decimal,
pub ar_subledger_balance: Decimal,
pub ar_difference: Decimal,
pub ap_reconciled: bool,
pub ap_gl_balance: Decimal,
pub ap_subledger_balance: Decimal,
pub ap_difference: Decimal,
pub fa_asset_reconciled: bool,
pub fa_asset_gl_balance: Decimal,
pub fa_asset_subledger_balance: Decimal,
pub fa_asset_difference: Decimal,
pub fa_accum_depr_reconciled: bool,
pub fa_accum_depr_gl_balance: Decimal,
pub fa_accum_depr_subledger_balance: Decimal,
pub fa_accum_depr_difference: Decimal,
pub inventory_reconciled: bool,
pub inventory_gl_balance: Decimal,
pub inventory_subledger_balance: Decimal,
pub inventory_difference: Decimal,
pub completeness_score: f64,
pub subledgers_reconciled: usize,
pub subledgers_total: usize,
}
#[derive(Debug, Clone, Default)]
pub struct SubledgerData {
pub ar_gl_balance: Option<Decimal>,
pub ar_subledger_balance: Option<Decimal>,
pub ap_gl_balance: Option<Decimal>,
pub ap_subledger_balance: Option<Decimal>,
pub fa_asset_gl_balance: Option<Decimal>,
pub fa_asset_subledger_balance: Option<Decimal>,
pub fa_accum_depr_gl_balance: Option<Decimal>,
pub fa_accum_depr_subledger_balance: Option<Decimal>,
pub inventory_gl_balance: Option<Decimal>,
pub inventory_subledger_balance: Option<Decimal>,
}
pub struct SubledgerEvaluator {
tolerance: Decimal,
}
impl SubledgerEvaluator {
pub fn new(tolerance: Decimal) -> Self {
Self { tolerance }
}
pub fn evaluate(&self, data: &SubledgerData) -> EvalResult<SubledgerReconciliationEvaluation> {
let mut subledgers_reconciled = 0;
let mut subledgers_total = 0;
let (ar_reconciled, ar_gl, ar_sub, ar_diff) = self.check_reconciliation(
data.ar_gl_balance,
data.ar_subledger_balance,
&mut subledgers_reconciled,
&mut subledgers_total,
);
let (ap_reconciled, ap_gl, ap_sub, ap_diff) = self.check_reconciliation(
data.ap_gl_balance,
data.ap_subledger_balance,
&mut subledgers_reconciled,
&mut subledgers_total,
);
let (fa_asset_reconciled, fa_asset_gl, fa_asset_sub, fa_asset_diff) = self
.check_reconciliation(
data.fa_asset_gl_balance,
data.fa_asset_subledger_balance,
&mut subledgers_reconciled,
&mut subledgers_total,
);
let (fa_accum_reconciled, fa_accum_gl, fa_accum_sub, fa_accum_diff) = self
.check_reconciliation(
data.fa_accum_depr_gl_balance,
data.fa_accum_depr_subledger_balance,
&mut subledgers_reconciled,
&mut subledgers_total,
);
let (inv_reconciled, inv_gl, inv_sub, inv_diff) = self.check_reconciliation(
data.inventory_gl_balance,
data.inventory_subledger_balance,
&mut subledgers_reconciled,
&mut subledgers_total,
);
let completeness_score = if subledgers_total > 0 {
subledgers_reconciled as f64 / subledgers_total as f64
} else {
1.0 };
Ok(SubledgerReconciliationEvaluation {
ar_reconciled,
ar_gl_balance: ar_gl,
ar_subledger_balance: ar_sub,
ar_difference: ar_diff,
ap_reconciled,
ap_gl_balance: ap_gl,
ap_subledger_balance: ap_sub,
ap_difference: ap_diff,
fa_asset_reconciled,
fa_asset_gl_balance: fa_asset_gl,
fa_asset_subledger_balance: fa_asset_sub,
fa_asset_difference: fa_asset_diff,
fa_accum_depr_reconciled: fa_accum_reconciled,
fa_accum_depr_gl_balance: fa_accum_gl,
fa_accum_depr_subledger_balance: fa_accum_sub,
fa_accum_depr_difference: fa_accum_diff,
inventory_reconciled: inv_reconciled,
inventory_gl_balance: inv_gl,
inventory_subledger_balance: inv_sub,
inventory_difference: inv_diff,
completeness_score,
subledgers_reconciled,
subledgers_total,
})
}
fn check_reconciliation(
&self,
gl_balance: Option<Decimal>,
subledger_balance: Option<Decimal>,
reconciled_count: &mut usize,
total_count: &mut usize,
) -> (bool, Decimal, Decimal, Decimal) {
match (gl_balance, subledger_balance) {
(Some(gl), Some(sub)) => {
*total_count += 1;
let diff = gl - sub;
let is_reconciled = diff.abs() <= self.tolerance;
if is_reconciled {
*reconciled_count += 1;
}
(is_reconciled, gl, sub, diff)
}
(Some(gl), None) => {
*total_count += 1;
(false, gl, Decimal::ZERO, gl)
}
(None, Some(sub)) => {
*total_count += 1;
(false, Decimal::ZERO, sub, Decimal::ZERO - sub)
}
(None, None) => {
(true, Decimal::ZERO, Decimal::ZERO, Decimal::ZERO)
}
}
}
}
impl Default for SubledgerEvaluator {
fn default() -> Self {
Self::new(Decimal::new(1, 2)) }
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn test_reconciled_subledgers() {
let data = SubledgerData {
ar_gl_balance: Some(Decimal::new(100000, 2)),
ar_subledger_balance: Some(Decimal::new(100000, 2)),
ap_gl_balance: Some(Decimal::new(50000, 2)),
ap_subledger_balance: Some(Decimal::new(50000, 2)),
..Default::default()
};
let evaluator = SubledgerEvaluator::default();
let result = evaluator.evaluate(&data).unwrap();
assert!(result.ar_reconciled);
assert!(result.ap_reconciled);
assert_eq!(result.completeness_score, 1.0);
}
#[test]
fn test_unreconciled_subledger() {
let data = SubledgerData {
ar_gl_balance: Some(Decimal::new(100000, 2)),
ar_subledger_balance: Some(Decimal::new(99000, 2)), ..Default::default()
};
let evaluator = SubledgerEvaluator::default();
let result = evaluator.evaluate(&data).unwrap();
assert!(!result.ar_reconciled);
assert_eq!(result.ar_difference, Decimal::new(1000, 2));
}
#[test]
fn test_no_subledger_data() {
let data = SubledgerData::default();
let evaluator = SubledgerEvaluator::default();
let result = evaluator.evaluate(&data).unwrap();
assert_eq!(result.completeness_score, 1.0);
}
}