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 entity_id(&self) -> String {
288 self.id.to_string()
289 }
290 fn set_entity_id(&mut self, id: String) {
291 if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
292 self.id = uuid;
293 }
294 }
295 fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
296 self.metadata.created_at
297 }
298 fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
299 self.metadata.created_at = Some(ts);
300 }
301 fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
302 self.metadata.updated_at
303 }
304 fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
305 self.metadata.updated_at = Some(ts);
306 }
307 fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
308 self.metadata.deleted_at
309 }
310 fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
311 self.metadata.deleted_at = ts;
312 }
313}
314
315impl backbone_orm::EntityRepoMeta for DeliveryNote {
316 fn column_types() -> std::collections::HashMap<String, String> {
317 let mut m = std::collections::HashMap::new();
318 m.insert("id".to_string(), "uuid".to_string());
319 m.insert("branch_id".to_string(), "uuid".to_string());
320 m.insert("customer_id".to_string(), "uuid".to_string());
321 m.insert("source_so_id".to_string(), "uuid".to_string());
322 m.insert("warehouse_id".to_string(), "uuid".to_string());
323 m.insert("cogs_account_id".to_string(), "uuid".to_string());
324 m.insert("inventory_account_id".to_string(), "uuid".to_string());
325 m.insert("transfer_id".to_string(), "uuid".to_string());
326 m.insert("journal_id".to_string(), "uuid".to_string());
327 m.insert("accounting_post_id".to_string(), "uuid".to_string());
328 m.insert("status".to_string(), "doc_status".to_string());
329 m.insert("posting_state".to_string(), "gl_posting_state".to_string());
330 m.insert("posting_date".to_string(), "date".to_string());
331 m.insert("posted_at".to_string(), "timestamptz".to_string());
332 m
333 }
334 fn search_fields() -> &'static [&'static str] {
335 &["delivery_number"]
336 }
337 fn relations() -> &'static [(&'static str, &'static str, &'static str)] {
338 &[("warehouse", "warehouses", "warehouseId"), ("transfer", "transfers", "transferId")]
339 }
340}
341
342#[derive(Debug, Clone, Default)]
347pub struct DeliveryNoteBuilder {
348 delivery_number: Option<String>,
349 branch_id: Option<Uuid>,
350 customer_id: Option<Uuid>,
351 source_so_id: Option<Uuid>,
352 warehouse_id: Option<Uuid>,
353 posting_date: Option<NaiveDate>,
354 total_cogs: Option<Decimal>,
355 cogs_account_id: Option<Uuid>,
356 inventory_account_id: Option<Uuid>,
357 transfer_id: Option<Uuid>,
358 status: Option<DocStatus>,
359 posting_state: Option<GlPostingState>,
360 journal_id: Option<Uuid>,
361 accounting_post_id: Option<Uuid>,
362 posted_at: Option<DateTime<Utc>>,
363 notes: Option<String>,
364}
365
366impl DeliveryNoteBuilder {
367 pub fn delivery_number(mut self, value: String) -> Self {
369 self.delivery_number = Some(value);
370 self
371 }
372
373 pub fn branch_id(mut self, value: Uuid) -> Self {
375 self.branch_id = Some(value);
376 self
377 }
378
379 pub fn customer_id(mut self, value: Uuid) -> Self {
381 self.customer_id = Some(value);
382 self
383 }
384
385 pub fn source_so_id(mut self, value: Uuid) -> Self {
387 self.source_so_id = Some(value);
388 self
389 }
390
391 pub fn warehouse_id(mut self, value: Uuid) -> Self {
393 self.warehouse_id = Some(value);
394 self
395 }
396
397 pub fn posting_date(mut self, value: NaiveDate) -> Self {
399 self.posting_date = Some(value);
400 self
401 }
402
403 pub fn total_cogs(mut self, value: Decimal) -> Self {
405 self.total_cogs = Some(value);
406 self
407 }
408
409 pub fn cogs_account_id(mut self, value: Uuid) -> Self {
411 self.cogs_account_id = Some(value);
412 self
413 }
414
415 pub fn inventory_account_id(mut self, value: Uuid) -> Self {
417 self.inventory_account_id = Some(value);
418 self
419 }
420
421 pub fn transfer_id(mut self, value: Uuid) -> Self {
423 self.transfer_id = Some(value);
424 self
425 }
426
427 pub fn status(mut self, value: DocStatus) -> Self {
429 self.status = Some(value);
430 self
431 }
432
433 pub fn posting_state(mut self, value: GlPostingState) -> Self {
435 self.posting_state = Some(value);
436 self
437 }
438
439 pub fn journal_id(mut self, value: Uuid) -> Self {
441 self.journal_id = Some(value);
442 self
443 }
444
445 pub fn accounting_post_id(mut self, value: Uuid) -> Self {
447 self.accounting_post_id = Some(value);
448 self
449 }
450
451 pub fn posted_at(mut self, value: DateTime<Utc>) -> Self {
453 self.posted_at = Some(value);
454 self
455 }
456
457 pub fn notes(mut self, value: String) -> Self {
459 self.notes = Some(value);
460 self
461 }
462
463 pub fn build(self) -> Result<DeliveryNote, String> {
467 let delivery_number = self.delivery_number.ok_or_else(|| "delivery_number is required".to_string())?;
468 let customer_id = self.customer_id.ok_or_else(|| "customer_id is required".to_string())?;
469 let warehouse_id = self.warehouse_id.ok_or_else(|| "warehouse_id is required".to_string())?;
470 let posting_date = self.posting_date.ok_or_else(|| "posting_date is required".to_string())?;
471 let cogs_account_id = self.cogs_account_id.ok_or_else(|| "cogs_account_id is required".to_string())?;
472 let inventory_account_id = self.inventory_account_id.ok_or_else(|| "inventory_account_id is required".to_string())?;
473
474 Ok(DeliveryNote {
475 id: Uuid::new_v4(),
476 delivery_number,
477 branch_id: self.branch_id,
478 customer_id,
479 source_so_id: self.source_so_id,
480 warehouse_id,
481 posting_date,
482 total_cogs: self.total_cogs.unwrap_or(Decimal::from(0)),
483 cogs_account_id,
484 inventory_account_id,
485 transfer_id: self.transfer_id,
486 status: self.status.unwrap_or_default(),
487 posting_state: self.posting_state.unwrap_or_default(),
488 journal_id: self.journal_id,
489 accounting_post_id: self.accounting_post_id,
490 posted_at: self.posted_at,
491 notes: self.notes,
492 metadata: AuditMetadata::default(),
493 })
494 }
495}