backbone_inventory/infrastructure/persistence/stock_entry_repository.rs
1//! Repository for StockEntry 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 StockEntry (transfer) SQL
6//! (4-layer rule: services orchestrate, repositories hold the SQL).
7//!
8//! Thin newtype over `backbone_orm::GenericCrudRepository<StockEntry, backbone_orm::SoftDelete>`.
9//! All standard CRUD methods are available via `Deref`.
10
11use anyhow::Result;
12use rust_decimal::Decimal;
13use sqlx::PgPool;
14use uuid::Uuid;
15
16use crate::domain::entity::StockEntry;
17
18/// Table name for StockEntry entities
19pub const TABLE_NAME: &str = "inventory.stock_entries";
20
21/// Repository for StockEntry entities.
22///
23/// All standard CRUD, soft-delete, pagination, and bulk methods are
24/// provided automatically via `Deref` to `backbone_orm::GenericCrudRepository`.
25pub struct StockEntryRepository(
26 backbone_orm::GenericCrudRepository<StockEntry, backbone_orm::SoftDelete>,
27);
28
29impl std::ops::Deref for StockEntryRepository {
30 type Target = backbone_orm::GenericCrudRepository<StockEntry, backbone_orm::SoftDelete>;
31 fn deref(&self) -> &Self::Target { &self.0 }
32}
33
34impl StockEntryRepository {
35 /// Create a new repository instance.
36 pub fn new(pool: PgPool) -> Self {
37 Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
38 }
39}
40
41/// The exact row a transfer Stock Entry header insert writes.
42///
43/// Mirrors the raw column shape rather than the `StockEntry` entity: `stock_entry_type`, `status`
44/// and `posting_state` are literal casts. A transfer is submitted immediately and is value-neutral,
45/// so its posting_state is `not_applicable` — there is no GL post to reconcile.
46pub struct NewTransferRow<'a> {
47 pub id: Uuid,
48 pub entry_number: &'a str,
49 pub from_warehouse_id: Uuid,
50 pub to_warehouse_id: Uuid,
51 pub posting_date: chrono::NaiveDate,
52}
53
54/// Hand-written StockEntry SQL. Lives here per the module's 4-layer rule.
55impl StockEntryRepository {
56 /// Insert the transfer header.
57 ///
58 /// Takes the CALLER'S connection so the header, its items, and BOTH bins' movements commit as one
59 /// unit — a transfer that half-committed would create or destroy value. The caller has already
60 /// relayed the ambient org scope onto it — don't re-bind here.
61 ///
62 /// Returns the raw `sqlx::Error` deliberately: the caller inspects it for a unique violation to
63 /// turn a duplicate entry number into `DuplicateNumber`.
64 pub async fn insert_transfer(
65 &self,
66 conn: &mut sqlx::PgConnection,
67 t: &NewTransferRow<'_>,
68 ) -> Result<(), sqlx::Error> {
69 sqlx::query(
70 r#"INSERT INTO inventory.stock_entries
71 (id, entry_number, stock_entry_type, from_warehouse_id, to_warehouse_id,
72 posting_date, status, posting_state)
73 VALUES ($1,$2,'transfer'::stock_entry_type,$3,$4,$5,'submitted'::doc_status,'not_applicable'::gl_posting_state)"#,
74 )
75 .bind(t.id).bind(t.entry_number).bind(t.from_warehouse_id)
76 .bind(t.to_warehouse_id).bind(t.posting_date)
77 .execute(conn)
78 .await?;
79 Ok(())
80 }
81}
82
83backbone_core::impl_crud_repository!(StockEntryRepository, StockEntry, soft_delete);