use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use stateset_primitives::{ProductId, StockSnapshotId, StockSnapshotLineId};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StockSnapshotLine {
pub id: StockSnapshotLineId,
pub stock_snapshot_id: StockSnapshotId,
pub product_id: ProductId,
pub sku: String,
pub quantity_on_hand: Decimal,
pub quantity_available: Decimal,
pub location: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StockSnapshot {
pub id: StockSnapshotId,
pub label: Option<String>,
pub total_skus: u64,
pub total_units: Decimal,
pub lines: Vec<StockSnapshotLine>,
pub captured_at: DateTime<Utc>,
}
impl StockSnapshot {
#[must_use]
pub fn total_available(&self) -> Decimal {
self.lines.iter().map(|l| l.quantity_available).sum()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CaptureStockLine {
pub product_id: ProductId,
pub sku: String,
pub quantity_on_hand: Decimal,
pub quantity_available: Decimal,
pub location: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CaptureStockSnapshot {
pub label: Option<String>,
pub lines: Vec<CaptureStockLine>,
}
impl CaptureStockSnapshot {
#[must_use]
pub fn total_skus(&self) -> u64 {
self.lines.len() as u64
}
#[must_use]
pub fn total_units(&self) -> Decimal {
self.lines.iter().map(|l| l.quantity_on_hand).sum()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct StockSnapshotFilter {
pub limit: Option<u32>,
pub offset: Option<u32>,
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
fn capture() -> CaptureStockSnapshot {
CaptureStockSnapshot {
label: Some("EOM".into()),
lines: vec![
CaptureStockLine {
product_id: ProductId::new(),
sku: "SKU-1".into(),
quantity_on_hand: dec!(10),
quantity_available: dec!(8),
location: Some("MAIN".into()),
},
CaptureStockLine {
product_id: ProductId::new(),
sku: "SKU-2".into(),
quantity_on_hand: dec!(5),
quantity_available: dec!(5),
location: None,
},
],
}
}
#[test]
fn capture_totals() {
let c = capture();
assert_eq!(c.total_skus(), 2);
assert_eq!(c.total_units(), dec!(15));
}
#[test]
fn snapshot_total_available() {
let c = capture();
let snapshot = StockSnapshot {
id: StockSnapshotId::new(),
label: c.label.clone(),
total_skus: c.total_skus(),
total_units: c.total_units(),
lines: c
.lines
.iter()
.map(|l| StockSnapshotLine {
id: StockSnapshotLineId::new(),
stock_snapshot_id: StockSnapshotId::new(),
product_id: l.product_id,
sku: l.sku.clone(),
quantity_on_hand: l.quantity_on_hand,
quantity_available: l.quantity_available,
location: l.location.clone(),
})
.collect(),
captured_at: Utc::now(),
};
assert_eq!(snapshot.total_available(), dec!(13));
assert_eq!(snapshot.total_units, dec!(15));
}
}