backbone_inventory/infrastructure/persistence/
stock_reconciliation_repository.rs1use anyhow::Result;
12use rust_decimal::Decimal;
13use sqlx::{PgPool, Row};
14use uuid::Uuid;
15
16use backbone_orm::org_scope;
17
18use super::gl_voucher_repository::GlSettlementState;
19
20use crate::domain::entity::StockReconciliation;
21
22pub const TABLE_NAME: &str = "inventory.stock_reconciliations";
24
25pub struct StockReconciliationRepository(
30 backbone_orm::GenericCrudRepository<StockReconciliation, backbone_orm::SoftDelete>,
31);
32
33impl std::ops::Deref for StockReconciliationRepository {
34 type Target = backbone_orm::GenericCrudRepository<StockReconciliation, backbone_orm::SoftDelete>;
35 fn deref(&self) -> &Self::Target { &self.0 }
36}
37
38impl StockReconciliationRepository {
39 pub fn new(pool: PgPool) -> Self {
41 Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
42 }
43}
44
45pub struct NewReconciliationRow<'a> {
51 pub id: Uuid,
52 pub recon_number: &'a str,
53 pub warehouse_id: Uuid,
54 pub posting_date: chrono::NaiveDate,
55 pub currency: &'a str,
56 pub inventory_account_id: Uuid,
57 pub adjustment_account_id: Uuid,
58}
59
60pub struct ReconRepostHeaderRow {
64 pub recon_number: String,
65 pub posting_date: chrono::NaiveDate,
66 pub currency: String,
67 pub net_difference: Decimal,
68 pub inventory_account_id: Uuid,
69 pub adjustment_account_id: Uuid,
70 pub gl: GlSettlementState,
71}
72
73impl StockReconciliationRepository {
75 pub async fn insert_submitted(
84 &self,
85 conn: &mut sqlx::PgConnection,
86 r: &NewReconciliationRow<'_>,
87 ) -> Result<(), sqlx::Error> {
88 sqlx::query(
89 r#"INSERT INTO inventory.stock_reconciliations
90 (id, recon_number, warehouse_id, posting_date, currency, net_difference,
91 inventory_account_id, adjustment_account_id, status, posting_state)
92 VALUES ($1,$2,$3,$4,$5,0,$6,$7,'submitted'::doc_status,'pending'::gl_posting_state)"#,
93 )
94 .bind(r.id).bind(r.recon_number).bind(r.warehouse_id).bind(r.posting_date)
95 .bind(r.currency)
96 .bind(r.inventory_account_id).bind(r.adjustment_account_id)
97 .execute(conn)
98 .await?;
99 Ok(())
100 }
101
102 pub async fn update_net_difference(
104 &self,
105 conn: &mut sqlx::PgConnection,
106 id: Uuid,
107 net_difference: Decimal,
108 ) -> Result<(), sqlx::Error> {
109 sqlx::query("UPDATE inventory.stock_reconciliations SET net_difference=$2 WHERE id=$1")
110 .bind(id).bind(net_difference)
111 .execute(conn)
112 .await?;
113 Ok(())
114 }
115
116 pub async fn mark_not_applicable(
122 &self,
123 pool: &PgPool,
124 id: Uuid,
125 ) -> Result<(), sqlx::Error> {
126 org_scope::execute_scoped(
127 pool,
128 sqlx::query("UPDATE inventory.stock_reconciliations SET posting_state='not_applicable'::gl_posting_state WHERE id=$1")
129 .bind(id),
130 )
131 .await?;
132 Ok(())
133 }
134
135 pub async fn fetch_repost_header(
140 &self,
141 pool: &PgPool,
142 id: Uuid,
143 ) -> Result<Option<ReconRepostHeaderRow>, sqlx::Error> {
144 let row = org_scope::fetch_optional_row_scoped(
145 pool,
146 sqlx::query(
147 r#"SELECT recon_number, posting_date, currency, net_difference,
148 inventory_account_id, adjustment_account_id,
149 posting_state::text AS ps, journal_id, accounting_post_id
150 FROM inventory.stock_reconciliations WHERE id=$1 AND (metadata->>'deleted_at') IS NULL"#,
151 )
152 .bind(id),
153 )
154 .await?;
155 Ok(row.map(|h| ReconRepostHeaderRow {
156 recon_number: h.get("recon_number"),
157 posting_date: h.get("posting_date"), currency: h.get("currency"),
158 net_difference: h.get("net_difference"),
159 inventory_account_id: h.get("inventory_account_id"),
160 adjustment_account_id: h.get("adjustment_account_id"),
161 gl: GlSettlementState {
162 posting_state: h.get("ps"), journal_id: h.get("journal_id"),
163 accounting_post_id: h.get("accounting_post_id"),
164 },
165 }))
166 }
167}
168
169backbone_core::impl_crud_repository!(StockReconciliationRepository, StockReconciliation, soft_delete);