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 }
272
273impl super::Entity for DeliveryNote {
274 type Id = Uuid;
275
276 fn entity_id(&self) -> &Self::Id {
277 &self.id
278 }
279
280 fn entity_type() -> &'static str {
281 "DeliveryNote"
282 }
283}
284
285impl backbone_core::PersistentEntity for DeliveryNote {
286 fn entity_id(&self) -> String {
287 self.id.to_string()
288 }
289 fn set_entity_id(&mut self, id: String) {
290 if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
291 self.id = uuid;
292 }
293 }
294 fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
295 self.metadata.created_at
296 }
297 fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
298 self.metadata.created_at = Some(ts);
299 }
300 fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
301 self.metadata.updated_at
302 }
303 fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
304 self.metadata.updated_at = Some(ts);
305 }
306 fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
307 self.metadata.deleted_at
308 }
309 fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
310 self.metadata.deleted_at = ts;
311 }
312}
313
314impl backbone_orm::EntityRepoMeta for DeliveryNote {
315 fn column_types() -> std::collections::HashMap<String, String> {
316 let mut m = std::collections::HashMap::new();
317 m.insert("id".to_string(), "uuid".to_string());
318 m.insert("branch_id".to_string(), "uuid".to_string());
319 m.insert("customer_id".to_string(), "uuid".to_string());
320 m.insert("source_so_id".to_string(), "uuid".to_string());
321 m.insert("warehouse_id".to_string(), "uuid".to_string());
322 m.insert("cogs_account_id".to_string(), "uuid".to_string());
323 m.insert("inventory_account_id".to_string(), "uuid".to_string());
324 m.insert("transfer_id".to_string(), "uuid".to_string());
325 m.insert("journal_id".to_string(), "uuid".to_string());
326 m.insert("accounting_post_id".to_string(), "uuid".to_string());
327 m.insert("status".to_string(), "doc_status".to_string());
328 m.insert("posting_state".to_string(), "gl_posting_state".to_string());
329 m.insert("posting_date".to_string(), "date".to_string());
330 m.insert("posted_at".to_string(), "timestamptz".to_string());
331 m
332 }
333 fn search_fields() -> &'static [&'static str] {
334 &["delivery_number"]
335 }
336}
337
338#[derive(Debug, Clone, Default)]
343pub struct DeliveryNoteBuilder {
344 delivery_number: Option<String>,
345 branch_id: Option<Uuid>,
346 customer_id: Option<Uuid>,
347 source_so_id: Option<Uuid>,
348 warehouse_id: Option<Uuid>,
349 posting_date: Option<NaiveDate>,
350 total_cogs: Option<Decimal>,
351 cogs_account_id: Option<Uuid>,
352 inventory_account_id: Option<Uuid>,
353 transfer_id: Option<Uuid>,
354 status: Option<DocStatus>,
355 posting_state: Option<GlPostingState>,
356 journal_id: Option<Uuid>,
357 accounting_post_id: Option<Uuid>,
358 posted_at: Option<DateTime<Utc>>,
359 notes: Option<String>,
360}
361
362impl DeliveryNoteBuilder {
363 pub fn delivery_number(mut self, value: String) -> Self {
365 self.delivery_number = Some(value);
366 self
367 }
368
369 pub fn branch_id(mut self, value: Uuid) -> Self {
371 self.branch_id = Some(value);
372 self
373 }
374
375 pub fn customer_id(mut self, value: Uuid) -> Self {
377 self.customer_id = Some(value);
378 self
379 }
380
381 pub fn source_so_id(mut self, value: Uuid) -> Self {
383 self.source_so_id = Some(value);
384 self
385 }
386
387 pub fn warehouse_id(mut self, value: Uuid) -> Self {
389 self.warehouse_id = Some(value);
390 self
391 }
392
393 pub fn posting_date(mut self, value: NaiveDate) -> Self {
395 self.posting_date = Some(value);
396 self
397 }
398
399 pub fn total_cogs(mut self, value: Decimal) -> Self {
401 self.total_cogs = Some(value);
402 self
403 }
404
405 pub fn cogs_account_id(mut self, value: Uuid) -> Self {
407 self.cogs_account_id = Some(value);
408 self
409 }
410
411 pub fn inventory_account_id(mut self, value: Uuid) -> Self {
413 self.inventory_account_id = Some(value);
414 self
415 }
416
417 pub fn transfer_id(mut self, value: Uuid) -> Self {
419 self.transfer_id = Some(value);
420 self
421 }
422
423 pub fn status(mut self, value: DocStatus) -> Self {
425 self.status = Some(value);
426 self
427 }
428
429 pub fn posting_state(mut self, value: GlPostingState) -> Self {
431 self.posting_state = Some(value);
432 self
433 }
434
435 pub fn journal_id(mut self, value: Uuid) -> Self {
437 self.journal_id = Some(value);
438 self
439 }
440
441 pub fn accounting_post_id(mut self, value: Uuid) -> Self {
443 self.accounting_post_id = Some(value);
444 self
445 }
446
447 pub fn posted_at(mut self, value: DateTime<Utc>) -> Self {
449 self.posted_at = Some(value);
450 self
451 }
452
453 pub fn notes(mut self, value: String) -> Self {
455 self.notes = Some(value);
456 self
457 }
458
459 pub fn build(self) -> Result<DeliveryNote, String> {
463 let delivery_number = self.delivery_number.ok_or_else(|| "delivery_number is required".to_string())?;
464 let customer_id = self.customer_id.ok_or_else(|| "customer_id is required".to_string())?;
465 let warehouse_id = self.warehouse_id.ok_or_else(|| "warehouse_id is required".to_string())?;
466 let posting_date = self.posting_date.ok_or_else(|| "posting_date is required".to_string())?;
467 let cogs_account_id = self.cogs_account_id.ok_or_else(|| "cogs_account_id is required".to_string())?;
468 let inventory_account_id = self.inventory_account_id.ok_or_else(|| "inventory_account_id is required".to_string())?;
469
470 Ok(DeliveryNote {
471 id: Uuid::new_v4(),
472 delivery_number,
473 branch_id: self.branch_id,
474 customer_id,
475 source_so_id: self.source_so_id,
476 warehouse_id,
477 posting_date,
478 total_cogs: self.total_cogs.unwrap_or(Decimal::from(0)),
479 cogs_account_id,
480 inventory_account_id,
481 transfer_id: self.transfer_id,
482 status: self.status.unwrap_or_default(),
483 posting_state: self.posting_state.unwrap_or_default(),
484 journal_id: self.journal_id,
485 accounting_post_id: self.accounting_post_id,
486 posted_at: self.posted_at,
487 notes: self.notes,
488 metadata: AuditMetadata::default(),
489 })
490 }
491}