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 fn rpool(&self) -> PgPool {
79 crate::request_pool::current().unwrap_or_else(|| self.db_pool.clone())
80 }
81
82 pub async fn availability(&self, item_id: Uuid, warehouse_id: Uuid) -> Result<AvailabilityView, sqlx::Error> {
85 let row = self.bins.fetch_availability(&self.rpool(), item_id, warehouse_id).await?;
86 let (actual, reserved) = match row {
87 Some(r) => (r.actual_qty, r.reserved_qty),
88 None => (Decimal::ZERO, Decimal::ZERO),
89 };
90 Ok(AvailabilityView { item_id, warehouse_id, actual_qty: actual, reserved_qty: reserved, available_qty: actual - reserved })
91 }
92
93 pub async fn availability_across_warehouses(&self, item_id: Uuid) -> Result<Vec<AvailabilityView>, sqlx::Error> {
95 let rows = self.bins.fetch_availability_across_warehouses(&self.rpool(), item_id).await?;
96 Ok(rows.into_iter().map(|r| {
97 let actual = r.actual_qty;
98 let reserved = r.reserved_qty;
99 AvailabilityView { item_id, warehouse_id: r.warehouse_id, actual_qty: actual, reserved_qty: reserved, available_qty: actual - reserved }
100 }).collect())
101 }
102
103 pub async fn stock_balance(&self, item_id: Uuid, warehouse_id: Uuid) -> Result<Option<StockBalance>, sqlx::Error> {
105 let row = self.bins.fetch_balance(&self.rpool(), item_id, warehouse_id).await?;
106 Ok(row.map(|r| StockBalance {
107 item_id, warehouse_id,
108 actual_qty: r.actual_qty, valuation_rate: r.valuation_rate, stock_value: r.stock_value,
109 }))
110 }
111
112 pub async fn quant_availability(&self, item_id: Uuid, location_id: Uuid) -> Result<QuantAvailability, sqlx::Error> {
117 let row = self.quants.fetch_on_hand(&self.rpool(), item_id, location_id).await?;
118 Ok(QuantAvailability {
119 item_id,
120 location_id,
121 on_hand_qty: row.on_hand_qty,
122 reserved_qty: row.reserved_qty,
123 available_qty: row.on_hand_qty - row.reserved_qty,
124 quant_count: row.quant_count,
125 })
126 }
127}