use rust_decimal::Decimal;
use sqlx::{PgConnection, PgPool, Row};
use uuid::Uuid;
use crate::domain::entity::ItemVariant;
use crate::infrastructure::persistence::item_repository::ItemHit;
pub const TABLE_NAME: &str = "catalog.item_variants";
pub struct ItemVariantRepository(
backbone_orm::GenericCrudRepository<ItemVariant, backbone_orm::SoftDelete>,
);
impl std::ops::Deref for ItemVariantRepository {
type Target = backbone_orm::GenericCrudRepository<ItemVariant, backbone_orm::SoftDelete>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl ItemVariantRepository {
pub fn new(pool: PgPool) -> Self {
Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
}
}
pub struct NewItemVariantRow<'a> {
pub id: Uuid,
pub item_id: Uuid,
pub sku: &'a str,
pub variant_label: &'a str,
pub options: &'a serde_json::Value,
pub barcode: Option<&'a str>,
pub is_default: bool,
pub weight_per_unit: Option<Decimal>,
}
impl ItemVariantRepository {
pub async fn find_variant_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 v.item_id, v.id AS variant_id, i.item_code, i.name, v.barcode, v.sku
FROM catalog.item_variants v JOIN catalog.items i ON i.id = v.item_id
WHERE (v.barcode = $1 OR v.sku = $1)
AND (v.metadata->>'deleted_at') IS NULL
LIMIT 1"#,
)
.bind(code)
.fetch_optional(executor)
.await?;
Ok(hit)
}
pub async fn find_variant_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 v.item_id, v.id AS variant_id, i.item_code, i.name, v.barcode, v.sku
FROM catalog.item_variants v JOIN catalog.items i ON i.id = v.item_id
WHERE (v.barcode = $1 OR v.sku = $1)
AND (v.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 find_item_id_for_live(
&self,
executor: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
variant_id: Uuid,
) -> Result<Option<Uuid>, sqlx::Error> {
let item_id: Option<Uuid> = sqlx::query_scalar(
"SELECT item_id FROM catalog.item_variants \
WHERE id=$1 AND (metadata->>'deleted_at') IS NULL",
)
.bind(variant_id)
.fetch_optional(executor)
.await?;
Ok(item_id)
}
pub async fn insert_variant(
&self,
conn: &mut PgConnection,
r: &NewItemVariantRow<'_>,
) -> Result<(), sqlx::Error> {
sqlx::query(
r#"INSERT INTO catalog.item_variants
(id, item_id, sku, variant_label, options, barcode, is_default, weight_per_unit, status)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,'active'::catalog_status)"#,
)
.bind(r.id)
.bind(r.item_id)
.bind(r.sku)
.bind(r.variant_label)
.bind(r.options)
.bind(r.barcode)
.bind(r.is_default)
.bind(r.weight_per_unit)
.execute(conn)
.await?;
Ok(())
}
pub async fn soft_delete_variant(
&self,
conn: &mut PgConnection,
variant_id: Uuid,
) -> Result<(), sqlx::Error> {
sqlx::query(
"UPDATE catalog.item_variants \
SET metadata = jsonb_set(metadata, '{deleted_at}', to_jsonb(now())) \
WHERE id=$1",
)
.bind(variant_id)
.execute(conn)
.await?;
Ok(())
}
pub async fn count_live_variants(
&self,
conn: &mut PgConnection,
item_id: Uuid,
) -> Result<i64, sqlx::Error> {
let remaining: i64 = sqlx::query_scalar(
"SELECT COUNT(*) FROM catalog.item_variants \
WHERE item_id=$1 AND (metadata->>'deleted_at') IS NULL",
)
.bind(item_id)
.fetch_one(conn)
.await?;
Ok(remaining)
}
}
backbone_core::impl_crud_repository!(ItemVariantRepository, ItemVariant, soft_delete);