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}
338
339#[derive(Debug, Clone, Default)]
344pub struct DeliveryNoteBuilder {
345 delivery_number: Option<String>,
346 branch_id: Option<Uuid>,
347 customer_id: Option<Uuid>,
348 source_so_id: Option<Uuid>,
349 warehouse_id: Option<Uuid>,
350 posting_date: Option<NaiveDate>,
351 total_cogs: Option<Decimal>,
352 cogs_account_id: Option<Uuid>,
353 inventory_account_id: Option<Uuid>,
354 transfer_id: Option<Uuid>,
355 status: Option<DocStatus>,
356 posting_state: Option<GlPostingState>,
357 journal_id: Option<Uuid>,
358 accounting_post_id: Option<Uuid>,
359 posted_at: Option<DateTime<Utc>>,
360 notes: Option<String>,
361}
362
363impl DeliveryNoteBuilder {
364 pub fn delivery_number(mut self, value: String) -> Self {
366 self.delivery_number = Some(value);
367 self
368 }
369
370 pub fn branch_id(mut self, value: Uuid) -> Self {
372 self.branch_id = Some(value);
373 self
374 }
375
376 pub fn customer_id(mut self, value: Uuid) -> Self {
378 self.customer_id = Some(value);
379 self
380 }
381
382 pub fn source_so_id(mut self, value: Uuid) -> Self {
384 self.source_so_id = Some(value);
385 self
386 }
387
388 pub fn warehouse_id(mut self, value: Uuid) -> Self {
390 self.warehouse_id = Some(value);
391 self
392 }
393
394 pub fn posting_date(mut self, value: NaiveDate) -> Self {
396 self.posting_date = Some(value);
397 self
398 }
399
400 pub fn total_cogs(mut self, value: Decimal) -> Self {
402 self.total_cogs = Some(value);
403 self
404 }
405
406 pub fn cogs_account_id(mut self, value: Uuid) -> Self {
408 self.cogs_account_id = Some(value);
409 self
410 }
411
412 pub fn inventory_account_id(mut self, value: Uuid) -> Self {
414 self.inventory_account_id = Some(value);
415 self
416 }
417
418 pub fn transfer_id(mut self, value: Uuid) -> Self {
420 self.transfer_id = Some(value);
421 self
422 }
423
424 pub fn status(mut self, value: DocStatus) -> Self {
426 self.status = Some(value);
427 self
428 }
429
430 pub fn posting_state(mut self, value: GlPostingState) -> Self {
432 self.posting_state = Some(value);
433 self
434 }
435
436 pub fn journal_id(mut self, value: Uuid) -> Self {
438 self.journal_id = Some(value);
439 self
440 }
441
442 pub fn accounting_post_id(mut self, value: Uuid) -> Self {
444 self.accounting_post_id = Some(value);
445 self
446 }
447
448 pub fn posted_at(mut self, value: DateTime<Utc>) -> Self {
450 self.posted_at = Some(value);
451 self
452 }
453
454 pub fn notes(mut self, value: String) -> Self {
456 self.notes = Some(value);
457 self
458 }
459
460 pub fn build(self) -> Result<DeliveryNote, String> {
464 let delivery_number = self.delivery_number.ok_or_else(|| "delivery_number is required".to_string())?;
465 let customer_id = self.customer_id.ok_or_else(|| "customer_id is required".to_string())?;
466 let warehouse_id = self.warehouse_id.ok_or_else(|| "warehouse_id is required".to_string())?;
467 let posting_date = self.posting_date.ok_or_else(|| "posting_date is required".to_string())?;
468 let cogs_account_id = self.cogs_account_id.ok_or_else(|| "cogs_account_id is required".to_string())?;
469 let inventory_account_id = self.inventory_account_id.ok_or_else(|| "inventory_account_id is required".to_string())?;
470
471 Ok(DeliveryNote {
472 id: Uuid::new_v4(),
473 delivery_number,
474 branch_id: self.branch_id,
475 customer_id,
476 source_so_id: self.source_so_id,
477 warehouse_id,
478 posting_date,
479 total_cogs: self.total_cogs.unwrap_or(Decimal::from(0)),
480 cogs_account_id,
481 inventory_account_id,
482 transfer_id: self.transfer_id,
483 status: self.status.unwrap_or_default(),
484 posting_state: self.posting_state.unwrap_or_default(),
485 journal_id: self.journal_id,
486 accounting_post_id: self.accounting_post_id,
487 posted_at: self.posted_at,
488 notes: self.notes,
489 metadata: AuditMetadata::default(),
490 })
491 }
492}