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