use rust_decimal::Decimal;
use sqlx::{PgConnection, PgPool};
use uuid::Uuid;
use crate::domain::entity::Uom;
use crate::domain::services::uom_tree::UomChainNode;
pub const TABLE_NAME: &str = "catalog.uoms";
pub struct UomRepository(
backbone_orm::GenericCrudRepository<Uom, backbone_orm::SoftDelete>,
);
impl std::ops::Deref for UomRepository {
type Target = backbone_orm::GenericCrudRepository<Uom, backbone_orm::SoftDelete>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl UomRepository {
pub fn new(pool: PgPool) -> Self {
Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
}
}
pub struct NewUomRow<'a> {
pub id: Uuid,
pub code: &'a str,
pub name: &'a str,
pub uom_type: &'a str,
pub decimal_places: i32,
pub relative_uom_id: Option<Uuid>,
pub relative_factor: Option<Decimal>,
pub factor: Decimal,
}
impl UomRepository {
pub async fn exists_id(
&self,
executor: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
id: Uuid,
) -> Result<bool, sqlx::Error> {
let found: Option<Uuid> = sqlx::query_scalar(
"SELECT id FROM catalog.uoms \
WHERE id = $1 AND (metadata->>'deleted_at') IS NULL",
)
.bind(id)
.fetch_optional(executor)
.await?;
Ok(found.is_some())
}
pub async fn find_factor(
&self,
executor: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
id: Uuid,
) -> Result<Option<Decimal>, sqlx::Error> {
let factor: Option<Decimal> = sqlx::query_scalar(
"SELECT factor FROM catalog.uoms \
WHERE id = $1 AND (metadata->>'deleted_at') IS NULL",
)
.bind(id)
.fetch_optional(executor)
.await?;
Ok(factor)
}
pub async fn insert_uom(
&self,
executor: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
r: &NewUomRow<'_>,
) -> Result<(), sqlx::Error> {
sqlx::query(
r#"INSERT INTO catalog.uoms
(id, code, name, uom_type, decimal_places,
relative_uom_id, relative_factor, factor, status)
VALUES ($1,$2,$3,$4::uom_type,$5,$6,$7,$8,'active'::catalog_status)"#,
)
.bind(r.id)
.bind(r.code)
.bind(r.name)
.bind(r.uom_type)
.bind(r.decimal_places)
.bind(r.relative_uom_id)
.bind(r.relative_factor)
.bind(r.factor)
.execute(executor)
.await?;
Ok(())
}
pub async fn load_tree_chain(
&self,
executor: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
uom: Uuid,
) -> Result<Option<Vec<UomChainNode>>, sqlx::Error> {
let rows: Vec<UomChainNode> = sqlx::query_as(
r#"WITH RECURSIVE chain AS (
SELECT id, code, relative_uom_id, relative_factor, factor
FROM catalog.uoms
WHERE id = $1 AND (metadata->>'deleted_at') IS NULL
UNION
SELECT p.id, p.code, p.relative_uom_id, p.relative_factor, p.factor
FROM catalog.uoms p
JOIN chain ON p.id = chain.relative_uom_id
)
SELECT id, code, relative_uom_id, relative_factor, factor FROM chain"#,
)
.bind(uom)
.fetch_all(executor)
.await?;
Ok(if rows.is_empty() { None } else { Some(rows) })
}
pub async fn is_self_or_descendant(
&self,
conn: &mut PgConnection,
uom: Uuid,
candidate: Uuid,
) -> Result<bool, sqlx::Error> {
let hit: bool = sqlx::query_scalar(
r#"WITH RECURSIVE subtree AS (
SELECT id FROM catalog.uoms WHERE id = $1
UNION
SELECT c.id FROM catalog.uoms c JOIN subtree s ON c.relative_uom_id = s.id
)
SELECT EXISTS (SELECT 1 FROM subtree WHERE id = $2)"#,
)
.bind(uom)
.bind(candidate)
.fetch_one(conn)
.await?;
Ok(hit)
}
pub async fn set_relative(
&self,
conn: &mut PgConnection,
uom: Uuid,
relative_uom_id: Option<Uuid>,
relative_factor: Option<Decimal>,
) -> Result<(), sqlx::Error> {
sqlx::query(
"UPDATE catalog.uoms SET relative_uom_id = $2, relative_factor = $3 \
WHERE id = $1 AND (metadata->>'deleted_at') IS NULL",
)
.bind(uom)
.bind(relative_uom_id)
.bind(relative_factor)
.execute(conn)
.await?;
Ok(())
}
pub async fn recompute_factors(
&self,
executor: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
) -> Result<(), sqlx::Error> {
sqlx::query("SELECT catalog.uom_recompute_factors()")
.execute(executor)
.await?;
Ok(())
}
pub async fn find_protection(
&self,
conn: &mut PgConnection,
id: Uuid,
) -> Result<Option<bool>, sqlx::Error> {
let protected: Option<bool> = sqlx::query_scalar(
"SELECT is_protected FROM catalog.uoms \
WHERE id = $1 AND (metadata->>'deleted_at') IS NULL",
)
.bind(id)
.fetch_optional(conn)
.await?;
Ok(protected)
}
pub async fn count_live_children(
&self,
conn: &mut PgConnection,
uom: Uuid,
) -> Result<i64, sqlx::Error> {
let n: i64 = sqlx::query_scalar(
"SELECT count(*) FROM catalog.uoms \
WHERE relative_uom_id = $1 AND (metadata->>'deleted_at') IS NULL",
)
.bind(uom)
.fetch_one(conn)
.await?;
Ok(n)
}
pub async fn exists_live_item_using(
&self,
conn: &mut PgConnection,
uom: Uuid,
) -> Result<bool, sqlx::Error> {
let hit: bool = sqlx::query_scalar(
"SELECT EXISTS ( \
SELECT 1 FROM catalog.items \
WHERE default_uom_id = $1 \
AND (metadata->>'deleted_at') IS NULL )",
)
.bind(uom)
.fetch_one(conn)
.await?;
Ok(hit)
}
pub async fn soft_delete_uom(
&self,
conn: &mut PgConnection,
uom: Uuid,
) -> Result<(), sqlx::Error> {
sqlx::query(
"UPDATE catalog.uoms \
SET metadata = jsonb_set(metadata, '{deleted_at}', to_jsonb(now())) \
WHERE id = $1",
)
.bind(uom)
.execute(conn)
.await?;
Ok(())
}
}
backbone_core::impl_crud_repository!(UomRepository, Uom, soft_delete);