Skip to main content

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