Skip to main content

backbone_inventory/domain/entity/
route.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5use super::AuditMetadata;
6
7/// Strongly-typed ID for Route
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9#[serde(transparent)]
10pub struct RouteId(pub Uuid);
11
12impl RouteId {
13    pub fn new(id: Uuid) -> Self { Self(id) }
14    pub fn generate() -> Self { Self(Uuid::new_v4()) }
15    pub fn into_inner(self) -> Uuid { self.0 }
16}
17
18impl std::fmt::Display for RouteId {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        write!(f, "{}", self.0)
21    }
22}
23
24impl std::str::FromStr for RouteId {
25    type Err = uuid::Error;
26    fn from_str(s: &str) -> Result<Self, Self::Err> {
27        Ok(Self(Uuid::parse_str(s)?))
28    }
29}
30
31impl From<Uuid> for RouteId {
32    fn from(id: Uuid) -> Self { Self(id) }
33}
34
35impl From<RouteId> for Uuid {
36    fn from(id: RouteId) -> Self { id.0 }
37}
38
39impl AsRef<Uuid> for RouteId {
40    fn as_ref(&self) -> &Uuid { &self.0 }
41}
42
43impl std::ops::Deref for RouteId {
44    type Target = Uuid;
45    fn deref(&self) -> &Self::Target { &self.0 }
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
49pub struct Route {
50    pub id: Uuid,
51    pub name: String,
52    pub active: bool,
53    pub sequence: i32,
54    pub product_selectable: bool,
55    pub product_categ_selectable: bool,
56    pub warehouse_selectable: bool,
57    #[serde(default)]
58    #[sqlx(json)]
59    pub metadata: AuditMetadata,
60}
61
62impl Route {
63    /// Create a builder for Route
64    pub fn builder() -> RouteBuilder {
65        <RouteBuilder as Default>::default()
66    }
67
68    /// Create a new Route with required fields
69    pub fn new(name: String, active: bool, sequence: i32, product_selectable: bool, product_categ_selectable: bool, warehouse_selectable: bool) -> Self {
70        Self {
71            id: Uuid::new_v4(),
72            name,
73            active,
74            sequence,
75            product_selectable,
76            product_categ_selectable,
77            warehouse_selectable,
78            metadata: AuditMetadata::default(),
79        }
80    }
81
82    /// Get the entity's unique identifier
83    pub fn id(&self) -> &Uuid {
84        &self.id
85    }
86
87    /// Get a strongly-typed ID for this entity
88    pub fn typed_id(&self) -> RouteId {
89        RouteId(self.id)
90    }
91
92    /// Get when this entity was created
93    pub fn created_at(&self) -> Option<&DateTime<Utc>> {
94        self.metadata.created_at.as_ref()
95    }
96
97    /// Get when this entity was last updated
98    pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
99        self.metadata.updated_at.as_ref()
100    }
101
102    /// Check if this entity is soft deleted
103    pub fn is_deleted(&self) -> bool {
104        self.metadata.deleted_at.is_some()
105    }
106
107    /// Check if this entity is active (not deleted)
108    pub fn is_active(&self) -> bool {
109        self.metadata.deleted_at.is_none()
110    }
111
112    /// Get when this entity was deleted
113    pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
114        self.metadata.deleted_at.as_ref()
115    }
116
117    /// Get who created this entity
118    pub fn created_by(&self) -> Option<&Uuid> {
119        self.metadata.created_by.as_ref()
120    }
121
122    /// Get who last updated this entity
123    pub fn updated_by(&self) -> Option<&Uuid> {
124        self.metadata.updated_by.as_ref()
125    }
126
127    /// Get who deleted this entity
128    pub fn deleted_by(&self) -> Option<&Uuid> {
129        self.metadata.deleted_by.as_ref()
130    }
131
132
133    // ==========================================================
134    // Partial Update
135    // ==========================================================
136
137    /// Apply partial updates from a map of field name to JSON value
138    pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
139        for (key, value) in fields {
140            match key.as_str() {
141                "name" => {
142                    if let Ok(v) = serde_json::from_value(value) { self.name = v; }
143                }
144                "active" => {
145                    if let Ok(v) = serde_json::from_value(value) { self.active = v; }
146                }
147                "sequence" => {
148                    if let Ok(v) = serde_json::from_value(value) { self.sequence = v; }
149                }
150                "product_selectable" => {
151                    if let Ok(v) = serde_json::from_value(value) { self.product_selectable = v; }
152                }
153                "product_categ_selectable" => {
154                    if let Ok(v) = serde_json::from_value(value) { self.product_categ_selectable = v; }
155                }
156                "warehouse_selectable" => {
157                    if let Ok(v) = serde_json::from_value(value) { self.warehouse_selectable = v; }
158                }
159                _ => {} // ignore unknown fields
160            }
161        }
162    }
163
164}
165
166impl super::Entity for Route {
167    type Id = Uuid;
168
169    fn entity_id(&self) -> &Self::Id {
170        &self.id
171    }
172
173    fn entity_type() -> &'static str {
174        "Route"
175    }
176}
177
178impl backbone_core::PersistentEntity for Route {
179    fn entity_id(&self) -> String {
180        self.id.to_string()
181    }
182    fn set_entity_id(&mut self, id: String) {
183        if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
184            self.id = uuid;
185        }
186    }
187    fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
188        self.metadata.created_at
189    }
190    fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
191        self.metadata.created_at = Some(ts);
192    }
193    fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
194        self.metadata.updated_at
195    }
196    fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
197        self.metadata.updated_at = Some(ts);
198    }
199    fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
200        self.metadata.deleted_at
201    }
202    fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
203        self.metadata.deleted_at = ts;
204    }
205}
206
207impl backbone_orm::EntityRepoMeta for Route {
208    fn column_types() -> std::collections::HashMap<String, String> {
209        let mut m = std::collections::HashMap::new();
210        m.insert("id".to_string(), "uuid".to_string());
211        m
212    }
213    fn search_fields() -> &'static [&'static str] {
214        &["name"]
215    }
216}
217
218/// Builder for Route entity
219///
220/// Provides a fluent API for constructing Route instances.
221/// System fields (id, metadata, timestamps) are auto-initialized.
222#[derive(Debug, Clone, Default)]
223pub struct RouteBuilder {
224    name: Option<String>,
225    active: Option<bool>,
226    sequence: Option<i32>,
227    product_selectable: Option<bool>,
228    product_categ_selectable: Option<bool>,
229    warehouse_selectable: Option<bool>,
230}
231
232impl RouteBuilder {
233    /// Set the name field (required)
234    pub fn name(mut self, value: String) -> Self {
235        self.name = Some(value);
236        self
237    }
238
239    /// Set the active field (default: `false`)
240    pub fn active(mut self, value: bool) -> Self {
241        self.active = Some(value);
242        self
243    }
244
245    /// Set the sequence field (default: `0`)
246    pub fn sequence(mut self, value: i32) -> Self {
247        self.sequence = Some(value);
248        self
249    }
250
251    /// Set the product_selectable field (default: `true`)
252    pub fn product_selectable(mut self, value: bool) -> Self {
253        self.product_selectable = Some(value);
254        self
255    }
256
257    /// Set the product_categ_selectable field (default: `false`)
258    pub fn product_categ_selectable(mut self, value: bool) -> Self {
259        self.product_categ_selectable = Some(value);
260        self
261    }
262
263    /// Set the warehouse_selectable field (default: `false`)
264    pub fn warehouse_selectable(mut self, value: bool) -> Self {
265        self.warehouse_selectable = Some(value);
266        self
267    }
268
269    /// Build the Route entity
270    ///
271    /// Returns Err if any required field without a default is missing.
272    pub fn build(self) -> Result<Route, String> {
273        let name = self.name.ok_or_else(|| "name is required".to_string())?;
274
275        Ok(Route {
276            id: Uuid::new_v4(),
277            name,
278            active: self.active.unwrap_or(false),
279            sequence: self.sequence.unwrap_or(0),
280            product_selectable: self.product_selectable.unwrap_or(true),
281            product_categ_selectable: self.product_categ_selectable.unwrap_or(false),
282            warehouse_selectable: self.warehouse_selectable.unwrap_or(false),
283            metadata: AuditMetadata::default(),
284        })
285    }
286}