Skip to main content

backbone_inventory/domain/entity/
package.rs

1use chrono::{DateTime, Utc, NaiveDate};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5use super::AuditMetadata;
6
7/// Strongly-typed ID for Package
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9#[serde(transparent)]
10pub struct PackageId(pub Uuid);
11
12impl PackageId {
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 PackageId {
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 PackageId {
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 PackageId {
32    fn from(id: Uuid) -> Self { Self(id) }
33}
34
35impl From<PackageId> for Uuid {
36    fn from(id: PackageId) -> Self { id.0 }
37}
38
39impl AsRef<Uuid> for PackageId {
40    fn as_ref(&self) -> &Uuid { &self.0 }
41}
42
43impl std::ops::Deref for PackageId {
44    type Target = Uuid;
45    fn deref(&self) -> &Self::Target { &self.0 }
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
49pub struct Package {
50    pub id: Uuid,
51    pub name: String,
52    pub complete_name: String,
53    pub location_id: Option<Uuid>,
54    pub parent_package_id: Option<Uuid>,
55    pub parent_path: String,
56    pub pack_date: Option<NaiveDate>,
57    pub package_type_id: Option<Uuid>,
58    #[serde(default)]
59    #[sqlx(json)]
60    pub metadata: AuditMetadata,
61}
62
63impl Package {
64    /// Create a builder for Package
65    pub fn builder() -> PackageBuilder {
66        <PackageBuilder as Default>::default()
67    }
68
69    /// Create a new Package with required fields
70    pub fn new(name: String, complete_name: String, parent_path: String) -> Self {
71        Self {
72            id: Uuid::new_v4(),
73            name,
74            complete_name,
75            location_id: None,
76            parent_package_id: None,
77            parent_path,
78            pack_date: None,
79            package_type_id: None,
80            metadata: AuditMetadata::default(),
81        }
82    }
83
84    /// Get the entity's unique identifier
85    pub fn id(&self) -> &Uuid {
86        &self.id
87    }
88
89    /// Get a strongly-typed ID for this entity
90    pub fn typed_id(&self) -> PackageId {
91        PackageId(self.id)
92    }
93
94    /// Get when this entity was created
95    pub fn created_at(&self) -> Option<&DateTime<Utc>> {
96        self.metadata.created_at.as_ref()
97    }
98
99    /// Get when this entity was last updated
100    pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
101        self.metadata.updated_at.as_ref()
102    }
103
104    /// Check if this entity is soft deleted
105    pub fn is_deleted(&self) -> bool {
106        self.metadata.deleted_at.is_some()
107    }
108
109    /// Check if this entity is active (not deleted)
110    pub fn is_active(&self) -> bool {
111        self.metadata.deleted_at.is_none()
112    }
113
114    /// Get when this entity was deleted
115    pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
116        self.metadata.deleted_at.as_ref()
117    }
118
119    /// Get who created this entity
120    pub fn created_by(&self) -> Option<&Uuid> {
121        self.metadata.created_by.as_ref()
122    }
123
124    /// Get who last updated this entity
125    pub fn updated_by(&self) -> Option<&Uuid> {
126        self.metadata.updated_by.as_ref()
127    }
128
129    /// Get who deleted this entity
130    pub fn deleted_by(&self) -> Option<&Uuid> {
131        self.metadata.deleted_by.as_ref()
132    }
133
134
135    // ==========================================================
136    // Fluent Setters (with_* for optional fields)
137    // ==========================================================
138
139    /// Set the location_id field (chainable)
140    pub fn with_location_id(mut self, value: Uuid) -> Self {
141        self.location_id = Some(value);
142        self
143    }
144
145    /// Set the parent_package_id field (chainable)
146    pub fn with_parent_package_id(mut self, value: Uuid) -> Self {
147        self.parent_package_id = Some(value);
148        self
149    }
150
151    /// Set the pack_date field (chainable)
152    pub fn with_pack_date(mut self, value: NaiveDate) -> Self {
153        self.pack_date = Some(value);
154        self
155    }
156
157    /// Set the package_type_id field (chainable)
158    pub fn with_package_type_id(mut self, value: Uuid) -> Self {
159        self.package_type_id = Some(value);
160        self
161    }
162
163    // ==========================================================
164    // Partial Update
165    // ==========================================================
166
167    /// Apply partial updates from a map of field name to JSON value
168    pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
169        for (key, value) in fields {
170            match key.as_str() {
171                "name" => {
172                    if let Ok(v) = serde_json::from_value(value) { self.name = v; }
173                }
174                "complete_name" => {
175                    if let Ok(v) = serde_json::from_value(value) { self.complete_name = v; }
176                }
177                "location_id" => {
178                    if let Ok(v) = serde_json::from_value(value) { self.location_id = v; }
179                }
180                "parent_package_id" => {
181                    if let Ok(v) = serde_json::from_value(value) { self.parent_package_id = v; }
182                }
183                "parent_path" => {
184                    if let Ok(v) = serde_json::from_value(value) { self.parent_path = v; }
185                }
186                "pack_date" => {
187                    if let Ok(v) = serde_json::from_value(value) { self.pack_date = v; }
188                }
189                "package_type_id" => {
190                    if let Ok(v) = serde_json::from_value(value) { self.package_type_id = v; }
191                }
192                _ => {} // ignore unknown fields
193            }
194        }
195    }
196
197    // <<< CUSTOM METHODS START >>>
198    // <<< CUSTOM METHODS END >>>
199}
200
201impl super::Entity for Package {
202    type Id = Uuid;
203
204    fn entity_id(&self) -> &Self::Id {
205        &self.id
206    }
207
208    fn entity_type() -> &'static str {
209        "Package"
210    }
211}
212
213impl backbone_core::PersistentEntity for Package {
214    fn entity_id(&self) -> String {
215        self.id.to_string()
216    }
217    fn set_entity_id(&mut self, id: String) {
218        if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
219            self.id = uuid;
220        }
221    }
222    fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
223        self.metadata.created_at
224    }
225    fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
226        self.metadata.created_at = Some(ts);
227    }
228    fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
229        self.metadata.updated_at
230    }
231    fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
232        self.metadata.updated_at = Some(ts);
233    }
234    fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
235        self.metadata.deleted_at
236    }
237    fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
238        self.metadata.deleted_at = ts;
239    }
240}
241
242impl backbone_orm::EntityRepoMeta for Package {
243    fn column_types() -> std::collections::HashMap<String, String> {
244        let mut m = std::collections::HashMap::new();
245        m.insert("id".to_string(), "uuid".to_string());
246        m.insert("location_id".to_string(), "uuid".to_string());
247        m.insert("parent_package_id".to_string(), "uuid".to_string());
248        m.insert("package_type_id".to_string(), "uuid".to_string());
249        m.insert("pack_date".to_string(), "date".to_string());
250        m
251    }
252    fn search_fields() -> &'static [&'static str] {
253        &["name", "complete_name", "parent_path"]
254    }
255}
256
257/// Builder for Package entity
258///
259/// Provides a fluent API for constructing Package instances.
260/// System fields (id, metadata, timestamps) are auto-initialized.
261#[derive(Debug, Clone, Default)]
262pub struct PackageBuilder {
263    name: Option<String>,
264    complete_name: Option<String>,
265    location_id: Option<Uuid>,
266    parent_package_id: Option<Uuid>,
267    parent_path: Option<String>,
268    pack_date: Option<NaiveDate>,
269    package_type_id: Option<Uuid>,
270}
271
272impl PackageBuilder {
273    /// Set the name field (required)
274    pub fn name(mut self, value: String) -> Self {
275        self.name = Some(value);
276        self
277    }
278
279    /// Set the complete_name field (required)
280    pub fn complete_name(mut self, value: String) -> Self {
281        self.complete_name = Some(value);
282        self
283    }
284
285    /// Set the location_id field (optional)
286    pub fn location_id(mut self, value: Uuid) -> Self {
287        self.location_id = Some(value);
288        self
289    }
290
291    /// Set the parent_package_id field (optional)
292    pub fn parent_package_id(mut self, value: Uuid) -> Self {
293        self.parent_package_id = Some(value);
294        self
295    }
296
297    /// Set the parent_path field (required)
298    pub fn parent_path(mut self, value: String) -> Self {
299        self.parent_path = Some(value);
300        self
301    }
302
303    /// Set the pack_date field (optional)
304    pub fn pack_date(mut self, value: NaiveDate) -> Self {
305        self.pack_date = Some(value);
306        self
307    }
308
309    /// Set the package_type_id field (optional)
310    pub fn package_type_id(mut self, value: Uuid) -> Self {
311        self.package_type_id = Some(value);
312        self
313    }
314
315    /// Build the Package entity
316    ///
317    /// Returns Err if any required field without a default is missing.
318    pub fn build(self) -> Result<Package, String> {
319        let name = self.name.ok_or_else(|| "name is required".to_string())?;
320        let complete_name = self.complete_name.ok_or_else(|| "complete_name is required".to_string())?;
321        let parent_path = self.parent_path.ok_or_else(|| "parent_path is required".to_string())?;
322
323        Ok(Package {
324            id: Uuid::new_v4(),
325            name,
326            complete_name,
327            location_id: self.location_id,
328            parent_package_id: self.parent_package_id,
329            parent_path,
330            pack_date: self.pack_date,
331            package_type_id: self.package_type_id,
332            metadata: AuditMetadata::default(),
333        })
334    }
335}