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}
265
266impl super::Entity for Transfer {
267    type Id = Uuid;
268
269    fn entity_id(&self) -> &Self::Id {
270        &self.id
271    }
272
273    fn entity_type() -> &'static str {
274        "Transfer"
275    }
276}
277
278impl backbone_core::PersistentEntity for Transfer {
279    fn write_protected_fields() -> &'static [&'static str] {
280        &["state"]
281    }
282    fn entity_id(&self) -> String {
283        self.id.to_string()
284    }
285    fn set_entity_id(&mut self, id: String) {
286        if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
287            self.id = uuid;
288        }
289    }
290    fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
291        self.metadata.created_at
292    }
293    fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
294        self.metadata.created_at = Some(ts);
295    }
296    fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
297        self.metadata.updated_at
298    }
299    fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
300        self.metadata.updated_at = Some(ts);
301    }
302    fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
303        self.metadata.deleted_at
304    }
305    fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
306        self.metadata.deleted_at = ts;
307    }
308}
309
310impl backbone_orm::EntityRepoMeta for Transfer {
311    fn column_types() -> std::collections::HashMap<String, String> {
312        let mut m = std::collections::HashMap::new();
313        m.insert("id".to_string(), "uuid".to_string());
314        m.insert("picking_type_id".to_string(), "uuid".to_string());
315        m.insert("location_id".to_string(), "uuid".to_string());
316        m.insert("location_dest_id".to_string(), "uuid".to_string());
317        m.insert("partner_id".to_string(), "uuid".to_string());
318        m.insert("backorder_id".to_string(), "uuid".to_string());
319        m.insert("return_id".to_string(), "uuid".to_string());
320        m.insert("batch_id".to_string(), "uuid".to_string());
321        m.insert("priority".to_string(), "priority".to_string());
322        m.insert("move_type".to_string(), "move_type".to_string());
323        m.insert("state".to_string(), "transfer_state".to_string());
324        m.insert("scheduled_date".to_string(), "timestamptz".to_string());
325        m.insert("date_done".to_string(), "timestamptz".to_string());
326        m
327    }
328    fn search_fields() -> &'static [&'static str] {
329        &["name"]
330    }
331    fn relations() -> &'static [(&'static str, &'static str, &'static str)] {
332        &[("pickingType", "operation_types", "pickingTypeId"), ("location", "locations", "locationId"), ("locationDest", "locations", "locationDestId"), ("backorder", "transfers", "backorderId"), ("return", "transfers", "returnId")]
333    }
334}
335
336/// Builder for Transfer entity
337///
338/// Provides a fluent API for constructing Transfer instances.
339/// System fields (id, metadata, timestamps) are auto-initialized.
340#[derive(Debug, Clone, Default)]
341pub struct TransferBuilder {
342    name: Option<String>,
343    origin: Option<String>,
344    note: Option<String>,
345    priority: Option<Priority>,
346    picking_type_id: Option<Uuid>,
347    location_id: Option<Uuid>,
348    location_dest_id: Option<Uuid>,
349    partner_id: Option<Uuid>,
350    move_type: Option<MoveType>,
351    scheduled_date: Option<DateTime<Utc>>,
352    date_done: Option<DateTime<Utc>>,
353    state: Option<TransferState>,
354    is_locked: Option<bool>,
355    backorder_id: Option<Uuid>,
356    return_id: Option<Uuid>,
357    batch_id: Option<Uuid>,
358}
359
360impl TransferBuilder {
361    /// Set the name field (default: `"/".to_string()`)
362    pub fn name(mut self, value: String) -> Self {
363        self.name = Some(value);
364        self
365    }
366
367    /// Set the origin field (optional)
368    pub fn origin(mut self, value: String) -> Self {
369        self.origin = Some(value);
370        self
371    }
372
373    /// Set the note field (optional)
374    pub fn note(mut self, value: String) -> Self {
375        self.note = Some(value);
376        self
377    }
378
379    /// Set the priority field (default: `Priority::default()`)
380    pub fn priority(mut self, value: Priority) -> Self {
381        self.priority = Some(value);
382        self
383    }
384
385    /// Set the picking_type_id field (required)
386    pub fn picking_type_id(mut self, value: Uuid) -> Self {
387        self.picking_type_id = Some(value);
388        self
389    }
390
391    /// Set the location_id field (required)
392    pub fn location_id(mut self, value: Uuid) -> Self {
393        self.location_id = Some(value);
394        self
395    }
396
397    /// Set the location_dest_id field (required)
398    pub fn location_dest_id(mut self, value: Uuid) -> Self {
399        self.location_dest_id = Some(value);
400        self
401    }
402
403    /// Set the partner_id field (optional)
404    pub fn partner_id(mut self, value: Uuid) -> Self {
405        self.partner_id = Some(value);
406        self
407    }
408
409    /// Set the move_type field (default: `MoveType::default()`)
410    pub fn move_type(mut self, value: MoveType) -> Self {
411        self.move_type = Some(value);
412        self
413    }
414
415    /// Set the scheduled_date field (default: `Utc::now()`)
416    pub fn scheduled_date(mut self, value: DateTime<Utc>) -> Self {
417        self.scheduled_date = Some(value);
418        self
419    }
420
421    /// Set the date_done field (optional)
422    pub fn date_done(mut self, value: DateTime<Utc>) -> Self {
423        self.date_done = Some(value);
424        self
425    }
426
427    /// Set the state field (default: `TransferState::default()`)
428    pub fn state(mut self, value: TransferState) -> Self {
429        self.state = Some(value);
430        self
431    }
432
433    /// Set the is_locked field (default: `true`)
434    pub fn is_locked(mut self, value: bool) -> Self {
435        self.is_locked = Some(value);
436        self
437    }
438
439    /// Set the backorder_id field (optional)
440    pub fn backorder_id(mut self, value: Uuid) -> Self {
441        self.backorder_id = Some(value);
442        self
443    }
444
445    /// Set the return_id field (optional)
446    pub fn return_id(mut self, value: Uuid) -> Self {
447        self.return_id = Some(value);
448        self
449    }
450
451    /// Set the batch_id field (optional)
452    pub fn batch_id(mut self, value: Uuid) -> Self {
453        self.batch_id = Some(value);
454        self
455    }
456
457    /// Build the Transfer entity
458    ///
459    /// Returns Err if any required field without a default is missing.
460    pub fn build(self) -> Result<Transfer, String> {
461        let picking_type_id = self.picking_type_id.ok_or_else(|| "picking_type_id is required".to_string())?;
462        let location_id = self.location_id.ok_or_else(|| "location_id is required".to_string())?;
463        let location_dest_id = self.location_dest_id.ok_or_else(|| "location_dest_id is required".to_string())?;
464
465        Ok(Transfer {
466            id: Uuid::new_v4(),
467            name: self.name.unwrap_or("/".to_string()),
468            origin: self.origin,
469            note: self.note,
470            priority: self.priority.unwrap_or_default(),
471            picking_type_id,
472            location_id,
473            location_dest_id,
474            partner_id: self.partner_id,
475            move_type: self.move_type.unwrap_or_default(),
476            scheduled_date: self.scheduled_date.unwrap_or(Utc::now()),
477            date_done: self.date_done,
478            state: self.state.unwrap_or_default(),
479            is_locked: self.is_locked.unwrap_or(true),
480            backorder_id: self.backorder_id,
481            return_id: self.return_id,
482            batch_id: self.batch_id,
483            metadata: AuditMetadata::default(),
484        })
485    }
486}