Skip to main content

backbone_inventory/domain/entity/
warehouse.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5
6use super::WarehouseType;
7use super::AuditMetadata;
8
9/// Strongly-typed ID for Warehouse
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(transparent)]
12pub struct WarehouseId(pub Uuid);
13
14impl WarehouseId {
15    pub fn new(id: Uuid) -> Self { Self(id) }
16    pub fn generate() -> Self { Self(Uuid::new_v4()) }
17    pub fn into_inner(self) -> Uuid { self.0 }
18}
19
20impl std::fmt::Display for WarehouseId {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        write!(f, "{}", self.0)
23    }
24}
25
26impl std::str::FromStr for WarehouseId {
27    type Err = uuid::Error;
28    fn from_str(s: &str) -> Result<Self, Self::Err> {
29        Ok(Self(Uuid::parse_str(s)?))
30    }
31}
32
33impl From<Uuid> for WarehouseId {
34    fn from(id: Uuid) -> Self { Self(id) }
35}
36
37impl From<WarehouseId> for Uuid {
38    fn from(id: WarehouseId) -> Self { id.0 }
39}
40
41impl AsRef<Uuid> for WarehouseId {
42    fn as_ref(&self) -> &Uuid { &self.0 }
43}
44
45impl std::ops::Deref for WarehouseId {
46    type Target = Uuid;
47    fn deref(&self) -> &Self::Target { &self.0 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
51pub struct Warehouse {
52    pub id: Uuid,
53    pub code: String,
54    pub name: String,
55    pub warehouse_type: WarehouseType,
56    pub parent_warehouse_id: Option<Uuid>,
57    pub is_group: bool,
58    #[serde(default)]
59    #[sqlx(json)]
60    pub metadata: AuditMetadata,
61}
62
63impl Warehouse {
64    /// Create a builder for Warehouse
65    pub fn builder() -> WarehouseBuilder {
66        <WarehouseBuilder as Default>::default()
67    }
68
69    /// Create a new Warehouse with required fields
70    pub fn new(code: String, name: String, warehouse_type: WarehouseType, is_group: bool) -> Self {
71        Self {
72            id: Uuid::new_v4(),
73            code,
74            name,
75            warehouse_type,
76            parent_warehouse_id: None,
77            is_group,
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) -> WarehouseId {
89        WarehouseId(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    // Fluent Setters (with_* for optional fields)
135    // ==========================================================
136
137    /// Set the parent_warehouse_id field (chainable)
138    pub fn with_parent_warehouse_id(mut self, value: Uuid) -> Self {
139        self.parent_warehouse_id = Some(value);
140        self
141    }
142
143    // ==========================================================
144    // Partial Update
145    // ==========================================================
146
147    /// Apply partial updates from a map of field name to JSON value
148    pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
149        for (key, value) in fields {
150            match key.as_str() {
151                "code" => {
152                    if let Ok(v) = serde_json::from_value(value) { self.code = v; }
153                }
154                "name" => {
155                    if let Ok(v) = serde_json::from_value(value) { self.name = v; }
156                }
157                "warehouse_type" => {
158                    if let Ok(v) = serde_json::from_value(value) { self.warehouse_type = v; }
159                }
160                "parent_warehouse_id" => {
161                    if let Ok(v) = serde_json::from_value(value) { self.parent_warehouse_id = v; }
162                }
163                "is_group" => {
164                    if let Ok(v) = serde_json::from_value(value) { self.is_group = v; }
165                }
166                _ => {} // ignore unknown fields
167            }
168        }
169    }
170
171}
172
173impl super::Entity for Warehouse {
174    type Id = Uuid;
175
176    fn entity_id(&self) -> &Self::Id {
177        &self.id
178    }
179
180    fn entity_type() -> &'static str {
181        "Warehouse"
182    }
183}
184
185impl backbone_core::PersistentEntity for Warehouse {
186    fn entity_id(&self) -> String {
187        self.id.to_string()
188    }
189    fn set_entity_id(&mut self, id: String) {
190        if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
191            self.id = uuid;
192        }
193    }
194    fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
195        self.metadata.created_at
196    }
197    fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
198        self.metadata.created_at = Some(ts);
199    }
200    fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
201        self.metadata.updated_at
202    }
203    fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
204        self.metadata.updated_at = Some(ts);
205    }
206    fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
207        self.metadata.deleted_at
208    }
209    fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
210        self.metadata.deleted_at = ts;
211    }
212}
213
214impl backbone_orm::EntityRepoMeta for Warehouse {
215    fn column_types() -> std::collections::HashMap<String, String> {
216        let mut m = std::collections::HashMap::new();
217        m.insert("id".to_string(), "uuid".to_string());
218        m.insert("parent_warehouse_id".to_string(), "uuid".to_string());
219        m.insert("warehouse_type".to_string(), "warehouse_type".to_string());
220        m
221    }
222    fn search_fields() -> &'static [&'static str] {
223        &["code", "name"]
224    }
225    fn relations() -> &'static [(&'static str, &'static str, &'static str)] {
226        &[("parentWarehouse", "warehouses", "parentWarehouseId")]
227    }
228}
229
230/// Builder for Warehouse entity
231///
232/// Provides a fluent API for constructing Warehouse instances.
233/// System fields (id, metadata, timestamps) are auto-initialized.
234#[derive(Debug, Clone, Default)]
235pub struct WarehouseBuilder {
236    code: Option<String>,
237    name: Option<String>,
238    warehouse_type: Option<WarehouseType>,
239    parent_warehouse_id: Option<Uuid>,
240    is_group: Option<bool>,
241}
242
243impl WarehouseBuilder {
244    /// Set the code field (required)
245    pub fn code(mut self, value: String) -> Self {
246        self.code = Some(value);
247        self
248    }
249
250    /// Set the name field (required)
251    pub fn name(mut self, value: String) -> Self {
252        self.name = Some(value);
253        self
254    }
255
256    /// Set the warehouse_type field (default: `WarehouseType::default()`)
257    pub fn warehouse_type(mut self, value: WarehouseType) -> Self {
258        self.warehouse_type = Some(value);
259        self
260    }
261
262    /// Set the parent_warehouse_id field (optional)
263    pub fn parent_warehouse_id(mut self, value: Uuid) -> Self {
264        self.parent_warehouse_id = Some(value);
265        self
266    }
267
268    /// Set the is_group field (default: `false`)
269    pub fn is_group(mut self, value: bool) -> Self {
270        self.is_group = Some(value);
271        self
272    }
273
274    /// Build the Warehouse entity
275    ///
276    /// Returns Err if any required field without a default is missing.
277    pub fn build(self) -> Result<Warehouse, String> {
278        let code = self.code.ok_or_else(|| "code is required".to_string())?;
279        let name = self.name.ok_or_else(|| "name is required".to_string())?;
280
281        Ok(Warehouse {
282            id: Uuid::new_v4(),
283            code,
284            name,
285            warehouse_type: self.warehouse_type.unwrap_or_default(),
286            parent_warehouse_id: self.parent_warehouse_id,
287            is_group: self.is_group.unwrap_or(false),
288            metadata: AuditMetadata::default(),
289        })
290    }
291}