use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CloseBooksLine {
pub sku: String,
pub cost: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CloseBooksInventory {
pub sku: String,
pub quantity: Decimal,
pub valuation: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZeroCostLine {
pub sku: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZeroValuationItem {
pub sku: String,
pub quantity: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CloseBooksReport {
pub zero_cost_lines: Vec<ZeroCostLine>,
pub zero_valuation_items: Vec<ZeroValuationItem>,
pub zero_cost_count: u64,
pub zero_valuation_count: u64,
pub is_ready: bool,
}
#[must_use]
pub fn compute_close_books_readiness(
lines: &[CloseBooksLine],
inventory: &[CloseBooksInventory],
) -> CloseBooksReport {
let zero_cost_lines: Vec<ZeroCostLine> = lines
.iter()
.filter(|l| l.cost <= Decimal::ZERO)
.map(|l| ZeroCostLine { sku: l.sku.clone() })
.collect();
let zero_valuation_items: Vec<ZeroValuationItem> = inventory
.iter()
.filter(|i| i.quantity > Decimal::ZERO && i.valuation <= Decimal::ZERO)
.map(|i| ZeroValuationItem { sku: i.sku.clone(), quantity: i.quantity })
.collect();
let zero_cost_count = zero_cost_lines.len() as u64;
let zero_valuation_count = zero_valuation_items.len() as u64;
let is_ready = zero_cost_count == 0 && zero_valuation_count == 0;
CloseBooksReport {
zero_cost_lines,
zero_valuation_items,
zero_cost_count,
zero_valuation_count,
is_ready,
}
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
fn line(sku: &str, cost: Decimal) -> CloseBooksLine {
CloseBooksLine { sku: sku.into(), cost }
}
fn inv(sku: &str, qty: Decimal, val: Decimal) -> CloseBooksInventory {
CloseBooksInventory { sku: sku.into(), quantity: qty, valuation: val }
}
#[test]
fn flags_zero_cost_lines() {
let report = compute_close_books_readiness(
&[line("a", dec!(10)), line("b", dec!(0)), line("c", dec!(5))],
&[],
);
assert_eq!(report.zero_cost_count, 1);
assert_eq!(report.zero_cost_lines[0].sku, "b");
assert!(!report.is_ready);
}
#[test]
fn flags_zero_valuation_on_hand() {
let report = compute_close_books_readiness(
&[],
&[
inv("x", dec!(10), dec!(0)), inv("y", dec!(0), dec!(0)), inv("z", dec!(5), dec!(100)), ],
);
assert_eq!(report.zero_valuation_count, 1);
assert_eq!(report.zero_valuation_items[0].sku, "x");
assert_eq!(report.zero_valuation_items[0].quantity, dec!(10));
}
#[test]
fn ready_when_clean() {
let report =
compute_close_books_readiness(&[line("a", dec!(10))], &[inv("x", dec!(5), dec!(100))]);
assert!(report.is_ready);
assert_eq!(report.zero_cost_count, 0);
assert_eq!(report.zero_valuation_count, 0);
}
#[test]
fn empty_is_ready() {
let report = compute_close_books_readiness(&[], &[]);
assert!(report.is_ready);
}
}