backbone_inventory/domain/entity/
delivery_note.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 DeliveryNoteId(pub Uuid);
15
16impl DeliveryNoteId {
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 DeliveryNoteId {
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 DeliveryNoteId {
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 DeliveryNoteId {
36 fn from(id: Uuid) -> Self { Self(id) }
37}
38
39impl From<DeliveryNoteId> for Uuid {
40 fn from(id: DeliveryNoteId) -> Self { id.0 }
41}
42
43impl AsRef<Uuid> for DeliveryNoteId {
44 fn as_ref(&self) -> &Uuid { &self.0 }
45}
46
47impl std::ops::Deref for DeliveryNoteId {
48 type Target = Uuid;
49 fn deref(&self) -> &Self::Target { &self.0 }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
53pub struct DeliveryNote {
54 pub id: Uuid,
55 pub delivery_number: String,
56 pub branch_id: Option<Uuid>,
57 pub customer_id: Uuid,
58 pub source_so_id: Option<Uuid>,
59 pub warehouse_id: Uuid,
60 pub posting_date: NaiveDate,
61 pub total_cogs: Decimal,
62 pub cogs_account_id: Uuid,
63 pub inventory_account_id: Uuid,
64 pub transfer_id: Option<Uuid>,
65 pub status: DocStatus,
66 pub posting_state: GlPostingState,
67 pub journal_id: Option<Uuid>,
68 pub accounting_post_id: Option<Uuid>,
69 pub posted_at: Option<DateTime<Utc>>,
70 pub notes: Option<String>,
71 #[serde(default)]
72 #[sqlx(json)]
73 pub metadata: AuditMetadata,
74}
75
76impl DeliveryNote {
77 pub fn builder() -> DeliveryNoteBuilder {
79 <DeliveryNoteBuilder as Default>::default()
80 }
81
82 pub fn new(delivery_number: String, customer_id: Uuid, warehouse_id: Uuid, posting_date: NaiveDate, total_cogs: Decimal, cogs_account_id: Uuid, inventory_account_id: Uuid, status: DocStatus, posting_state: GlPostingState) -> Self {
84 Self {
85 id: Uuid::new_v4(),
86 delivery_number,
87 branch_id: None,
88 customer_id,
89 source_so_id: None,
90 warehouse_id,
91 posting_date,
92 total_cogs,
93 cogs_account_id,
94 inventory_account_id,
95 transfer_id: None,
96 status,
97 posting_state,
98 journal_id: None,
99 accounting_post_id: None,
100 posted_at: None,
101 notes: None,
102 metadata: AuditMetadata::default(),
103 }
104 }
105
106 pub fn id(&self) -> &Uuid {
108 &self.id
109 }
110
111 pub fn typed_id(&self) -> DeliveryNoteId {
113 DeliveryNoteId(self.id)
114 }
115
116 pub fn created_at(&self) -> Option<&DateTime<Utc>> {
118 self.metadata.created_at.as_ref()
119 }
120
121 pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
123 self.metadata.updated_at.as_ref()
124 }
125
126 pub fn is_deleted(&self) -> bool {
128 self.metadata.deleted_at.is_some()
129 }
130
131 pub fn is_active(&self) -> bool {
133 self.metadata.deleted_at.is_none()
134 }
135
136 pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
138 self.metadata.deleted_at.as_ref()
139 }
140
141 pub fn created_by(&self) -> Option<&Uuid> {
143 self.metadata.created_by.as_ref()
144 }
145
146 pub fn updated_by(&self) -> Option<&Uuid> {
148 self.metadata.updated_by.as_ref()
149 }
150
151 pub fn deleted_by(&self) -> Option<&Uuid> {
153 self.metadata.deleted_by.as_ref()
154 }
155
156 pub fn status(&self) -> &DocStatus {
158 &self.status
159 }
160
161
162 pub fn with_branch_id(mut self, value: Uuid) -> Self {
168 self.branch_id = Some(value);
169 self
170 }
171
172 pub fn with_source_so_id(mut self, value: Uuid) -> Self {
174 self.source_so_id = Some(value);
175 self
176 }
177
178 pub fn with_transfer_id(mut self, value: Uuid) -> Self {
180 self.transfer_id = Some(value);
181 self
182 }
183
184 pub fn with_journal_id(mut self, value: Uuid) -> Self {
186 self.journal_id = Some(value);
187 self
188 }
189
190 pub fn with_accounting_post_id(mut self, value: Uuid) -> Self {
192 self.accounting_post_id = Some(value);
193 self
194 }
195
196 pub fn with_posted_at(mut self, value: DateTime<Utc>) -> Self {
198 self.posted_at = Some(value);
199 self
200 }
201
202 pub fn with_notes(mut self, value: String) -> Self {
204 self.notes = Some(value);
205 self
206 }
207
208 pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
214 for (key, value) in fields {
215 match key.as_str() {
216 "delivery_number" => {
217 if let Ok(v) = serde_json::from_value(value) { self.delivery_number = v; }
218 }
219 "branch_id" => {
220 if let Ok(v) = serde_json::from_value(value) { self.branch_id = v; }
221 }
222 "customer_id" => {
223 if let Ok(v) = serde_json::from_value(value) { self.customer_id = v; }
224 }
225 "source_so_id" => {
226 if let Ok(v) = serde_json::from_value(value) { self.source_so_id = v; }
227 }
228 "warehouse_id" => {
229 if let Ok(v) = serde_json::from_value(value) { self.warehouse_id = v; }
230 }
231 "posting_date" => {
232 if let Ok(v) = serde_json::from_value(value) { self.posting_date = v; }
233 }
234 "total_cogs" => {
235 if let Ok(v) = serde_json::from_value(value) { self.total_cogs = v; }
236 }
237 "cogs_account_id" => {
238 if let Ok(v) = serde_json::from_value(value) { self.cogs_account_id = v; }
239 }
240 "inventory_account_id" => {
241 if let Ok(v) = serde_json::from_value(value) { self.inventory_account_id = v; }
242 }
243 "transfer_id" => {
244 if let Ok(v) = serde_json::from_value(value) { self.transfer_id = v; }
245 }
246 "status" => {
247 if let Ok(v) = serde_json::from_value(value) { self.status = v; }
248 }
249 "posting_state" => {
250 if let Ok(v) = serde_json::from_value(value) { self.posting_state = v; }
251 }
252 "journal_id" => {
253 if let Ok(v) = serde_json::from_value(value) { self.journal_id = v; }
254 }
255 "accounting_post_id" => {
256 if let Ok(v) = serde_json::from_value(value) { self.accounting_post_id = v; }
257 }
258 "posted_at" => {
259 if let Ok(v) = serde_json::from_value(value) { self.posted_at = v; }
260 }
261 "notes" => {
262 if let Ok(v) = serde_json::from_value(value) { self.notes = v; }
263 }
264 _ => {} }
266 }
267 }
268
269}
270
271impl super::Entity for DeliveryNote {
272 type Id = Uuid;
273
274 fn entity_id(&self) -> &Self::Id {
275 &self.id
276 }
277
278 fn entity_type() -> &'static str {
279 "DeliveryNote"
280 }
281}
282
283impl backbone_core::PersistentEntity for DeliveryNote {
284 fn write_protected_fields() -> &'static [&'static str] {
285 &["status"]
286 }
287 fn state_fields() -> &'static [&'static str] {
288 &["status"]
289 }
290 fn references() -> &'static [backbone_core::Reference] {
291 const REFERENCES: &[backbone_core::Reference] = &[
292 backbone_core::Reference::new("warehouse_id", "inventory.warehouses", "id"),
293 backbone_core::Reference::new("transfer_id", "inventory.transfers", "id"),
294 ];
295 REFERENCES
296 }
297 fn entity_id(&self) -> String {
298 self.id.to_string()
299 }
300 fn set_entity_id(&mut self, id: String) {
301 if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
302 self.id = uuid;
303 }
304 }
305 fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
306 self.metadata.created_at
307 }
308 fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
309 self.metadata.created_at = Some(ts);
310 }
311 fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
312 self.metadata.updated_at
313 }
314 fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
315 self.metadata.updated_at = Some(ts);
316 }
317 fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
318 self.metadata.deleted_at
319 }
320 fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
321 self.metadata.deleted_at = ts;
322 }
323}
324
325impl backbone_orm::EntityRepoMeta for DeliveryNote {
326 fn column_types() -> std::collections::HashMap<String, String> {
327 let mut m = std::collections::HashMap::new();
328 m.insert("id".to_string(), "uuid".to_string());
329 m.insert("branch_id".to_string(), "uuid".to_string());
330 m.insert("customer_id".to_string(), "uuid".to_string());
331 m.insert("source_so_id".to_string(), "uuid".to_string());
332 m.insert("warehouse_id".to_string(), "uuid".to_string());
333 m.insert("cogs_account_id".to_string(), "uuid".to_string());
334 m.insert("inventory_account_id".to_string(), "uuid".to_string());
335 m.insert("transfer_id".to_string(), "uuid".to_string());
336 m.insert("journal_id".to_string(), "uuid".to_string());
337 m.insert("accounting_post_id".to_string(), "uuid".to_string());
338 m.insert("status".to_string(), "doc_status".to_string());
339 m.insert("posting_state".to_string(), "gl_posting_state".to_string());
340 m.insert("posting_date".to_string(), "date".to_string());
341 m.insert("posted_at".to_string(), "timestamptz".to_string());
342 m
343 }
344 fn search_fields() -> &'static [&'static str] {
345 &["delivery_number"]
346 }
347 fn relations() -> &'static [(&'static str, &'static str, &'static str)] {
348 &[("warehouse", "warehouses", "warehouseId"), ("transfer", "transfers", "transferId")]
349 }
350}
351
352#[derive(Debug, Clone, Default)]
357pub struct DeliveryNoteBuilder {
358 delivery_number: Option<String>,
359 branch_id: Option<Uuid>,
360 customer_id: Option<Uuid>,
361 source_so_id: Option<Uuid>,
362 warehouse_id: Option<Uuid>,
363 posting_date: Option<NaiveDate>,
364 total_cogs: Option<Decimal>,
365 cogs_account_id: Option<Uuid>,
366 inventory_account_id: Option<Uuid>,
367 transfer_id: Option<Uuid>,
368 status: Option<DocStatus>,
369 posting_state: Option<GlPostingState>,
370 journal_id: Option<Uuid>,
371 accounting_post_id: Option<Uuid>,
372 posted_at: Option<DateTime<Utc>>,
373 notes: Option<String>,
374}
375
376impl DeliveryNoteBuilder {
377 pub fn delivery_number(mut self, value: String) -> Self {
379 self.delivery_number = Some(value);
380 self
381 }
382
383 pub fn branch_id(mut self, value: Uuid) -> Self {
385 self.branch_id = Some(value);
386 self
387 }
388
389 pub fn customer_id(mut self, value: Uuid) -> Self {
391 self.customer_id = Some(value);
392 self
393 }
394
395 pub fn source_so_id(mut self, value: Uuid) -> Self {
397 self.source_so_id = Some(value);
398 self
399 }
400
401 pub fn warehouse_id(mut self, value: Uuid) -> Self {
403 self.warehouse_id = Some(value);
404 self
405 }
406
407 pub fn posting_date(mut self, value: NaiveDate) -> Self {
409 self.posting_date = Some(value);
410 self
411 }
412
413 pub fn total_cogs(mut self, value: Decimal) -> Self {
415 self.total_cogs = Some(value);
416 self
417 }
418
419 pub fn cogs_account_id(mut self, value: Uuid) -> Self {
421 self.cogs_account_id = Some(value);
422 self
423 }
424
425 pub fn inventory_account_id(mut self, value: Uuid) -> Self {
427 self.inventory_account_id = Some(value);
428 self
429 }
430
431 pub fn transfer_id(mut self, value: Uuid) -> Self {
433 self.transfer_id = Some(value);
434 self
435 }
436
437 pub fn status(mut self, value: DocStatus) -> Self {
439 self.status = Some(value);
440 self
441 }
442
443 pub fn posting_state(mut self, value: GlPostingState) -> Self {
445 self.posting_state = Some(value);
446 self
447 }
448
449 pub fn journal_id(mut self, value: Uuid) -> Self {
451 self.journal_id = Some(value);
452 self
453 }
454
455 pub fn accounting_post_id(mut self, value: Uuid) -> Self {
457 self.accounting_post_id = Some(value);
458 self
459 }
460
461 pub fn posted_at(mut self, value: DateTime<Utc>) -> Self {
463 self.posted_at = Some(value);
464 self
465 }
466
467 pub fn notes(mut self, value: String) -> Self {
469 self.notes = Some(value);
470 self
471 }
472
473 pub fn build(self) -> Result<DeliveryNote, String> {
477 let delivery_number = self.delivery_number.ok_or_else(|| "delivery_number is required".to_string())?;
478 let customer_id = self.customer_id.ok_or_else(|| "customer_id is required".to_string())?;
479 let warehouse_id = self.warehouse_id.ok_or_else(|| "warehouse_id is required".to_string())?;
480 let posting_date = self.posting_date.ok_or_else(|| "posting_date is required".to_string())?;
481 let cogs_account_id = self.cogs_account_id.ok_or_else(|| "cogs_account_id is required".to_string())?;
482 let inventory_account_id = self.inventory_account_id.ok_or_else(|| "inventory_account_id is required".to_string())?;
483
484 Ok(DeliveryNote {
485 id: Uuid::new_v4(),
486 delivery_number,
487 branch_id: self.branch_id,
488 customer_id,
489 source_so_id: self.source_so_id,
490 warehouse_id,
491 posting_date,
492 total_cogs: self.total_cogs.unwrap_or(Decimal::from(0)),
493 cogs_account_id,
494 inventory_account_id,
495 transfer_id: self.transfer_id,
496 status: self.status.unwrap_or_default(),
497 posting_state: self.posting_state.unwrap_or_default(),
498 journal_id: self.journal_id,
499 accounting_post_id: self.accounting_post_id,
500 posted_at: self.posted_at,
501 notes: self.notes,
502 metadata: AuditMetadata::default(),
503 })
504 }
505}