Skip to main content

backbone_inventory/infrastructure/persistence/
stock_item_repository.rs

1//! Repository for StockItem 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 hand-written StockItem registration SQL
6//! (4-layer rule: services orchestrate, repositories hold the SQL).
7//!
8//! Thin newtype over `backbone_orm::GenericCrudRepository<StockItem, backbone_orm::SoftDelete>`.
9//! All standard CRUD methods are available via `Deref`.
10
11use anyhow::Result;
12use rust_decimal::Decimal;
13use sqlx::{PgPool, Row};
14use uuid::Uuid;
15
16use backbone_orm::org_scope;
17
18use crate::domain::entity::StockItem;
19
20/// Table name for StockItem entities
21pub const TABLE_NAME: &str = "inventory.stock_items";
22
23/// Repository for StockItem entities.
24///
25/// All standard CRUD, soft-delete, pagination, and bulk methods are
26/// provided automatically via `Deref` to `backbone_orm::GenericCrudRepository`.
27pub struct StockItemRepository(
28    backbone_orm::GenericCrudRepository<StockItem, backbone_orm::SoftDelete>,
29);
30
31impl std::ops::Deref for StockItemRepository {
32    type Target = backbone_orm::GenericCrudRepository<StockItem, backbone_orm::SoftDelete>;
33    fn deref(&self) -> &Self::Target { &self.0 }
34}
35
36impl StockItemRepository {
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 stock-item registration writes.
44///
45/// Mirrors the raw column shape rather than the `StockItem` entity: `valuation_method` is cast at
46/// the DB (`$5::valuation_method`), and `is_stock_item`/`has_batch` are literals in the statement.
47pub struct NewStockItemRow<'a> {
48    pub id: Uuid,
49    pub item_id: Uuid,
50    pub stock_uom: &'a str,
51    pub valuation_method: &'a str,
52    pub reorder_level: Decimal,
53}
54
55/// Hand-written StockItem SQL. Lives here per the module's 4-layer rule.
56impl StockItemRepository {
57    /// Register an item as stock-tracked.
58    ///
59    /// A write outside any transaction: `org_scope::execute_scoped` on the pool — the write
60    /// rides the ambient org scope's request-dedicated connection (the composing service sets
61    /// it per request) so it passes the decorator's WITH CHECK fence (ADR-0029). Undecorated
62    /// (module tests) the insert runs plain.
63    ///
64    /// Returns the raw `sqlx::Error` deliberately: the caller inspects it for a unique violation
65    /// (the composing decorator's org-scoped item arbiter) to turn an already-registered item
66    /// into a domain error.
67    pub async fn insert_stock_item(
68        &self,
69        pool: &PgPool,
70        s: &NewStockItemRow<'_>,
71    ) -> Result<(), sqlx::Error> {
72        org_scope::execute_scoped(
73            pool,
74            sqlx::query(
75                r#"INSERT INTO inventory.stock_items
76                    (id, item_id, stock_uom, is_stock_item, has_batch, valuation_method, reorder_level)
77                   VALUES ($1,$2,$3,TRUE,FALSE,$4::valuation_method,$5)"#,
78            )
79            .bind(s.id).bind(s.item_id).bind(s.stock_uom)
80            .bind(s.valuation_method).bind(s.reorder_level),
81        )
82        .await?;
83        Ok(())
84    }
85}
86
87backbone_core::impl_crud_repository!(StockItemRepository, StockItem, soft_delete);