use rust_decimal::Decimal;
use sqlx::{PgConnection, PgPool, Row};
use uuid::Uuid;
use crate::domain::entity::Item;
pub const TABLE_NAME: &str = "catalog.items";
pub struct ItemRepository(
backbone_orm::GenericCrudRepository<Item, backbone_orm::SoftDelete>,
);
impl std::ops::Deref for ItemRepository {
type Target = backbone_orm::GenericCrudRepository<Item, backbone_orm::SoftDelete>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl ItemRepository {
pub fn new(pool: PgPool) -> Self {
Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
}
}
#[derive(Debug, Clone, serde::Serialize, sqlx::FromRow)]
pub struct ItemHit {
pub item_id: Uuid,
pub variant_id: Option<Uuid>,
pub item_code: String,
pub name: String,
pub barcode: Option<String>,
pub sku: Option<String>,
}
pub struct NewItemRow<'a> {
pub id: Uuid,
pub item_code: &'a str,
pub name: &'a str,
pub description: Option<&'a str>,
pub barcode: Option<&'a str>,
pub brand_id: Option<Uuid>,
pub item_group_id: Uuid,
pub default_uom_id: Uuid,
pub item_type: &'a str,
pub is_sales_item: bool,
pub is_purchase_item: bool,
pub is_stock_item: bool,
pub hsn_code: Option<&'a str>,
pub is_taxable: bool,
pub weight_per_unit: Option<Decimal>,
pub standard_cost: Option<Decimal>,
pub tags: &'a serde_json::Value,
pub data: &'a serde_json::Value,
}
impl ItemRepository {
pub async fn find_by_scan_code(
&self,
executor: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
code: &str,
) -> Result<Option<ItemHit>, sqlx::Error> {
let hit = sqlx::query_as::<_, ItemHit>(
r#"SELECT id AS item_id, NULL::uuid AS variant_id, item_code, name, barcode, NULL::text AS sku
FROM catalog.items
WHERE (barcode = $1 OR item_code = $1)
AND (metadata->>'deleted_at') IS NULL
LIMIT 1"#,
)
.bind(code)
.fetch_optional(executor)
.await?;
Ok(hit)
}
pub async fn find_by_scan_code_scoped(
&self,
pool: &PgPool,
code: &str,
) -> Result<Option<ItemHit>, sqlx::Error> {
let row = backbone_orm::org_scope::fetch_optional_row_scoped(
pool,
sqlx::query(
r#"SELECT id AS item_id, NULL::uuid AS variant_id, item_code, name, barcode, NULL::text AS sku
FROM catalog.items
WHERE (barcode = $1 OR item_code = $1)
AND (metadata->>'deleted_at') IS NULL
LIMIT 1"#,
)
.bind(code),
)
.await?;
Ok(row.map(|r| ItemHit {
item_id: r.get("item_id"),
variant_id: r.get("variant_id"),
item_code: r.get("item_code"),
name: r.get("name"),
barcode: r.get("barcode"),
sku: r.get("sku"),
}))
}
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.items \
WHERE id = $1 AND (metadata->>'deleted_at') IS NULL",
)
.bind(id)
.fetch_optional(executor)
.await?;
Ok(found.is_some())
}
pub async fn insert_item(
&self,
executor: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
r: &NewItemRow<'_>,
) -> Result<(), sqlx::Error> {
sqlx::query(
r#"INSERT INTO catalog.items
(id, item_code, name, description, barcode, brand_id, item_group_id,
default_uom_id, item_type, is_sales_item, is_purchase_item, is_stock_item,
hsn_code, is_taxable, weight_per_unit, standard_cost, tags, data, status)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::item_type,$10,$11,$12,$13,$14,$15,$16,$17,$18,'active'::catalog_status)"#,
)
.bind(r.id)
.bind(r.item_code)
.bind(r.name)
.bind(r.description)
.bind(r.barcode)
.bind(r.brand_id)
.bind(r.item_group_id)
.bind(r.default_uom_id)
.bind(r.item_type)
.bind(r.is_sales_item)
.bind(r.is_purchase_item)
.bind(r.is_stock_item)
.bind(r.hsn_code)
.bind(r.is_taxable)
.bind(r.weight_per_unit)
.bind(r.standard_cost)
.bind(r.tags)
.bind(r.data)
.execute(executor)
.await?;
Ok(())
}
pub async fn set_has_variants_true(
&self,
conn: &mut PgConnection,
item_id: Uuid,
) -> Result<(), sqlx::Error> {
sqlx::query("UPDATE catalog.items SET has_variants = TRUE WHERE id = $1")
.bind(item_id)
.execute(conn)
.await?;
Ok(())
}
pub async fn find_status(
&self,
conn: &mut PgConnection,
item_id: Uuid,
) -> Result<Option<crate::domain::entity::CatalogStatus>, sqlx::Error> {
Ok(sqlx::query_scalar("SELECT status FROM catalog.items WHERE id = $1")
.bind(item_id)
.fetch_optional(conn)
.await?)
}
pub async fn set_status(
&self,
conn: &mut PgConnection,
item_id: Uuid,
status: crate::domain::entity::CatalogStatus,
) -> Result<(), sqlx::Error> {
sqlx::query("UPDATE catalog.items SET status = $1 WHERE id = $2")
.bind(status)
.bind(item_id)
.execute(conn)
.await?;
Ok(())
}
pub async fn set_has_variants_false(
&self,
conn: &mut PgConnection,
item_id: Uuid,
) -> Result<(), sqlx::Error> {
sqlx::query("UPDATE catalog.items SET has_variants = FALSE WHERE id=$1")
.bind(item_id)
.execute(conn)
.await?;
Ok(())
}
}
backbone_core::impl_crud_repository!(ItemRepository, Item, soft_delete);