Skip to main content

backbone_inventory/domain/entity/
transfer.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5
6use super::Priority;
7use super::MoveType;
8use super::TransferState;
9use super::AuditMetadata;
10
11/// Strongly-typed ID for Transfer
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(transparent)]
14pub struct TransferId(pub Uuid);
15
16impl TransferId {
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 TransferId {
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 TransferId {
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 TransferId {
36    fn from(id: Uuid) -> Self { Self(id) }
37}
38
39impl From<TransferId> for Uuid {
40    fn from(id: TransferId) -> Self { id.0 }
41}
42
43impl AsRef<Uuid> for TransferId {
44    fn as_ref(&self) -> &Uuid { &self.0 }
45}
46
47impl std::ops::Deref for TransferId {
48    type Target = Uuid;
49    fn deref(&self) -> &Self::Target { &self.0 }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
53pub struct Transfer {
54    pub id: Uuid,
55    pub name: String,
56    pub origin: Option<String>,
57    pub note: Option<String>,
58    pub priority: Priority,
59    pub picking_type_id: Uuid,
60    pub location_id: Uuid,
61    pub location_dest_id: Uuid,
62    pub partner_id: Option<Uuid>,
63    pub move_type: MoveType,
64    pub scheduled_date: DateTime<Utc>,
65    pub date_done: Option<DateTime<Utc>>,
66    pub state: TransferState,
67    pub is_locked: bool,
68    pub backorder_id: Option<Uuid>,
69    pub return_id: Option<Uuid>,
70    pub batch_id: Option<Uuid>,
71    #[serde(default)]
72    #[sqlx(json)]
73    pub metadata: AuditMetadata,
74}
75
76impl Transfer {
77    /// Create a builder for Transfer
78    pub fn builder() -> TransferBuilder {
79        <TransferBuilder as Default>::default()
80    }
81
82    /// Create a new Transfer with required fields
83    pub fn new(name: String, priority: Priority, picking_type_id: Uuid, location_id: Uuid, location_dest_id: Uuid, move_type: MoveType, scheduled_date: DateTime<Utc>, state: TransferState, is_locked: bool) -> Self {
84        Self {
85            id: Uuid::new_v4(),
86            name,
87            origin: None,
88            note: None,
89            priority,
90            picking_type_id,
91            location_id,
92            location_dest_id,
93            partner_id: None,
94            move_type,
95            scheduled_date,
96            date_done: None,
97            state,
98            is_locked,
99            backorder_id: None,
100            return_id: None,
101            batch_id: None,
102            metadata: AuditMetadata::default(),
103        }
104    }
105
106    /// Get the entity's unique identifier
107    pub fn id(&self) -> &Uuid {
108        &self.id
109    }
110
111    /// Get a strongly-typed ID for this entity
112    pub fn typed_id(&self) -> TransferId {
113        TransferId(self.id)
114    }
115
116    /// Get when this entity was created
117    pub fn created_at(&self) -> Option<&DateTime<Utc>> {
118        self.metadata.created_at.as_ref()
119    }
120
121    /// Get when this entity was last updated
122    pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
123        self.metadata.updated_at.as_ref()
124    }
125
126    /// Check if this entity is soft deleted
127    pub fn is_deleted(&self) -> bool {
128        self.metadata.deleted_at.is_some()
129    }
130
131    /// Check if this entity is active (not deleted)
132    pub fn is_active(&self) -> bool {
133        self.metadata.deleted_at.is_none()
134    }
135
136    /// Get when this entity was deleted
137    pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
138        self.metadata.deleted_at.as_ref()
139    }
140
141    /// Get who created this entity
142    pub fn created_by(&self) -> Option<&Uuid> {
143        self.metadata.created_by.as_ref()
144    }
145
146    /// Get who last updated this entity
147    pub fn updated_by(&self) -> Option<&Uuid> {
148        self.metadata.updated_by.as_ref()
149    }
150
151    /// Get who deleted this entity
152    pub fn deleted_by(&self) -> Option<&Uuid> {
153        self.metadata.deleted_by.as_ref()
154    }
155
156
157    // ==========================================================
158    // Fluent Setters (with_* for optional fields)
159    // ==========================================================
160
161    /// Set the origin field (chainable)
162    pub fn with_origin(mut self, value: String) -> Self {
163        self.origin = Some(value);
164        self
165    }
166
167    /// Set the note field (chainable)
168    pub fn with_note(mut self, value: String) -> Self {
169        self.note = Some(value);
170        self
171    }
172
173    /// Set the partner_id field (chainable)
174    pub fn with_partner_id(mut self, value: Uuid) -> Self {
175        self.partner_id = Some(value);
176        self
177    }
178
179    /// Set the date_done field (chainable)
180    pub fn with_date_done(mut self, value: DateTime<Utc>) -> Self {
181        self.date_done = Some(value);
182        self
183    }
184
185    /// Set the backorder_id field (chainable)
186    pub fn with_backorder_id(mut self, value: Uuid) -> Self {
187        self.backorder_id = Some(value);
188        self
189    }
190
191    /// Set the return_id field (chainable)
192    pub fn with_return_id(mut self, value: Uuid) -> Self {
193        self.return_id = Some(value);
194        self
195    }
196
197    /// Set the batch_id field (chainable)
198    pub fn with_batch_id(mut self, value: Uuid) -> Self {
199        self.batch_id = Some(value);
200        self
201    }
202
203    // ==========================================================
204    // Partial Update
205    // ==========================================================
206
207    /// Apply partial updates from a map of field name to JSON value
208    pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
209        for (key, value) in fields {
210            match key.as_str() {
211                "name" => {
212                    if let Ok(v) = serde_json::from_value(value) { self.name = v; }
213                }
214                "origin" => {
215                    if let Ok(v) = serde_json::from_value(value) { self.origin = v; }
216                }
217                "note" => {
218                    if let Ok(v) = serde_json::from_value(value) { self.note = v; }
219                }
220                "priority" => {
221                    if let Ok(v) = serde_json::from_value(value) { self.priority = v; }
222                }
223                "picking_type_id" => {
224                    if let Ok(v) = serde_json::from_value(value) { self.picking_type_id = v; }
225                }
226                "location_id" => {
227                    if let Ok(v) = serde_json::from_value(value) { self.location_id = v; }
228                }
229                "location_dest_id" => {
230                    if let Ok(v) = serde_json::from_value(value) { self.location_dest_id = v; }
231                }
232                "partner_id" => {
233                    if let Ok(v) = serde_json::from_value(value) { self.partner_id = v; }
234                }
235                "move_type" => {
236                    if let Ok(v) = serde_json::from_value(value) { self.move_type = v; }
237                }
238                "scheduled_date" => {
239                    if let Ok(v) = serde_json::from_value(value) { self.scheduled_date = v; }
240                }
241                "date_done" => {
242                    if let Ok(v) = serde_json::from_value(value) { self.date_done = v; }
243                }
244                "state" => {
245                    if let Ok(v) = serde_json::from_value(value) { self.state = v; }
246                }
247                "is_locked" => {
248                    if let Ok(v) = serde_json::from_value(value) { self.is_locked = v; }
249                }
250                "backorder_id" => {
251                    if let Ok(v) = serde_json::from_value(value) { self.backorder_id = v; }
252                }
253                "return_id" => {
254                    if let Ok(v) = serde_json::from_value(value) { self.return_id = v; }
255                }
256                "batch_id" => {
257                    if let Ok(v) = serde_json::from_value(value) { self.batch_id = v; }
258                }
259                _ => {} // ignore unknown fields
260            }
261        }
262    }
263
264    // <<< CUSTOM METHODS START >>>
265    // <<< CUSTOM METHODS END >>>
266}
267
268impl super::Entity for Transfer {
269    type Id = Uuid;
270
271    fn entity_id(&self) -> &Self::Id {
272        &self.id
273    }
274
275    fn entity_type() -> &'static str {
276        "Transfer"
277    }
278}
279
280impl backbone_core::PersistentEntity for Transfer {
281    fn write_protected_fields() -> &'static [&'static str] {
282        &["state"]
283    }
284    fn entity_id(&self) -> String {
285        self.id.to_string()
286    }
287    fn set_entity_id(&mut self, id: String) {
288        if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
289            self.id = uuid;
290        }
291    }
292    fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
293        self.metadata.created_at
294    }
295    fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
296        self.metadata.created_at = Some(ts);
297    }
298    fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
299        self.metadata.updated_at
300    }
301    fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
302        self.metadata.updated_at = Some(ts);
303    }
304    fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
305        self.metadata.deleted_at
306    }
307    fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
308        self.metadata.deleted_at = ts;
309    }
310}
311
312impl backbone_orm::EntityRepoMeta for Transfer {
313    fn column_types() -> std::collections::HashMap<String, String> {
314        let mut m = std::collections::HashMap::new();
315        m.insert("id".to_string(), "uuid".to_string());
316        m.insert("picking_type_id".to_string(), "uuid".to_string());
317        m.insert("location_id".to_string(), "uuid".to_string());
318        m.insert("location_dest_id".to_string(), "uuid".to_string());
319        m.insert("partner_id".to_string(), "uuid".to_string());
320        m.insert("backorder_id".to_string(), "uuid".to_string());
321        m.insert("return_id".to_string(), "uuid".to_string());
322        m.insert("batch_id".to_string(), "uuid".to_string());
323        m.insert("priority".to_string(), "priority".to_string());
324        m.insert("move_type".to_string(), "move_type".to_string());
325        m.insert("state".to_string(), "transfer_state".to_string());
326        m.insert("scheduled_date".to_string(), "timestamptz".to_string());
327        m.insert("date_done".to_string(), "timestamptz".to_string());
328        m
329    }
330    fn search_fields() -> &'static [&'static str] {
331        &["name"]
332    }
333}
334
335/// Builder for Transfer entity
336///
337/// Provides a fluent API for constructing Transfer instances.
338/// System fields (id, metadata, timestamps) are auto-initialized.
339#[derive(Debug, Clone, Default)]
340pub struct TransferBuilder {
341    name: Option<String>,
342    origin: Option<String>,
343    note: Option<String>,
344    priority: Option<Priority>,
345    picking_type_id: Option<Uuid>,
346    location_id: Option<Uuid>,
347    location_dest_id: Option<Uuid>,
348    partner_id: Option<Uuid>,
349    move_type: Option<MoveType>,
350    scheduled_date: Option<DateTime<Utc>>,
351    date_done: Option<DateTime<Utc>>,
352    state: Option<TransferState>,
353    is_locked: Option<bool>,
354    backorder_id: Option<Uuid>,
355    return_id: Option<Uuid>,
356    batch_id: Option<Uuid>,
357}
358
359impl TransferBuilder {
360    /// Set the name field (default: `"/".to_string()`)
361    pub fn name(mut self, value: String) -> Self {
362        self.name = Some(value);
363        self
364    }
365
366    /// Set the origin field (optional)
367    pub fn origin(mut self, value: String) -> Self {
368        self.origin = Some(value);
369        self
370    }
371
372    /// Set the note field (optional)
373    pub fn note(mut self, value: String) -> Self {
374        self.note = Some(value);
375        self
376    }
377
378    /// Set the priority field (default: `Priority::default()`)
379    pub fn priority(mut self, value: Priority) -> Self {
380        self.priority = Some(value);
381        self
382    }
383
384    /// Set the picking_type_id field (required)
385    pub fn picking_type_id(mut self, value: Uuid) -> Self {
386        self.picking_type_id = Some(value);
387        self
388    }
389
390    /// Set the location_id field (required)
391    pub fn location_id(mut self, value: Uuid) -> Self {
392        self.location_id = Some(value);
393        self
394    }
395
396    /// Set the location_dest_id field (required)
397    pub fn location_dest_id(mut self, value: Uuid) -> Self {
398        self.location_dest_id = Some(value);
399        self
400    }
401
402    /// Set the partner_id field (optional)
403    pub fn partner_id(mut self, value: Uuid) -> Self {
404        self.partner_id = Some(value);
405        self
406    }
407
408    /// Set the move_type field (default: `MoveType::default()`)
409    pub fn move_type(mut self, value: MoveType) -> Self {
410        self.move_type = Some(value);
411        self
412    }
413
414    /// Set the scheduled_date field (default: `Utc::now()`)
415    pub fn scheduled_date(mut self, value: DateTime<Utc>) -> Self {
416        self.scheduled_date = Some(value);
417        self
418    }
419
420    /// Set the date_done field (optional)
421    pub fn date_done(mut self, value: DateTime<Utc>) -> Self {
422        self.date_done = Some(value);
423        self
424    }
425
426    /// Set the state field (default: `TransferState::default()`)
427    pub fn state(mut self, value: TransferState) -> Self {
428        self.state = Some(value);
429        self
430    }
431
432    /// Set the is_locked field (default: `true`)
433    pub fn is_locked(mut self, value: bool) -> Self {
434        self.is_locked = Some(value);
435        self
436    }
437
438    /// Set the backorder_id field (optional)
439    pub fn backorder_id(mut self, value: Uuid) -> Self {
440        self.backorder_id = Some(value);
441        self
442    }
443
444    /// Set the return_id field (optional)
445    pub fn return_id(mut self, value: Uuid) -> Self {
446        self.return_id = Some(value);
447        self
448    }
449
450    /// Set the batch_id field (optional)
451    pub fn batch_id(mut self, value: Uuid) -> Self {
452        self.batch_id = Some(value);
453        self
454    }
455
456    /// Build the Transfer entity
457    ///
458    /// Returns Err if any required field without a default is missing.
459    pub fn build(self) -> Result<Transfer, String> {
460        let picking_type_id = self.picking_type_id.ok_or_else(|| "picking_type_id is required".to_string())?;
461        let location_id = self.location_id.ok_or_else(|| "location_id is required".to_string())?;
462        let location_dest_id = self.location_dest_id.ok_or_else(|| "location_dest_id is required".to_string())?;
463
464        Ok(Transfer {
465            id: Uuid::new_v4(),
466            name: self.name.unwrap_or("/".to_string()),
467            origin: self.origin,
468            note: self.note,
469            priority: self.priority.unwrap_or_default(),
470            picking_type_id,
471            location_id,
472            location_dest_id,
473            partner_id: self.partner_id,
474            move_type: self.move_type.unwrap_or_default(),
475            scheduled_date: self.scheduled_date.unwrap_or(Utc::now()),
476            date_done: self.date_done,
477            state: self.state.unwrap_or_default(),
478            is_locked: self.is_locked.unwrap_or(true),
479            backorder_id: self.backorder_id,
480            return_id: self.return_id,
481            batch_id: self.batch_id,
482            metadata: AuditMetadata::default(),
483        })
484    }
485}