backbone_banking/infrastructure/persistence/bank_statement_import_repository.rs
1//! Repository for BankStatementImport 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 BankStatementImport SQL (4-layer rule: services orchestrate, repos
6//! hold SQL).
7//!
8//! Thin newtype over `backbone_orm::GenericCrudRepository<BankStatementImport, 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::BankStatementImport;
17
18/// Table name for BankStatementImport entities
19pub const TABLE_NAME: &str = "banking.bank_statement_imports";
20
21/// Repository for BankStatementImport entities.
22///
23/// All standard CRUD, soft-delete, pagination, and bulk methods are
24/// provided automatically via `Deref` to `backbone_orm::GenericCrudRepository`.
25pub struct BankStatementImportRepository(
26 backbone_orm::GenericCrudRepository<BankStatementImport, backbone_orm::SoftDelete>,
27);
28
29impl std::ops::Deref for BankStatementImportRepository {
30 type Target =
31 backbone_orm::GenericCrudRepository<BankStatementImport, backbone_orm::SoftDelete>;
32 fn deref(&self) -> &Self::Target {
33 &self.0
34 }
35}
36
37impl BankStatementImportRepository {
38 /// Create a new repository instance.
39 pub fn new(pool: PgPool) -> Self {
40 Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
41 }
42}
43
44/// The exact row a statement-import header writes.
45///
46/// Mirrors the raw column shape rather than the `BankStatementImport` entity: `source_format` binds
47/// as `&str` and is cast at the DB (`$4::source_format`), so a bad value fails as a DB error rather
48/// than a deserialize panic. `status` is not a field — the INSERT hard-codes `'imported'`. The
49/// balances carry the SERVER-rounded values (2dp half-up) the continuity check validated.
50pub struct NewStatementImportRow<'a> {
51 pub id: Uuid,
52 pub bank_account_id: Uuid,
53 pub source_format: &'a str,
54 pub statement_period_start: chrono::NaiveDate,
55 pub statement_period_end: chrono::NaiveDate,
56 pub opening_balance: Decimal,
57 pub closing_balance: Decimal,
58 pub file_ref: Option<&'a str>,
59 pub row_count: i32,
60}
61
62/// Hand-written BankStatementImport SQL. Lives here (not in the write service) per the module's
63/// 4-layer rule.
64impl BankStatementImportRepository {
65 /// Insert a statement-import header as `imported`.
66 ///
67 /// Takes the CALLER'S connection so the header and every statement line commit as one unit.
68 /// The caller has already relayed the ambient org scope onto it (`org_scope::bind_org_scope_on`)
69 /// — don't re-bind here (ADR-0029).
70 pub async fn insert_import(
71 &self,
72 conn: &mut sqlx::PgConnection,
73 i: &NewStatementImportRow<'_>,
74 ) -> Result<(), sqlx::Error> {
75 sqlx::query(
76 r#"INSERT INTO banking.bank_statement_imports
77 (id, bank_account_id, source_format, statement_period_start, statement_period_end,
78 opening_balance, closing_balance, file_ref, status, row_count)
79 VALUES ($1,$2,$3::source_format,$4,$5,$6,$7,$8,'imported'::import_status,$9)"#,
80 )
81 .bind(i.id).bind(i.bank_account_id).bind(i.source_format)
82 .bind(i.statement_period_start).bind(i.statement_period_end)
83 .bind(i.opening_balance).bind(i.closing_balance).bind(i.file_ref).bind(i.row_count)
84 .execute(conn)
85 .await?;
86 Ok(())
87 }
88}
89
90backbone_core::impl_crud_repository!(
91 BankStatementImportRepository,
92 BankStatementImport,
93 soft_delete
94);