use anyhow::Result;
use backbone_orm::org_scope;
use rust_decimal::Decimal;
use sqlx::{PgPool, Row};
use uuid::Uuid;
use crate::domain::entity::Bin;
pub const TABLE_NAME: &str = "inventory.bins";
pub struct BinRepository(
backbone_orm::GenericCrudRepository<Bin, backbone_orm::SoftDelete>,
);
impl std::ops::Deref for BinRepository {
type Target = backbone_orm::GenericCrudRepository<Bin, backbone_orm::SoftDelete>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl BinRepository {
pub fn new(pool: PgPool) -> Self {
Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
}
}
pub struct BinBalanceRow {
pub actual_qty: Decimal,
pub valuation_rate: Decimal,
pub stock_value: Decimal,
}
impl BinRepository {
pub async fn lock_or_init(
&self,
conn: &mut sqlx::PgConnection,
item_id: Uuid,
warehouse_id: Uuid,
) -> Result<BinBalanceRow, sqlx::Error> {
let row = sqlx::query(
r#"SELECT actual_qty, valuation_rate, stock_value FROM inventory.bins
WHERE item_id=$1 AND warehouse_id=$2 AND (metadata->>'deleted_at') IS NULL
FOR UPDATE"#,
)
.bind(item_id).bind(warehouse_id)
.fetch_optional(&mut *conn)
.await?;
if let Some(r) = row {
return Ok(BinBalanceRow {
actual_qty: r.get("actual_qty"),
valuation_rate: r.get("valuation_rate"),
stock_value: r.get("stock_value"),
});
}
sqlx::query(
r#"INSERT INTO inventory.bins (id, item_id, warehouse_id, actual_qty, reserved_qty, valuation_rate, stock_value)
VALUES ($1,$2,$3,0,0,0,0)"#,
)
.bind(Uuid::new_v4()).bind(item_id).bind(warehouse_id)
.execute(&mut *conn)
.await?;
Ok(BinBalanceRow {
actual_qty: Decimal::ZERO, valuation_rate: Decimal::ZERO, stock_value: Decimal::ZERO,
})
}
pub async fn update_balance(
&self,
conn: &mut sqlx::PgConnection,
item_id: Uuid,
warehouse_id: Uuid,
actual_qty: Decimal,
valuation_rate: Decimal,
stock_value: Decimal,
) -> Result<(), sqlx::Error> {
sqlx::query(
r#"UPDATE inventory.bins SET actual_qty=$3, valuation_rate=$4, stock_value=$5
WHERE item_id=$1 AND warehouse_id=$2 AND (metadata->>'deleted_at') IS NULL"#,
)
.bind(item_id).bind(warehouse_id)
.bind(actual_qty).bind(valuation_rate).bind(stock_value)
.execute(conn)
.await?;
Ok(())
}
}
pub struct BinAvailabilityRow {
pub actual_qty: Decimal,
pub reserved_qty: Decimal,
}
pub struct BinWarehouseAvailabilityRow {
pub warehouse_id: Uuid,
pub actual_qty: Decimal,
pub reserved_qty: Decimal,
}
impl BinRepository {
pub async fn fetch_availability(
&self,
pool: &PgPool,
item_id: Uuid,
warehouse_id: Uuid,
) -> Result<Option<BinAvailabilityRow>, sqlx::Error> {
let row = org_scope::fetch_optional_row_scoped(
pool,
sqlx::query(
r#"SELECT actual_qty, reserved_qty FROM inventory.bins
WHERE item_id=$1 AND warehouse_id=$2 AND (metadata->>'deleted_at') IS NULL"#,
)
.bind(item_id).bind(warehouse_id),
)
.await?;
Ok(row.map(|r| BinAvailabilityRow {
actual_qty: r.get("actual_qty"),
reserved_qty: r.get("reserved_qty"),
}))
}
pub async fn fetch_availability_across_warehouses(
&self,
pool: &PgPool,
item_id: Uuid,
) -> Result<Vec<BinWarehouseAvailabilityRow>, sqlx::Error> {
let rows = org_scope::fetch_all_rows_scoped(
pool,
sqlx::query(
r#"SELECT warehouse_id, actual_qty, reserved_qty FROM inventory.bins
WHERE item_id=$1 AND (metadata->>'deleted_at') IS NULL
ORDER BY warehouse_id"#,
)
.bind(item_id),
)
.await?;
Ok(rows
.into_iter()
.map(|r| BinWarehouseAvailabilityRow {
warehouse_id: r.get("warehouse_id"),
actual_qty: r.get("actual_qty"),
reserved_qty: r.get("reserved_qty"),
})
.collect())
}
pub async fn fetch_balance(
&self,
pool: &PgPool,
item_id: Uuid,
warehouse_id: Uuid,
) -> Result<Option<BinBalanceRow>, sqlx::Error> {
let row = org_scope::fetch_optional_row_scoped(
pool,
sqlx::query(
r#"SELECT actual_qty, valuation_rate, stock_value FROM inventory.bins
WHERE item_id=$1 AND warehouse_id=$2 AND (metadata->>'deleted_at') IS NULL"#,
)
.bind(item_id).bind(warehouse_id),
)
.await?;
Ok(row.map(|r| BinBalanceRow {
actual_qty: r.get("actual_qty"),
valuation_rate: r.get("valuation_rate"),
stock_value: r.get("stock_value"),
}))
}
}
backbone_core::impl_crud_repository!(BinRepository, Bin, soft_delete);