1use 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::PurchaseReceipt;
21
22pub const TABLE_NAME: &str = "inventory.purchase_receipts";
24
25pub struct PurchaseReceiptRepository(
30 backbone_orm::GenericCrudRepository<PurchaseReceipt, backbone_orm::SoftDelete>,
31);
32
33impl std::ops::Deref for PurchaseReceiptRepository {
34 type Target = backbone_orm::GenericCrudRepository<PurchaseReceipt, backbone_orm::SoftDelete>;
35 fn deref(&self) -> &Self::Target { &self.0 }
36}
37
38impl PurchaseReceiptRepository {
39 pub fn new(pool: PgPool) -> Self {
41 Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
42 }
43}
44
45pub struct NewReceiptRow<'a> {
52 pub id: Uuid,
53 pub receipt_number: &'a str,
54 pub branch_id: Option<Uuid>,
55 pub supplier_id: Uuid,
56 pub source_po_id: Option<Uuid>,
57 pub warehouse_id: Uuid,
58 pub posting_date: chrono::NaiveDate,
59 pub currency: &'a str,
60 pub total_value: Decimal,
61 pub inventory_account_id: Uuid,
62 pub grir_account_id: Uuid,
63}
64
65pub struct ReceiptSubmitHeaderRow {
67 pub receipt_number: String,
68 pub branch_id: Option<Uuid>,
69 pub warehouse_id: Uuid,
70 pub posting_date: chrono::NaiveDate,
71 pub source_po_id: Option<Uuid>,
72 pub currency: String,
73 pub status: String,
75 pub inventory_account_id: Uuid,
76 pub grir_account_id: Uuid,
77}
78
79pub struct ReceiptRepostHeaderRow {
82 pub branch_id: Option<Uuid>,
83 pub receipt_number: String,
84 pub posting_date: chrono::NaiveDate,
85 pub currency: String,
86 pub total_value: Decimal,
87 pub inventory_account_id: Uuid,
88 pub grir_account_id: Uuid,
89 pub warehouse_id: Uuid,
92 pub gl: GlSettlementState,
93}
94
95pub struct ReceiptCancelHeaderRow {
99 pub branch_id: Option<Uuid>,
100 pub warehouse_id: Uuid,
101 pub posting_date: chrono::NaiveDate,
102 pub currency: String,
103 pub receipt_number: String,
104 pub total_value: Decimal,
106 pub status: String,
109 pub inventory_account_id: Uuid,
110 pub grir_account_id: Uuid,
111 pub gl: GlSettlementState,
112 pub reversal_journal_id: Option<Uuid>,
113 pub reversal_accounting_post_id: Option<Uuid>,
114}
115
116impl PurchaseReceiptRepository {
118 pub async fn insert_draft(
126 &self,
127 conn: &mut sqlx::PgConnection,
128 r: &NewReceiptRow<'_>,
129 ) -> Result<(), sqlx::Error> {
130 sqlx::query(
131 r#"INSERT INTO inventory.purchase_receipts
132 (id, receipt_number, branch_id, supplier_id, source_po_id, warehouse_id,
133 posting_date, currency, total_value, inventory_account_id, grir_account_id,
134 status, posting_state)
135 VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,'draft'::doc_status,'pending'::gl_posting_state)"#,
136 )
137 .bind(r.id).bind(r.receipt_number).bind(r.branch_id).bind(r.supplier_id)
138 .bind(r.source_po_id).bind(r.warehouse_id).bind(r.posting_date).bind(r.currency).bind(r.total_value)
139 .bind(r.inventory_account_id).bind(r.grir_account_id)
140 .execute(conn)
141 .await?;
142 Ok(())
143 }
144
145 pub async fn fetch_submit_header(
150 &self,
151 pool: &PgPool,
152 id: Uuid,
153 ) -> Result<Option<ReceiptSubmitHeaderRow>, sqlx::Error> {
154 let row = org_scope::fetch_optional_row_scoped(
155 pool,
156 sqlx::query(
157 r#"SELECT receipt_number, branch_id, warehouse_id, posting_date, source_po_id,
158 currency, status::text AS st, inventory_account_id, grir_account_id
159 FROM inventory.purchase_receipts WHERE id=$1 AND (metadata->>'deleted_at') IS NULL"#,
160 )
161 .bind(id),
162 )
163 .await?;
164 Ok(row.map(|h| ReceiptSubmitHeaderRow {
165 receipt_number: h.get("receipt_number"),
166 branch_id: h.get("branch_id"), warehouse_id: h.get("warehouse_id"),
167 posting_date: h.get("posting_date"), source_po_id: h.get("source_po_id"),
168 currency: h.get("currency"),
169 status: h.get("st"), inventory_account_id: h.get("inventory_account_id"),
170 grir_account_id: h.get("grir_account_id"),
171 }))
172 }
173
174 pub async fn fetch_repost_header(
176 &self,
177 pool: &PgPool,
178 id: Uuid,
179 ) -> Result<Option<ReceiptRepostHeaderRow>, sqlx::Error> {
180 let row = org_scope::fetch_optional_row_scoped(
181 pool,
182 sqlx::query(
183 r#"SELECT branch_id, receipt_number, posting_date, currency, total_value,
184 inventory_account_id, grir_account_id, posting_state::text AS ps, journal_id, accounting_post_id,
185 warehouse_id
186 FROM inventory.purchase_receipts WHERE id=$1 AND (metadata->>'deleted_at') IS NULL"#,
187 )
188 .bind(id),
189 )
190 .await?;
191 Ok(row.map(|h| ReceiptRepostHeaderRow {
192 branch_id: h.get("branch_id"),
193 receipt_number: h.get("receipt_number"), posting_date: h.get("posting_date"),
194 currency: h.get("currency"),
195 total_value: h.get("total_value"), inventory_account_id: h.get("inventory_account_id"),
196 grir_account_id: h.get("grir_account_id"),
197 warehouse_id: h.get("warehouse_id"),
198 gl: GlSettlementState {
199 posting_state: h.get("ps"), journal_id: h.get("journal_id"),
200 accounting_post_id: h.get("accounting_post_id"),
201 },
202 }))
203 }
204
205 pub async fn mark_submitted(
208 &self,
209 conn: &mut sqlx::PgConnection,
210 id: Uuid,
211 ) -> Result<(), sqlx::Error> {
212 sqlx::query("UPDATE inventory.purchase_receipts SET status='submitted'::doc_status WHERE id=$1")
213 .bind(id)
214 .execute(conn)
215 .await?;
216 Ok(())
217 }
218
219 pub async fn mark_cancelled(
223 &self,
224 conn: &mut sqlx::PgConnection,
225 id: Uuid,
226 ) -> Result<(), sqlx::Error> {
227 sqlx::query("UPDATE inventory.purchase_receipts SET status='cancelled'::doc_status WHERE id=$1 AND status='submitted'::doc_status")
228 .bind(id)
229 .execute(conn)
230 .await?;
231 Ok(())
232 }
233
234 pub async fn fetch_cancel_header(
236 &self,
237 pool: &PgPool,
238 id: Uuid,
239 ) -> Result<Option<ReceiptCancelHeaderRow>, sqlx::Error> {
240 let row = org_scope::fetch_optional_row_scoped(
241 pool,
242 sqlx::query(
243 r#"SELECT branch_id, warehouse_id, posting_date, currency, receipt_number,
244 total_value, status::text AS st, inventory_account_id, grir_account_id,
245 posting_state::text AS ps, journal_id, accounting_post_id,
246 reversal_journal_id, reversal_accounting_post_id
247 FROM inventory.purchase_receipts WHERE id=$1 AND (metadata->>'deleted_at') IS NULL"#,
248 )
249 .bind(id),
250 )
251 .await?;
252 Ok(row.map(|h| ReceiptCancelHeaderRow {
253 branch_id: h.get("branch_id"),
254 warehouse_id: h.get("warehouse_id"), posting_date: h.get("posting_date"),
255 currency: h.get("currency"), receipt_number: h.get("receipt_number"),
256 total_value: h.get("total_value"),
257 status: h.get("st"), inventory_account_id: h.get("inventory_account_id"),
258 grir_account_id: h.get("grir_account_id"),
259 reversal_journal_id: h.get("reversal_journal_id"),
260 reversal_accounting_post_id: h.get("reversal_accounting_post_id"),
261 gl: GlSettlementState {
262 posting_state: h.get("ps"), journal_id: h.get("journal_id"),
263 accounting_post_id: h.get("accounting_post_id"),
264 },
265 }))
266 }
267}
268
269backbone_core::impl_crud_repository!(PurchaseReceiptRepository, PurchaseReceipt, soft_delete);