Skip to main content

backbone_catalog/infrastructure/persistence/
item_group_repository.rs

1//! Repository for ItemGroup entities
2//!
3//! Originally generated by metaphor-schema; now **user-owned** — this exact path is declared under
4//! `user_owned` in `metaphor.codegen.yaml`, so the generator skips it wholesale. The custom methods
5//! below hold the catalog write service's item-group SQL (4-layer rule: services orchestrate, repos
6//! hold SQL).
7//!
8//! Tenant-agnostic (ADR-0029): no statement here names a tenancy column. When the composing
9//! host mounts a request-scoped org fence, plain pool reads ride the request-dedicated
10//! scoped connection and see only in-scope rows; undecorated, they see the whole table.
11//!
12//! Thin newtype over `backbone_orm::GenericCrudRepository<ItemGroup, backbone_orm::SoftDelete>`.
13//! All standard CRUD methods are available via `Deref`.
14
15use sqlx::PgPool;
16use uuid::Uuid;
17
18use crate::domain::entity::ItemGroup;
19
20/// Table name for ItemGroup entities
21pub const TABLE_NAME: &str = "catalog.item_groups";
22
23/// Repository for ItemGroup entities.
24///
25/// All standard CRUD, soft-delete, pagination, and bulk methods are
26/// provided automatically via `Deref` to `backbone_orm::GenericCrudRepository`.
27pub struct ItemGroupRepository(
28    backbone_orm::GenericCrudRepository<ItemGroup, backbone_orm::SoftDelete>,
29);
30
31impl std::ops::Deref for ItemGroupRepository {
32    type Target = backbone_orm::GenericCrudRepository<ItemGroup, backbone_orm::SoftDelete>;
33    fn deref(&self) -> &Self::Target { &self.0 }
34}
35
36impl ItemGroupRepository {
37    /// Create a new repository instance.
38    pub fn new(pool: PgPool) -> Self {
39        Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
40    }
41}
42
43/// The exact row a validated item-group insert writes.
44pub struct NewItemGroupRow<'a> {
45    pub id: Uuid,
46    pub code: &'a str,
47    pub name: &'a str,
48    pub parent_id: Option<Uuid>,
49    pub is_group: bool,
50}
51
52/// Catalog item-group SQL. Lives here (not in the service) per the module's 4-layer rule.
53impl ItemGroupRepository {
54    /// `EXISTS` probe for a live row (replaces the prior string-built `exists_in` helper
55    /// in the write service). Used for both `parent_id` validation on create-item-group and
56    /// `item_group_id` FK validation on create-item.
57    pub async fn exists_id(&self, executor: impl sqlx::Executor<'_, Database = sqlx::Postgres>, id: Uuid) -> Result<bool, sqlx::Error> {
58        let found: Option<Uuid> = sqlx::query_scalar(
59            "SELECT id FROM catalog.item_groups \
60             WHERE id = $1 AND (metadata->>'deleted_at') IS NULL",
61        )
62        .bind(id)
63        .fetch_optional(executor)
64        .await?;
65        Ok(found.is_some())
66    }
67
68    /// Insert a validated item-group row. Unique-constraint errors propagate as
69    /// `sqlx::Error` so the service can disambiguate code duplicates.
70    pub async fn insert_item_group(
71        &self,
72        executor: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
73        r: &NewItemGroupRow<'_>,
74    ) -> Result<(), sqlx::Error> {
75        sqlx::query(
76            r#"INSERT INTO catalog.item_groups (id, code, name, parent_id, is_group, status)
77               VALUES ($1,$2,$3,$4,$5,'active'::catalog_status)"#,
78        )
79        .bind(r.id)
80        .bind(r.code)
81        .bind(r.name)
82        .bind(r.parent_id)
83        .bind(r.is_group)
84        .execute(executor)
85        .await?;
86        Ok(())
87    }
88}
89
90backbone_core::impl_crud_repository!(ItemGroupRepository, ItemGroup, soft_delete);