Skip to main content

backbone_banking/infrastructure/persistence/
bank_account_repository.rs

1//! Repository for BankAccount 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 BankAccount SQL (4-layer rule: services orchestrate, repos hold SQL).
6//!
7//! Thin newtype over `backbone_orm::GenericCrudRepository<BankAccount, backbone_orm::SoftDelete>`.
8//! All standard CRUD methods are available via `Deref`.
9
10use anyhow::Result;
11use sqlx::PgPool;
12use uuid::Uuid;
13
14use backbone_orm::org_scope;
15
16use crate::domain::entity::BankAccount;
17
18/// Table name for BankAccount entities
19pub const TABLE_NAME: &str = "banking.bank_accounts";
20
21/// Repository for BankAccount entities.
22///
23/// All standard CRUD, soft-delete, pagination, and bulk methods are
24/// provided automatically via `Deref` to `backbone_orm::GenericCrudRepository`.
25pub struct BankAccountRepository(
26    backbone_orm::GenericCrudRepository<BankAccount, backbone_orm::SoftDelete>,
27);
28
29impl std::ops::Deref for BankAccountRepository {
30    type Target = backbone_orm::GenericCrudRepository<BankAccount, backbone_orm::SoftDelete>;
31    fn deref(&self) -> &Self::Target {
32        &self.0
33    }
34}
35
36impl BankAccountRepository {
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 bank-account registration writes.
44///
45/// Mirrors the raw column shape rather than the `BankAccount` entity: `account_type` binds as `&str`
46/// and is cast at the DB (`$10::bank_account_type`), so a bad value fails as a DB error rather than a
47/// deserialize panic. `is_default`/`status` are not fields — the INSERT hard-codes `false`/`'active'`.
48/// `currency`/`account_type` are already defaulted by the caller, so they are not optional here.
49pub struct NewBankAccountRow<'a> {
50    pub id: Uuid,
51    pub branch_id: Option<Uuid>,
52    pub bank_id: Uuid,
53    pub account_name: &'a str,
54    pub account_number: &'a str,
55    /// The GL account the bank balance itself lives in.
56    pub gl_account_id: Uuid,
57    /// The account a settlement debits/credits until the statement confirms it — the account bank
58    /// clearing nets to zero.
59    pub clearing_account_id: Uuid,
60    pub currency: &'a str,
61    pub account_type: &'a str,
62}
63
64/// Hand-written BankAccount SQL. Lives here (not in the write service) per the module's 4-layer rule.
65impl BankAccountRepository {
66    /// Register a bank account.
67    ///
68    /// A write outside any transaction: takes the pool and runs `org_scope::execute_scoped` — the
69    /// same ambient-scope discipline as [`super::BankRepository::insert_bank`] (ADR-0029).
70    pub async fn insert_bank_account(
71        &self,
72        pool: &PgPool,
73        a: &NewBankAccountRow<'_>,
74    ) -> Result<(), sqlx::Error> {
75        org_scope::execute_scoped(
76            pool,
77            sqlx::query(
78                r#"INSERT INTO banking.bank_accounts
79                    (id, branch_id, bank_id, account_name, account_number, gl_account_id,
80                     clearing_account_id, currency, account_type, is_default, status)
81                   VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::bank_account_type,false,'active')"#,
82            )
83            .bind(a.id).bind(a.branch_id).bind(a.bank_id).bind(a.account_name)
84            .bind(a.account_number).bind(a.gl_account_id).bind(a.clearing_account_id)
85            .bind(a.currency).bind(a.account_type),
86        )
87        .await?;
88        Ok(())
89    }
90}
91
92backbone_core::impl_crud_repository!(BankAccountRepository, BankAccount, soft_delete);