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);