backbone_inventory/domain/entity/
purchase_receipt.rs1use chrono::{DateTime, Utc, NaiveDate};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5use rust_decimal::Decimal;
6
7use super::DocStatus;
8use super::GlPostingState;
9use super::AuditMetadata;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(transparent)]
14pub struct PurchaseReceiptId(pub Uuid);
15
16impl PurchaseReceiptId {
17 pub fn new(id: Uuid) -> Self { Self(id) }
18 pub fn generate() -> Self { Self(Uuid::new_v4()) }
19 pub fn into_inner(self) -> Uuid { self.0 }
20}
21
22impl std::fmt::Display for PurchaseReceiptId {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 write!(f, "{}", self.0)
25 }
26}
27
28impl std::str::FromStr for PurchaseReceiptId {
29 type Err = uuid::Error;
30 fn from_str(s: &str) -> Result<Self, Self::Err> {
31 Ok(Self(Uuid::parse_str(s)?))
32 }
33}
34
35impl From<Uuid> for PurchaseReceiptId {
36 fn from(id: Uuid) -> Self { Self(id) }
37}
38
39impl From<PurchaseReceiptId> for Uuid {
40 fn from(id: PurchaseReceiptId) -> Self { id.0 }
41}
42
43impl AsRef<Uuid> for PurchaseReceiptId {
44 fn as_ref(&self) -> &Uuid { &self.0 }
45}
46
47impl std::ops::Deref for PurchaseReceiptId {
48 type Target = Uuid;
49 fn deref(&self) -> &Self::Target { &self.0 }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
53pub struct PurchaseReceipt {
54 pub id: Uuid,
55 pub receipt_number: String,
56 pub branch_id: Option<Uuid>,
57 pub supplier_id: Uuid,
58 pub source_po_id: Option<Uuid>,
59 pub warehouse_id: Uuid,
60 pub posting_date: NaiveDate,
61 pub currency: String,
62 pub total_value: Decimal,
63 pub inventory_account_id: Uuid,
64 pub grir_account_id: Uuid,
65 pub transfer_id: Option<Uuid>,
66 pub status: DocStatus,
67 pub posting_state: GlPostingState,
68 pub journal_id: Option<Uuid>,
69 pub accounting_post_id: Option<Uuid>,
70 pub posted_at: Option<DateTime<Utc>>,
71 pub notes: Option<String>,
72 #[serde(default)]
73 #[sqlx(json)]
74 pub metadata: AuditMetadata,
75}
76
77impl PurchaseReceipt {
78 pub fn builder() -> PurchaseReceiptBuilder {
80 <PurchaseReceiptBuilder as Default>::default()
81 }
82
83 pub fn new(receipt_number: String, supplier_id: Uuid, warehouse_id: Uuid, posting_date: NaiveDate, currency: String, total_value: Decimal, inventory_account_id: Uuid, grir_account_id: Uuid, status: DocStatus, posting_state: GlPostingState) -> Self {
85 Self {
86 id: Uuid::new_v4(),
87 receipt_number,
88 branch_id: None,
89 supplier_id,
90 source_po_id: None,
91 warehouse_id,
92 posting_date,
93 currency,
94 total_value,
95 inventory_account_id,
96 grir_account_id,
97 transfer_id: None,
98 status,
99 posting_state,
100 journal_id: None,
101 accounting_post_id: None,
102 posted_at: None,
103 notes: None,
104 metadata: AuditMetadata::default(),
105 }
106 }
107
108 pub fn id(&self) -> &Uuid {
110 &self.id
111 }
112
113 pub fn typed_id(&self) -> PurchaseReceiptId {
115 PurchaseReceiptId(self.id)
116 }
117
118 pub fn created_at(&self) -> Option<&DateTime<Utc>> {
120 self.metadata.created_at.as_ref()
121 }
122
123 pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
125 self.metadata.updated_at.as_ref()
126 }
127
128 pub fn is_deleted(&self) -> bool {
130 self.metadata.deleted_at.is_some()
131 }
132
133 pub fn is_active(&self) -> bool {
135 self.metadata.deleted_at.is_none()
136 }
137
138 pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
140 self.metadata.deleted_at.as_ref()
141 }
142
143 pub fn created_by(&self) -> Option<&Uuid> {
145 self.metadata.created_by.as_ref()
146 }
147
148 pub fn updated_by(&self) -> Option<&Uuid> {
150 self.metadata.updated_by.as_ref()
151 }
152
153 pub fn deleted_by(&self) -> Option<&Uuid> {
155 self.metadata.deleted_by.as_ref()
156 }
157
158 pub fn status(&self) -> &DocStatus {
160 &self.status
161 }
162
163
164 pub fn with_branch_id(mut self, value: Uuid) -> Self {
170 self.branch_id = Some(value);
171 self
172 }
173
174 pub fn with_source_po_id(mut self, value: Uuid) -> Self {
176 self.source_po_id = Some(value);
177 self
178 }
179
180 pub fn with_transfer_id(mut self, value: Uuid) -> Self {
182 self.transfer_id = Some(value);
183 self
184 }
185
186 pub fn with_journal_id(mut self, value: Uuid) -> Self {
188 self.journal_id = Some(value);
189 self
190 }
191
192 pub fn with_accounting_post_id(mut self, value: Uuid) -> Self {
194 self.accounting_post_id = Some(value);
195 self
196 }
197
198 pub fn with_posted_at(mut self, value: DateTime<Utc>) -> Self {
200 self.posted_at = Some(value);
201 self
202 }
203
204 pub fn with_notes(mut self, value: String) -> Self {
206 self.notes = Some(value);
207 self
208 }
209
210 pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
216 for (key, value) in fields {
217 match key.as_str() {
218 "receipt_number" => {
219 if let Ok(v) = serde_json::from_value(value) { self.receipt_number = v; }
220 }
221 "branch_id" => {
222 if let Ok(v) = serde_json::from_value(value) { self.branch_id = v; }
223 }
224 "supplier_id" => {
225 if let Ok(v) = serde_json::from_value(value) { self.supplier_id = v; }
226 }
227 "source_po_id" => {
228 if let Ok(v) = serde_json::from_value(value) { self.source_po_id = v; }
229 }
230 "warehouse_id" => {
231 if let Ok(v) = serde_json::from_value(value) { self.warehouse_id = v; }
232 }
233 "posting_date" => {
234 if let Ok(v) = serde_json::from_value(value) { self.posting_date = v; }
235 }
236 "currency" => {
237 if let Ok(v) = serde_json::from_value(value) { self.currency = v; }
238 }
239 "total_value" => {
240 if let Ok(v) = serde_json::from_value(value) { self.total_value = v; }
241 }
242 "inventory_account_id" => {
243 if let Ok(v) = serde_json::from_value(value) { self.inventory_account_id = v; }
244 }
245 "grir_account_id" => {
246 if let Ok(v) = serde_json::from_value(value) { self.grir_account_id = v; }
247 }
248 "transfer_id" => {
249 if let Ok(v) = serde_json::from_value(value) { self.transfer_id = v; }
250 }
251 "status" => {
252 if let Ok(v) = serde_json::from_value(value) { self.status = v; }
253 }
254 "posting_state" => {
255 if let Ok(v) = serde_json::from_value(value) { self.posting_state = v; }
256 }
257 "journal_id" => {
258 if let Ok(v) = serde_json::from_value(value) { self.journal_id = v; }
259 }
260 "accounting_post_id" => {
261 if let Ok(v) = serde_json::from_value(value) { self.accounting_post_id = v; }
262 }
263 "posted_at" => {
264 if let Ok(v) = serde_json::from_value(value) { self.posted_at = v; }
265 }
266 "notes" => {
267 if let Ok(v) = serde_json::from_value(value) { self.notes = v; }
268 }
269 _ => {} }
271 }
272 }
273
274}
275
276impl super::Entity for PurchaseReceipt {
277 type Id = Uuid;
278
279 fn entity_id(&self) -> &Self::Id {
280 &self.id
281 }
282
283 fn entity_type() -> &'static str {
284 "PurchaseReceipt"
285 }
286}
287
288impl backbone_core::PersistentEntity for PurchaseReceipt {
289 fn write_protected_fields() -> &'static [&'static str] {
290 &["status"]
291 }
292 fn entity_id(&self) -> String {
293 self.id.to_string()
294 }
295 fn set_entity_id(&mut self, id: String) {
296 if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
297 self.id = uuid;
298 }
299 }
300 fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
301 self.metadata.created_at
302 }
303 fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
304 self.metadata.created_at = Some(ts);
305 }
306 fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
307 self.metadata.updated_at
308 }
309 fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
310 self.metadata.updated_at = Some(ts);
311 }
312 fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
313 self.metadata.deleted_at
314 }
315 fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
316 self.metadata.deleted_at = ts;
317 }
318}
319
320impl backbone_orm::EntityRepoMeta for PurchaseReceipt {
321 fn column_types() -> std::collections::HashMap<String, String> {
322 let mut m = std::collections::HashMap::new();
323 m.insert("id".to_string(), "uuid".to_string());
324 m.insert("branch_id".to_string(), "uuid".to_string());
325 m.insert("supplier_id".to_string(), "uuid".to_string());
326 m.insert("source_po_id".to_string(), "uuid".to_string());
327 m.insert("warehouse_id".to_string(), "uuid".to_string());
328 m.insert("inventory_account_id".to_string(), "uuid".to_string());
329 m.insert("grir_account_id".to_string(), "uuid".to_string());
330 m.insert("transfer_id".to_string(), "uuid".to_string());
331 m.insert("journal_id".to_string(), "uuid".to_string());
332 m.insert("accounting_post_id".to_string(), "uuid".to_string());
333 m.insert("status".to_string(), "doc_status".to_string());
334 m.insert("posting_state".to_string(), "gl_posting_state".to_string());
335 m.insert("posting_date".to_string(), "date".to_string());
336 m.insert("posted_at".to_string(), "timestamptz".to_string());
337 m
338 }
339 fn search_fields() -> &'static [&'static str] {
340 &["receipt_number", "currency"]
341 }
342 fn relations() -> &'static [(&'static str, &'static str, &'static str)] {
343 &[("warehouse", "warehouses", "warehouseId"), ("transfer", "transfers", "transferId")]
344 }
345}
346
347#[derive(Debug, Clone, Default)]
352pub struct PurchaseReceiptBuilder {
353 receipt_number: Option<String>,
354 branch_id: Option<Uuid>,
355 supplier_id: Option<Uuid>,
356 source_po_id: Option<Uuid>,
357 warehouse_id: Option<Uuid>,
358 posting_date: Option<NaiveDate>,
359 currency: Option<String>,
360 total_value: Option<Decimal>,
361 inventory_account_id: Option<Uuid>,
362 grir_account_id: Option<Uuid>,
363 transfer_id: Option<Uuid>,
364 status: Option<DocStatus>,
365 posting_state: Option<GlPostingState>,
366 journal_id: Option<Uuid>,
367 accounting_post_id: Option<Uuid>,
368 posted_at: Option<DateTime<Utc>>,
369 notes: Option<String>,
370}
371
372impl PurchaseReceiptBuilder {
373 pub fn receipt_number(mut self, value: String) -> Self {
375 self.receipt_number = Some(value);
376 self
377 }
378
379 pub fn branch_id(mut self, value: Uuid) -> Self {
381 self.branch_id = Some(value);
382 self
383 }
384
385 pub fn supplier_id(mut self, value: Uuid) -> Self {
387 self.supplier_id = Some(value);
388 self
389 }
390
391 pub fn source_po_id(mut self, value: Uuid) -> Self {
393 self.source_po_id = Some(value);
394 self
395 }
396
397 pub fn warehouse_id(mut self, value: Uuid) -> Self {
399 self.warehouse_id = Some(value);
400 self
401 }
402
403 pub fn posting_date(mut self, value: NaiveDate) -> Self {
405 self.posting_date = Some(value);
406 self
407 }
408
409 pub fn currency(mut self, value: String) -> Self {
411 self.currency = Some(value);
412 self
413 }
414
415 pub fn total_value(mut self, value: Decimal) -> Self {
417 self.total_value = Some(value);
418 self
419 }
420
421 pub fn inventory_account_id(mut self, value: Uuid) -> Self {
423 self.inventory_account_id = Some(value);
424 self
425 }
426
427 pub fn grir_account_id(mut self, value: Uuid) -> Self {
429 self.grir_account_id = Some(value);
430 self
431 }
432
433 pub fn transfer_id(mut self, value: Uuid) -> Self {
435 self.transfer_id = Some(value);
436 self
437 }
438
439 pub fn status(mut self, value: DocStatus) -> Self {
441 self.status = Some(value);
442 self
443 }
444
445 pub fn posting_state(mut self, value: GlPostingState) -> Self {
447 self.posting_state = Some(value);
448 self
449 }
450
451 pub fn journal_id(mut self, value: Uuid) -> Self {
453 self.journal_id = Some(value);
454 self
455 }
456
457 pub fn accounting_post_id(mut self, value: Uuid) -> Self {
459 self.accounting_post_id = Some(value);
460 self
461 }
462
463 pub fn posted_at(mut self, value: DateTime<Utc>) -> Self {
465 self.posted_at = Some(value);
466 self
467 }
468
469 pub fn notes(mut self, value: String) -> Self {
471 self.notes = Some(value);
472 self
473 }
474
475 pub fn build(self) -> Result<PurchaseReceipt, String> {
479 let receipt_number = self.receipt_number.ok_or_else(|| "receipt_number is required".to_string())?;
480 let supplier_id = self.supplier_id.ok_or_else(|| "supplier_id is required".to_string())?;
481 let warehouse_id = self.warehouse_id.ok_or_else(|| "warehouse_id is required".to_string())?;
482 let posting_date = self.posting_date.ok_or_else(|| "posting_date is required".to_string())?;
483 let inventory_account_id = self.inventory_account_id.ok_or_else(|| "inventory_account_id is required".to_string())?;
484 let grir_account_id = self.grir_account_id.ok_or_else(|| "grir_account_id is required".to_string())?;
485
486 Ok(PurchaseReceipt {
487 id: Uuid::new_v4(),
488 receipt_number,
489 branch_id: self.branch_id,
490 supplier_id,
491 source_po_id: self.source_po_id,
492 warehouse_id,
493 posting_date,
494 currency: self.currency.unwrap_or("IDR".to_string()),
495 total_value: self.total_value.unwrap_or(Decimal::from(0)),
496 inventory_account_id,
497 grir_account_id,
498 transfer_id: self.transfer_id,
499 status: self.status.unwrap_or_default(),
500 posting_state: self.posting_state.unwrap_or_default(),
501 journal_id: self.journal_id,
502 accounting_post_id: self.accounting_post_id,
503 posted_at: self.posted_at,
504 notes: self.notes,
505 metadata: AuditMetadata::default(),
506 })
507 }
508}