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}
198
199impl super::Entity for Package {
200    type Id = Uuid;
201
202    fn entity_id(&self) -> &Self::Id {
203        &self.id
204    }
205
206    fn entity_type() -> &'static str {
207        "Package"
208    }
209}
210
211impl backbone_core::PersistentEntity for Package {
212    fn entity_id(&self) -> String {
213        self.id.to_string()
214    }
215    fn set_entity_id(&mut self, id: String) {
216        if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
217            self.id = uuid;
218        }
219    }
220    fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
221        self.metadata.created_at
222    }
223    fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
224        self.metadata.created_at = Some(ts);
225    }
226    fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
227        self.metadata.updated_at
228    }
229    fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
230        self.metadata.updated_at = Some(ts);
231    }
232    fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
233        self.metadata.deleted_at
234    }
235    fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
236        self.metadata.deleted_at = ts;
237    }
238}
239
240impl backbone_orm::EntityRepoMeta for Package {
241    fn column_types() -> std::collections::HashMap<String, String> {
242        let mut m = std::collections::HashMap::new();
243        m.insert("id".to_string(), "uuid".to_string());
244        m.insert("location_id".to_string(), "uuid".to_string());
245        m.insert("parent_package_id".to_string(), "uuid".to_string());
246        m.insert("package_type_id".to_string(), "uuid".to_string());
247        m.insert("pack_date".to_string(), "date".to_string());
248        m
249    }
250    fn search_fields() -> &'static [&'static str] {
251        &["name", "complete_name", "parent_path"]
252    }
253    fn relations() -> &'static [(&'static str, &'static str, &'static str)] {
254        &[("location", "locations", "locationId"), ("parentPackage", "packages", "parentPackageId"), ("packageType", "package_types", "packageTypeId")]
255    }
256}
257
258/// Builder for Package entity
259///
260/// Provides a fluent API for constructing Package instances.
261/// System fields (id, metadata, timestamps) are auto-initialized.
262#[derive(Debug, Clone, Default)]
263pub struct PackageBuilder {
264    name: Option<String>,
265    complete_name: Option<String>,
266    location_id: Option<Uuid>,
267    parent_package_id: Option<Uuid>,
268    parent_path: Option<String>,
269    pack_date: Option<NaiveDate>,
270    package_type_id: Option<Uuid>,
271}
272
273impl PackageBuilder {
274    /// Set the name field (required)
275    pub fn name(mut self, value: String) -> Self {
276        self.name = Some(value);
277        self
278    }
279
280    /// Set the complete_name field (required)
281    pub fn complete_name(mut self, value: String) -> Self {
282        self.complete_name = Some(value);
283        self
284    }
285
286    /// Set the location_id field (optional)
287    pub fn location_id(mut self, value: Uuid) -> Self {
288        self.location_id = Some(value);
289        self
290    }
291
292    /// Set the parent_package_id field (optional)
293    pub fn parent_package_id(mut self, value: Uuid) -> Self {
294        self.parent_package_id = Some(value);
295        self
296    }
297
298    /// Set the parent_path field (required)
299    pub fn parent_path(mut self, value: String) -> Self {
300        self.parent_path = Some(value);
301        self
302    }
303
304    /// Set the pack_date field (optional)
305    pub fn pack_date(mut self, value: NaiveDate) -> Self {
306        self.pack_date = Some(value);
307        self
308    }
309
310    /// Set the package_type_id field (optional)
311    pub fn package_type_id(mut self, value: Uuid) -> Self {
312        self.package_type_id = Some(value);
313        self
314    }
315
316    /// Build the Package entity
317    ///
318    /// Returns Err if any required field without a default is missing.
319    pub fn build(self) -> Result<Package, String> {
320        let name = self.name.ok_or_else(|| "name is required".to_string())?;
321        let complete_name = self.complete_name.ok_or_else(|| "complete_name is required".to_string())?;
322        let parent_path = self.parent_path.ok_or_else(|| "parent_path is required".to_string())?;
323
324        Ok(Package {
325            id: Uuid::new_v4(),
326            name,
327            complete_name,
328            location_id: self.location_id,
329            parent_package_id: self.parent_package_id,
330            parent_path,
331            pack_date: self.pack_date,
332            package_type_id: self.package_type_id,
333            metadata: AuditMetadata::default(),
334        })
335    }
336}