backbone_inventory/application/service/
inventory_read.rs1use std::sync::Arc;
14
15use rust_decimal::Decimal;
16use serde::{Deserialize, Serialize};
17use sqlx::PgPool;
18use uuid::Uuid;
19
20use crate::infrastructure::persistence::{BinRepository, QuantRepository};
21
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
24pub struct AvailabilityView {
25 pub item_id: Uuid,
26 pub warehouse_id: Uuid,
27 pub actual_qty: Decimal,
28 pub reserved_qty: Decimal,
29 pub available_qty: Decimal,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
37pub struct QuantAvailability {
38 pub item_id: Uuid,
39 pub location_id: Uuid,
40 pub on_hand_qty: Decimal,
41 pub reserved_qty: Decimal,
42 pub available_qty: Decimal,
43 pub quant_count: i64,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
48pub struct StockBalance {
49 pub item_id: Uuid,
50 pub warehouse_id: Uuid,
51 pub actual_qty: Decimal,
52 pub valuation_rate: Decimal,
53 pub stock_value: Decimal,
54}
55
56#[derive(Clone)]
60pub struct InventoryReadService {
61 db_pool: PgPool,
62 bins: Arc<BinRepository>,
63 quants: Arc<QuantRepository>,
64}
65
66impl InventoryReadService {
67 pub fn new(db_pool: PgPool) -> Self {
68 Self {
69 bins: Arc::new(BinRepository::new(db_pool.clone())),
70 quants: Arc::new(QuantRepository::new(db_pool.clone())),
71 db_pool,
72 }
73 }
74
75 pub async fn availability(&self, item_id: Uuid, warehouse_id: Uuid) -> Result<AvailabilityView, sqlx::Error> {
78 let row = self.bins.fetch_availability(&self.db_pool, item_id, warehouse_id).await?;
79 let (actual, reserved) = match row {
80 Some(r) => (r.actual_qty, r.reserved_qty),
81 None => (Decimal::ZERO, Decimal::ZERO),
82 };
83 Ok(AvailabilityView { item_id, warehouse_id, actual_qty: actual, reserved_qty: reserved, available_qty: actual - reserved })
84 }
85
86 pub async fn availability_across_warehouses(&self, item_id: Uuid) -> Result<Vec<AvailabilityView>, sqlx::Error> {
88 let rows = self.bins.fetch_availability_across_warehouses(&self.db_pool, item_id).await?;
89 Ok(rows.into_iter().map(|r| {
90 let actual = r.actual_qty;
91 let reserved = r.reserved_qty;
92 AvailabilityView { item_id, warehouse_id: r.warehouse_id, actual_qty: actual, reserved_qty: reserved, available_qty: actual - reserved }
93 }).collect())
94 }
95
96 pub async fn stock_balance(&self, item_id: Uuid, warehouse_id: Uuid) -> Result<Option<StockBalance>, sqlx::Error> {
98 let row = self.bins.fetch_balance(&self.db_pool, item_id, warehouse_id).await?;
99 Ok(row.map(|r| StockBalance {
100 item_id, warehouse_id,
101 actual_qty: r.actual_qty, valuation_rate: r.valuation_rate, stock_value: r.stock_value,
102 }))
103 }
104
105 pub async fn quant_availability(&self, item_id: Uuid, location_id: Uuid) -> Result<QuantAvailability, sqlx::Error> {
110 let row = self.quants.fetch_on_hand(&self.db_pool, item_id, location_id).await?;
111 Ok(QuantAvailability {
112 item_id,
113 location_id,
114 on_hand_qty: row.on_hand_qty,
115 reserved_qty: row.reserved_qty,
116 available_qty: row.on_hand_qty - row.reserved_qty,
117 quant_count: row.quant_count,
118 })
119 }
120}