backbone-catalog 0.7.0

Canonical product/service identity: Item, Item Group, UOM (Indonesia-first)
Documentation
//! Repository for ItemGroup entities
//!
//! Originally generated by metaphor-schema; now **user-owned** — this exact path is declared under
//! `user_owned` in `metaphor.codegen.yaml`, so the generator skips it wholesale. The custom methods
//! below hold the catalog write service's item-group SQL (4-layer rule: services orchestrate, repos
//! hold SQL).
//!
//! Tenant-agnostic (ADR-0029): no statement here names a tenancy column. When the composing
//! host mounts a request-scoped org fence, plain pool reads ride the request-dedicated
//! scoped connection and see only in-scope rows; undecorated, they see the whole table.
//!
//! Thin newtype over `backbone_orm::GenericCrudRepository<ItemGroup, backbone_orm::SoftDelete>`.
//! All standard CRUD methods are available via `Deref`.

use sqlx::PgPool;
use uuid::Uuid;

use crate::domain::entity::ItemGroup;

/// Table name for ItemGroup entities
pub const TABLE_NAME: &str = "catalog.item_groups";

/// Repository for ItemGroup entities.
///
/// All standard CRUD, soft-delete, pagination, and bulk methods are
/// provided automatically via `Deref` to `backbone_orm::GenericCrudRepository`.
pub struct ItemGroupRepository(
    backbone_orm::GenericCrudRepository<ItemGroup, backbone_orm::SoftDelete>,
);

impl std::ops::Deref for ItemGroupRepository {
    type Target = backbone_orm::GenericCrudRepository<ItemGroup, backbone_orm::SoftDelete>;
    fn deref(&self) -> &Self::Target { &self.0 }
}

impl ItemGroupRepository {
    /// Create a new repository instance.
    pub fn new(pool: PgPool) -> Self {
        Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
    }
}

/// The exact row a validated item-group insert writes.
pub struct NewItemGroupRow<'a> {
    pub id: Uuid,
    pub code: &'a str,
    pub name: &'a str,
    pub parent_id: Option<Uuid>,
    pub is_group: bool,
}

/// Catalog item-group SQL. Lives here (not in the service) per the module's 4-layer rule.
impl ItemGroupRepository {
    /// `EXISTS` probe for a live row (replaces the prior string-built `exists_in` helper
    /// in the write service). Used for both `parent_id` validation on create-item-group and
    /// `item_group_id` FK validation on create-item.
    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.item_groups \
             WHERE id = $1 AND (metadata->>'deleted_at') IS NULL",
        )
        .bind(id)
        .fetch_optional(executor)
        .await?;
        Ok(found.is_some())
    }

    /// Insert a validated item-group row. Unique-constraint errors propagate as
    /// `sqlx::Error` so the service can disambiguate code duplicates.
    pub async fn insert_item_group(
        &self,
        executor: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
        r: &NewItemGroupRow<'_>,
    ) -> Result<(), sqlx::Error> {
        sqlx::query(
            r#"INSERT INTO catalog.item_groups (id, code, name, parent_id, is_group, status)
               VALUES ($1,$2,$3,$4,$5,'active'::catalog_status)"#,
        )
        .bind(r.id)
        .bind(r.code)
        .bind(r.name)
        .bind(r.parent_id)
        .bind(r.is_group)
        .execute(executor)
        .await?;
        Ok(())
    }
}

backbone_core::impl_crud_repository!(ItemGroupRepository, ItemGroup, soft_delete);