use anyhow::Result;
use rust_decimal::Decimal;
use sqlx::{PgPool, Row};
use uuid::Uuid;
use backbone_orm::org_scope;
use crate::domain::entity::BankClearance;
pub const TABLE_NAME: &str = "banking.bank_clearances";
pub struct BankClearanceRepository(
backbone_orm::GenericCrudRepository<BankClearance, backbone_orm::SoftDelete>,
);
impl std::ops::Deref for BankClearanceRepository {
type Target = backbone_orm::GenericCrudRepository<BankClearance, backbone_orm::SoftDelete>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl BankClearanceRepository {
pub fn new(pool: PgPool) -> Self {
Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
}
}
pub struct NewClearanceRow<'a> {
pub id: Uuid,
pub bank_transaction_id: Uuid,
pub matched_source_type: &'a str,
pub matched_source_id: Uuid,
pub matched_amount: Decimal,
pub match_method: &'a str,
pub clearance_date: chrono::NaiveDate,
pub accounting_post_id: Uuid,
pub journal_id: Uuid,
}
pub struct NewChargeClearanceRow {
pub id: Uuid,
pub bank_transaction_id: Uuid,
pub charge_account_id: Uuid,
pub matched_amount: Decimal,
pub clearance_date: chrono::NaiveDate,
pub accounting_post_id: Uuid,
pub journal_id: Uuid,
}
impl BankClearanceRepository {
pub async fn sums_cleared_for_payments(
&self,
pool: &PgPool,
payment_ids: &[Uuid],
) -> Result<std::collections::HashMap<Uuid, Decimal>, sqlx::Error> {
if payment_ids.is_empty() {
return Ok(std::collections::HashMap::new());
}
let query = sqlx::query(
r#"SELECT matched_source_id, SUM(matched_amount) AS cleared
FROM banking.bank_clearances
WHERE matched_source_type='payment' AND matched_source_id = ANY($1)
GROUP BY matched_source_id"#,
)
.bind(payment_ids);
let rows = if let Some(scope) = org_scope::current_org_scope() {
let mut tx = pool.begin().await?;
org_scope::bind_org_scope_on(&mut tx, &scope).await?;
backbone_orm::audit_context::relay_ambient_audit_on(&mut tx).await?;
let rows = query.fetch_all(&mut *tx).await?;
tx.commit().await?; rows
} else {
query.fetch_all(pool).await?
};
Ok(rows
.into_iter()
.map(|r| {
let id: Uuid = r.get("matched_source_id");
let cleared: Decimal = r.get("cleared");
(id, cleared)
})
.collect())
}
pub async fn lock_settlement(
&self,
conn: &mut sqlx::PgConnection,
matched_source_id: Uuid,
) -> Result<(), sqlx::Error> {
sqlx::query("SELECT pg_advisory_xact_lock(hashtextextended($1::text, 0))")
.bind(matched_source_id)
.execute(conn)
.await?;
Ok(())
}
pub async fn sum_cleared_against_settlement(
&self,
conn: &mut sqlx::PgConnection,
matched_source_type: &str,
matched_source_id: Uuid,
) -> Result<Decimal, sqlx::Error> {
sqlx::query_scalar(
"SELECT COALESCE(SUM(matched_amount),0) FROM banking.bank_clearances WHERE matched_source_type=$1::matched_source_type AND matched_source_id=$2 AND (metadata->>'deleted_at') IS NULL",
)
.bind(matched_source_type).bind(matched_source_id)
.fetch_one(conn)
.await
}
pub async fn insert_clearance(
&self,
conn: &mut sqlx::PgConnection,
c: &NewClearanceRow<'_>,
) -> Result<(), sqlx::Error> {
sqlx::query(
r#"INSERT INTO banking.bank_clearances
(id, bank_transaction_id, matched_source_type, matched_source_id,
matched_amount, match_method, clearance_date, accounting_post_id, journal_id)
VALUES ($1,$2,$3::matched_source_type,$4,$5,$6::match_method,$7,$8,$9)"#,
)
.bind(c.id)
.bind(c.bank_transaction_id)
.bind(c.matched_source_type)
.bind(c.matched_source_id)
.bind(c.matched_amount)
.bind(c.match_method)
.bind(c.clearance_date)
.bind(c.accounting_post_id)
.bind(c.journal_id)
.execute(conn)
.await?;
Ok(())
}
pub async fn insert_charge_clearance(
&self,
conn: &mut sqlx::PgConnection,
c: &NewChargeClearanceRow,
) -> Result<(), sqlx::Error> {
sqlx::query(
r#"INSERT INTO banking.bank_clearances
(id, bank_transaction_id, matched_source_type, matched_source_id,
matched_amount, match_method, clearance_date, accounting_post_id, journal_id)
VALUES ($1,$2,'charge'::matched_source_type,$3,$4,'manual'::match_method,$5,$6,$7)"#,
)
.bind(c.id).bind(c.bank_transaction_id).bind(c.charge_account_id)
.bind(c.matched_amount).bind(c.clearance_date).bind(c.accounting_post_id).bind(c.journal_id)
.execute(conn)
.await?;
Ok(())
}
}
backbone_core::impl_crud_repository!(BankClearanceRepository, BankClearance, soft_delete);