Skip to main content

backbone_banking/infrastructure/persistence/
bank_reconciliation_repository.rs

1//! Repository for BankReconciliation 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 BankReconciliation SQL (4-layer rule: services orchestrate, repos hold
6//! SQL).
7//!
8//! Thin newtype over `backbone_orm::GenericCrudRepository<BankReconciliation, 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 backbone_orm::org_scope;
17
18use crate::domain::entity::BankReconciliation;
19
20/// Table name for BankReconciliation entities
21pub const TABLE_NAME: &str = "banking.bank_reconciliations";
22
23/// Repository for BankReconciliation entities.
24///
25/// All standard CRUD, soft-delete, pagination, and bulk methods are
26/// provided automatically via `Deref` to `backbone_orm::GenericCrudRepository`.
27pub struct BankReconciliationRepository(
28    backbone_orm::GenericCrudRepository<BankReconciliation, backbone_orm::SoftDelete>,
29);
30
31impl std::ops::Deref for BankReconciliationRepository {
32    type Target = backbone_orm::GenericCrudRepository<BankReconciliation, backbone_orm::SoftDelete>;
33    fn deref(&self) -> &Self::Target {
34        &self.0
35    }
36}
37
38impl BankReconciliationRepository {
39    /// Create a new repository instance.
40    pub fn new(pool: PgPool) -> Self {
41        Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
42    }
43}
44
45/// The exact row a reconciliation session writes.
46///
47/// Mirrors the raw column shape rather than the `BankReconciliation` entity: `status` binds as `&str`
48/// and is cast at the DB (`$10::recon_status`), so a bad value fails as a DB error rather than a
49/// deserialize panic. `unreconciled_count` is the EXCEPTION SNAPSHOT at sign-off — persisted so the
50/// close decision stays audit-reconstructable.
51pub struct NewReconciliationRow<'a> {
52    pub id: Uuid,
53    pub bank_account_id: Uuid,
54    pub from_date: chrono::NaiveDate,
55    pub to_date: chrono::NaiveDate,
56    pub statement_closing_balance: Decimal,
57    pub ledger_balance: Decimal,
58    pub computed_difference: Decimal,
59    pub unreconciled_count: i32,
60    /// `open` | `balanced` | `closed`.
61    pub status: &'a str,
62}
63
64/// Hand-written BankReconciliation SQL. Lives here (not in the write service) per the module's
65/// 4-layer rule. The close DECISION (open / balanced / closed) stays in the service.
66impl BankReconciliationRepository {
67    /// Record a reconciliation session in its decided state.
68    ///
69    /// A write outside any transaction: takes the pool and runs `org_scope::execute_scoped` so the
70    /// statement rides the request-dedicated connection carrying the composing service's org scope
71    /// — the same ambient-scope discipline as the exception count that decided `status`
72    /// (ADR-0029). Undecorated deployments execute plainly on the pool.
73    pub async fn insert_reconciliation(
74        &self,
75        pool: &PgPool,
76        r: &NewReconciliationRow<'_>,
77    ) -> Result<(), sqlx::Error> {
78        org_scope::execute_scoped(
79            pool,
80            sqlx::query(
81                r#"INSERT INTO banking.bank_reconciliations
82                    (id, bank_account_id, from_date, to_date, statement_closing_balance,
83                     ledger_balance, computed_difference, unreconciled_count, status)
84                   VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::recon_status)"#,
85            )
86            .bind(r.id)
87            .bind(r.bank_account_id)
88            .bind(r.from_date)
89            .bind(r.to_date)
90            .bind(r.statement_closing_balance)
91            .bind(r.ledger_balance)
92            .bind(r.computed_difference)
93            .bind(r.unreconciled_count)
94            .bind(r.status),
95        )
96        .await?;
97        Ok(())
98    }
99}
100
101backbone_core::impl_crud_repository!(
102    BankReconciliationRepository,
103    BankReconciliation,
104    soft_delete
105);