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::DeliveryNote;
21
22pub const TABLE_NAME: &str = "inventory.delivery_notes";
24
25pub struct DeliveryNoteRepository(
30 backbone_orm::GenericCrudRepository<DeliveryNote, backbone_orm::SoftDelete>,
31);
32
33impl std::ops::Deref for DeliveryNoteRepository {
34 type Target = backbone_orm::GenericCrudRepository<DeliveryNote, backbone_orm::SoftDelete>;
35 fn deref(&self) -> &Self::Target { &self.0 }
36}
37
38impl DeliveryNoteRepository {
39 pub fn new(pool: PgPool) -> Self {
41 Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
42 }
43}
44
45pub struct NewDeliveryRow<'a> {
51 pub id: Uuid,
52 pub delivery_number: &'a str,
53 pub branch_id: Option<Uuid>,
54 pub customer_id: Uuid,
55 pub source_so_id: Option<Uuid>,
56 pub warehouse_id: Uuid,
57 pub posting_date: chrono::NaiveDate,
58 pub currency: &'a str,
59 pub cogs_account_id: Uuid,
60 pub inventory_account_id: Uuid,
61}
62
63pub struct DeliverySubmitHeaderRow {
65 pub delivery_number: String,
66 pub branch_id: Option<Uuid>,
67 pub warehouse_id: Uuid,
68 pub posting_date: chrono::NaiveDate,
69 pub source_so_id: Option<Uuid>,
70 pub currency: String,
71 pub status: String,
73 pub cogs_account_id: Uuid,
74 pub inventory_account_id: Uuid,
75}
76
77pub struct DeliveryRepostHeaderRow {
79 pub branch_id: Option<Uuid>,
80 pub delivery_number: String,
81 pub posting_date: chrono::NaiveDate,
82 pub currency: String,
83 pub total_cogs: Decimal,
84 pub cogs_account_id: Uuid,
85 pub inventory_account_id: Uuid,
86 pub warehouse_id: Uuid,
89 pub gl: GlSettlementState,
90}
91
92pub struct DeliveryCancelHeaderRow {
96 pub branch_id: Option<Uuid>,
97 pub warehouse_id: Uuid,
98 pub posting_date: chrono::NaiveDate,
99 pub currency: String,
100 pub delivery_number: String,
101 pub total_cogs: Decimal,
103 pub status: String,
105 pub cogs_account_id: Uuid,
106 pub inventory_account_id: Uuid,
107 pub gl: GlSettlementState,
108 pub reversal_journal_id: Option<Uuid>,
109 pub reversal_accounting_post_id: Option<Uuid>,
110}
111
112impl DeliveryNoteRepository {
114 pub async fn insert_draft(
122 &self,
123 conn: &mut sqlx::PgConnection,
124 d: &NewDeliveryRow<'_>,
125 ) -> Result<(), sqlx::Error> {
126 sqlx::query(
127 r#"INSERT INTO inventory.delivery_notes
128 (id, delivery_number, branch_id, customer_id, source_so_id, warehouse_id,
129 posting_date, currency, total_cogs, cogs_account_id, inventory_account_id, status, posting_state)
130 VALUES ($1,$2,$3,$4,$5,$6,$7,$8,0,$9,$10,'draft'::doc_status,'pending'::gl_posting_state)"#,
131 )
132 .bind(d.id).bind(d.delivery_number).bind(d.branch_id).bind(d.customer_id)
133 .bind(d.source_so_id).bind(d.warehouse_id).bind(d.posting_date).bind(d.currency)
134 .bind(d.cogs_account_id).bind(d.inventory_account_id)
135 .execute(conn)
136 .await?;
137 Ok(())
138 }
139
140 pub async fn fetch_submit_header(
145 &self,
146 pool: &PgPool,
147 id: Uuid,
148 ) -> Result<Option<DeliverySubmitHeaderRow>, sqlx::Error> {
149 let row = org_scope::fetch_optional_row_scoped(
150 pool,
151 sqlx::query(
152 r#"SELECT delivery_number, branch_id, warehouse_id, posting_date, source_so_id,
153 currency, status::text AS st, cogs_account_id, inventory_account_id
154 FROM inventory.delivery_notes WHERE id=$1 AND (metadata->>'deleted_at') IS NULL"#,
155 )
156 .bind(id),
157 )
158 .await?;
159 Ok(row.map(|h| DeliverySubmitHeaderRow {
160 delivery_number: h.get("delivery_number"),
161 branch_id: h.get("branch_id"), warehouse_id: h.get("warehouse_id"),
162 posting_date: h.get("posting_date"), source_so_id: h.get("source_so_id"),
163 currency: h.get("currency"),
164 status: h.get("st"), cogs_account_id: h.get("cogs_account_id"),
165 inventory_account_id: h.get("inventory_account_id"),
166 }))
167 }
168
169 pub async fn fetch_repost_header(
171 &self,
172 pool: &PgPool,
173 id: Uuid,
174 ) -> Result<Option<DeliveryRepostHeaderRow>, sqlx::Error> {
175 let row = org_scope::fetch_optional_row_scoped(
176 pool,
177 sqlx::query(
178 r#"SELECT branch_id, delivery_number, posting_date, currency, total_cogs,
179 cogs_account_id, inventory_account_id, posting_state::text AS ps, journal_id, accounting_post_id,
180 warehouse_id
181 FROM inventory.delivery_notes WHERE id=$1 AND (metadata->>'deleted_at') IS NULL"#,
182 )
183 .bind(id),
184 )
185 .await?;
186 Ok(row.map(|h| DeliveryRepostHeaderRow {
187 branch_id: h.get("branch_id"),
188 delivery_number: h.get("delivery_number"), posting_date: h.get("posting_date"),
189 currency: h.get("currency"),
190 total_cogs: h.get("total_cogs"), cogs_account_id: h.get("cogs_account_id"),
191 inventory_account_id: h.get("inventory_account_id"),
192 warehouse_id: h.get("warehouse_id"),
193 gl: GlSettlementState {
194 posting_state: h.get("ps"), journal_id: h.get("journal_id"),
195 accounting_post_id: h.get("accounting_post_id"),
196 },
197 }))
198 }
199
200 pub async fn mark_submitted_with_cogs(
203 &self,
204 conn: &mut sqlx::PgConnection,
205 id: Uuid,
206 total_cogs: Decimal,
207 ) -> Result<(), sqlx::Error> {
208 sqlx::query("UPDATE inventory.delivery_notes SET status='submitted'::doc_status, total_cogs=$2 WHERE id=$1")
209 .bind(id).bind(total_cogs)
210 .execute(conn)
211 .await?;
212 Ok(())
213 }
214
215 pub async fn mark_cancelled(
218 &self,
219 conn: &mut sqlx::PgConnection,
220 id: Uuid,
221 ) -> Result<(), sqlx::Error> {
222 sqlx::query("UPDATE inventory.delivery_notes SET status='cancelled'::doc_status WHERE id=$1 AND status='submitted'::doc_status")
223 .bind(id)
224 .execute(conn)
225 .await?;
226 Ok(())
227 }
228
229 pub async fn fetch_cancel_header(
231 &self,
232 pool: &PgPool,
233 id: Uuid,
234 ) -> Result<Option<DeliveryCancelHeaderRow>, sqlx::Error> {
235 let row = org_scope::fetch_optional_row_scoped(
236 pool,
237 sqlx::query(
238 r#"SELECT branch_id, warehouse_id, posting_date, currency, delivery_number,
239 total_cogs, status::text AS st, cogs_account_id, inventory_account_id,
240 posting_state::text AS ps, journal_id, accounting_post_id,
241 reversal_journal_id, reversal_accounting_post_id
242 FROM inventory.delivery_notes WHERE id=$1 AND (metadata->>'deleted_at') IS NULL"#,
243 )
244 .bind(id),
245 )
246 .await?;
247 Ok(row.map(|h| DeliveryCancelHeaderRow {
248 branch_id: h.get("branch_id"),
249 warehouse_id: h.get("warehouse_id"), posting_date: h.get("posting_date"),
250 currency: h.get("currency"), delivery_number: h.get("delivery_number"),
251 total_cogs: h.get("total_cogs"),
252 status: h.get("st"), cogs_account_id: h.get("cogs_account_id"),
253 inventory_account_id: h.get("inventory_account_id"),
254 reversal_journal_id: h.get("reversal_journal_id"),
255 reversal_accounting_post_id: h.get("reversal_accounting_post_id"),
256 gl: GlSettlementState {
257 posting_state: h.get("ps"), journal_id: h.get("journal_id"),
258 accounting_post_id: h.get("accounting_post_id"),
259 },
260 }))
261 }
262}
263
264backbone_core::impl_crud_repository!(DeliveryNoteRepository, DeliveryNote, soft_delete);