use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TotalCalculator;
impl TotalCalculator {
#[allow(clippy::too_many_arguments)]
pub fn calculate(
pinjaman: Decimal,
angsuran: Decimal,
sisa_tenor: u32,
asuransi: Decimal,
pajak: Decimal,
diskon: Decimal,
) -> Decimal {
tracing::info!(
event = "calculate_total",
pinjaman = %pinjaman,
angsuran = %angsuran,
sisa_tenor = sisa_tenor,
asuransi = %asuransi,
pajak = %pajak,
diskon = %diskon,
"Calculating total"
);
let sisa_pokok = pinjaman - (angsuran * Decimal::from(sisa_tenor));
let after_insurance = sisa_pokok - asuransi;
let after_tax = after_insurance - pajak;
let total = after_tax + diskon;
tracing::info!(
event = "total_calculated",
sisa_pokok = %sisa_pokok,
after_insurance = %after_insurance,
after_tax = %after_tax,
total = %total,
formula = "(E4-(E5*E6))-E8-E9+E7",
"Total calculated successfully"
);
total
}
pub fn calculate_with_breakdown(
pinjaman: Decimal,
angsuran: Decimal,
sisa_tenor: u32,
asuransi: Decimal,
pajak: Decimal,
diskon: Decimal,
) -> TotalBreakdown {
let sisa_pokok = pinjaman - (angsuran * Decimal::from(sisa_tenor));
let after_insurance = sisa_pokok - asuransi;
let after_tax = after_insurance - pajak;
let total = after_tax + diskon;
TotalBreakdown {
pinjaman,
total_angsuran_belum_bayar: angsuran * Decimal::from(sisa_tenor),
sisa_pokok,
asuransi,
pajak,
diskon,
total,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TotalBreakdown {
pub pinjaman: Decimal,
pub total_angsuran_belum_bayar: Decimal,
pub sisa_pokok: Decimal,
pub asuransi: Decimal,
pub pajak: Decimal,
pub diskon: Decimal,
pub total: Decimal,
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn test_calculate_basic() {
let total = TotalCalculator::calculate(
dec!(10_000_000),
dec!(500_000),
5,
dec!(150_000),
dec!(150_000),
dec!(200_000),
);
assert_eq!(total, dec!(7_400_000));
}
#[test]
fn test_calculate_with_breakdown() {
let breakdown = TotalCalculator::calculate_with_breakdown(
dec!(10_000_000),
dec!(500_000),
5,
dec!(150_000),
dec!(150_000),
dec!(200_000),
);
assert_eq!(breakdown.pinjaman, dec!(10_000_000));
assert_eq!(breakdown.total_angsuran_belum_bayar, dec!(2_500_000));
assert_eq!(breakdown.sisa_pokok, dec!(7_500_000));
assert_eq!(breakdown.asuransi, dec!(150_000));
assert_eq!(breakdown.pajak, dec!(150_000));
assert_eq!(breakdown.diskon, dec!(200_000));
assert_eq!(breakdown.total, dec!(7_400_000));
}
#[test]
fn test_calculate_zero_diskon() {
let total = TotalCalculator::calculate(
dec!(5_000_000),
dec!(250_000),
4,
dec!(75_000),
dec!(120_000),
dec!(0),
);
assert_eq!(total, dec!(3_805_000));
}
#[test]
fn test_calculate_edge_case_small_values() {
let total = TotalCalculator::calculate(
dec!(1_000_000),
dec!(100_000),
1,
dec!(15_000),
dec!(120_000),
dec!(50_000),
);
assert_eq!(total, dec!(815_000));
}
}