Skip to main content

backbone_inventory/domain/entity/
quant.rs

1use chrono::{DateTime, Utc, NaiveDate};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5use rust_decimal::Decimal;
6use super::AuditMetadata;
7
8/// Strongly-typed ID for Quant
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(transparent)]
11pub struct QuantId(pub Uuid);
12
13impl QuantId {
14    pub fn new(id: Uuid) -> Self { Self(id) }
15    pub fn generate() -> Self { Self(Uuid::new_v4()) }
16    pub fn into_inner(self) -> Uuid { self.0 }
17}
18
19impl std::fmt::Display for QuantId {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        write!(f, "{}", self.0)
22    }
23}
24
25impl std::str::FromStr for QuantId {
26    type Err = uuid::Error;
27    fn from_str(s: &str) -> Result<Self, Self::Err> {
28        Ok(Self(Uuid::parse_str(s)?))
29    }
30}
31
32impl From<Uuid> for QuantId {
33    fn from(id: Uuid) -> Self { Self(id) }
34}
35
36impl From<QuantId> for Uuid {
37    fn from(id: QuantId) -> Self { id.0 }
38}
39
40impl AsRef<Uuid> for QuantId {
41    fn as_ref(&self) -> &Uuid { &self.0 }
42}
43
44impl std::ops::Deref for QuantId {
45    type Target = Uuid;
46    fn deref(&self) -> &Self::Target { &self.0 }
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
50pub struct Quant {
51    pub id: Uuid,
52    pub item_id: Uuid,
53    pub location_id: Uuid,
54    pub lot_id: Option<Uuid>,
55    pub package_id: Option<Uuid>,
56    pub owner_id: Option<Uuid>,
57    pub quantity: Decimal,
58    pub reserved_quantity: Decimal,
59    pub available_quantity: Decimal,
60    pub in_date: Option<DateTime<Utc>>,
61    pub inventory_quantity: Option<Decimal>,
62    pub inventory_diff_quantity: Option<Decimal>,
63    pub inventory_quantity_set: bool,
64    pub inventory_date: Option<NaiveDate>,
65    pub sn_duplicated: bool,
66    #[serde(default)]
67    #[sqlx(json)]
68    pub metadata: AuditMetadata,
69}
70
71impl Quant {
72    /// Create a builder for Quant
73    pub fn builder() -> QuantBuilder {
74        <QuantBuilder as Default>::default()
75    }
76
77    /// Create a new Quant with required fields
78    pub fn new(item_id: Uuid, location_id: Uuid, quantity: Decimal, reserved_quantity: Decimal, available_quantity: Decimal, inventory_quantity_set: bool, sn_duplicated: bool) -> Self {
79        Self {
80            id: Uuid::new_v4(),
81            item_id,
82            location_id,
83            lot_id: None,
84            package_id: None,
85            owner_id: None,
86            quantity,
87            reserved_quantity,
88            available_quantity,
89            in_date: None,
90            inventory_quantity: None,
91            inventory_diff_quantity: None,
92            inventory_quantity_set,
93            inventory_date: None,
94            sn_duplicated,
95            metadata: AuditMetadata::default(),
96        }
97    }
98
99    /// Get the entity's unique identifier
100    pub fn id(&self) -> &Uuid {
101        &self.id
102    }
103
104    /// Get a strongly-typed ID for this entity
105    pub fn typed_id(&self) -> QuantId {
106        QuantId(self.id)
107    }
108
109    /// Get when this entity was created
110    pub fn created_at(&self) -> Option<&DateTime<Utc>> {
111        self.metadata.created_at.as_ref()
112    }
113
114    /// Get when this entity was last updated
115    pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
116        self.metadata.updated_at.as_ref()
117    }
118
119    /// Check if this entity is soft deleted
120    pub fn is_deleted(&self) -> bool {
121        self.metadata.deleted_at.is_some()
122    }
123
124    /// Check if this entity is active (not deleted)
125    pub fn is_active(&self) -> bool {
126        self.metadata.deleted_at.is_none()
127    }
128
129    /// Get when this entity was deleted
130    pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
131        self.metadata.deleted_at.as_ref()
132    }
133
134    /// Get who created this entity
135    pub fn created_by(&self) -> Option<&Uuid> {
136        self.metadata.created_by.as_ref()
137    }
138
139    /// Get who last updated this entity
140    pub fn updated_by(&self) -> Option<&Uuid> {
141        self.metadata.updated_by.as_ref()
142    }
143
144    /// Get who deleted this entity
145    pub fn deleted_by(&self) -> Option<&Uuid> {
146        self.metadata.deleted_by.as_ref()
147    }
148
149
150    // ==========================================================
151    // Fluent Setters (with_* for optional fields)
152    // ==========================================================
153
154    /// Set the lot_id field (chainable)
155    pub fn with_lot_id(mut self, value: Uuid) -> Self {
156        self.lot_id = Some(value);
157        self
158    }
159
160    /// Set the package_id field (chainable)
161    pub fn with_package_id(mut self, value: Uuid) -> Self {
162        self.package_id = Some(value);
163        self
164    }
165
166    /// Set the owner_id field (chainable)
167    pub fn with_owner_id(mut self, value: Uuid) -> Self {
168        self.owner_id = Some(value);
169        self
170    }
171
172    /// Set the in_date field (chainable)
173    pub fn with_in_date(mut self, value: DateTime<Utc>) -> Self {
174        self.in_date = Some(value);
175        self
176    }
177
178    /// Set the inventory_quantity field (chainable)
179    pub fn with_inventory_quantity(mut self, value: Decimal) -> Self {
180        self.inventory_quantity = Some(value);
181        self
182    }
183
184    /// Set the inventory_diff_quantity field (chainable)
185    pub fn with_inventory_diff_quantity(mut self, value: Decimal) -> Self {
186        self.inventory_diff_quantity = Some(value);
187        self
188    }
189
190    /// Set the inventory_date field (chainable)
191    pub fn with_inventory_date(mut self, value: NaiveDate) -> Self {
192        self.inventory_date = Some(value);
193        self
194    }
195
196    // ==========================================================
197    // Partial Update
198    // ==========================================================
199
200    /// Apply partial updates from a map of field name to JSON value
201    pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
202        for (key, value) in fields {
203            match key.as_str() {
204                "item_id" => {
205                    if let Ok(v) = serde_json::from_value(value) { self.item_id = v; }
206                }
207                "location_id" => {
208                    if let Ok(v) = serde_json::from_value(value) { self.location_id = v; }
209                }
210                "lot_id" => {
211                    if let Ok(v) = serde_json::from_value(value) { self.lot_id = v; }
212                }
213                "package_id" => {
214                    if let Ok(v) = serde_json::from_value(value) { self.package_id = v; }
215                }
216                "owner_id" => {
217                    if let Ok(v) = serde_json::from_value(value) { self.owner_id = v; }
218                }
219                "quantity" => {
220                    if let Ok(v) = serde_json::from_value(value) { self.quantity = v; }
221                }
222                "reserved_quantity" => {
223                    if let Ok(v) = serde_json::from_value(value) { self.reserved_quantity = v; }
224                }
225                "available_quantity" => {
226                    if let Ok(v) = serde_json::from_value(value) { self.available_quantity = v; }
227                }
228                "in_date" => {
229                    if let Ok(v) = serde_json::from_value(value) { self.in_date = v; }
230                }
231                "inventory_quantity" => {
232                    if let Ok(v) = serde_json::from_value(value) { self.inventory_quantity = v; }
233                }
234                "inventory_diff_quantity" => {
235                    if let Ok(v) = serde_json::from_value(value) { self.inventory_diff_quantity = v; }
236                }
237                "inventory_quantity_set" => {
238                    if let Ok(v) = serde_json::from_value(value) { self.inventory_quantity_set = v; }
239                }
240                "inventory_date" => {
241                    if let Ok(v) = serde_json::from_value(value) { self.inventory_date = v; }
242                }
243                "sn_duplicated" => {
244                    if let Ok(v) = serde_json::from_value(value) { self.sn_duplicated = v; }
245                }
246                _ => {} // ignore unknown fields
247            }
248        }
249    }
250
251}
252
253impl super::Entity for Quant {
254    type Id = Uuid;
255
256    fn entity_id(&self) -> &Self::Id {
257        &self.id
258    }
259
260    fn entity_type() -> &'static str {
261        "Quant"
262    }
263}
264
265impl backbone_core::PersistentEntity for Quant {
266    fn references() -> &'static [backbone_core::Reference] {
267        const REFERENCES: &[backbone_core::Reference] = &[
268            backbone_core::Reference::new("location_id", "inventory.locations", "id"),
269            backbone_core::Reference::new("lot_id", "inventory.lots", "id"),
270            backbone_core::Reference::new("package_id", "inventory.packages", "id"),
271        ];
272        REFERENCES
273    }
274    fn entity_id(&self) -> String {
275        self.id.to_string()
276    }
277    fn set_entity_id(&mut self, id: String) {
278        if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
279            self.id = uuid;
280        }
281    }
282    fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
283        self.metadata.created_at
284    }
285    fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
286        self.metadata.created_at = Some(ts);
287    }
288    fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
289        self.metadata.updated_at
290    }
291    fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
292        self.metadata.updated_at = Some(ts);
293    }
294    fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
295        self.metadata.deleted_at
296    }
297    fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
298        self.metadata.deleted_at = ts;
299    }
300}
301
302impl backbone_orm::EntityRepoMeta for Quant {
303    fn column_types() -> std::collections::HashMap<String, String> {
304        let mut m = std::collections::HashMap::new();
305        m.insert("id".to_string(), "uuid".to_string());
306        m.insert("item_id".to_string(), "uuid".to_string());
307        m.insert("location_id".to_string(), "uuid".to_string());
308        m.insert("lot_id".to_string(), "uuid".to_string());
309        m.insert("package_id".to_string(), "uuid".to_string());
310        m.insert("owner_id".to_string(), "uuid".to_string());
311        m.insert("in_date".to_string(), "timestamptz".to_string());
312        m.insert("inventory_date".to_string(), "date".to_string());
313        m
314    }
315    fn search_fields() -> &'static [&'static str] {
316        &[]
317    }
318    fn relations() -> &'static [(&'static str, &'static str, &'static str)] {
319        &[("location", "locations", "locationId"), ("lot", "lots", "lotId"), ("package", "packages", "packageId")]
320    }
321}
322
323/// Builder for Quant entity
324///
325/// Provides a fluent API for constructing Quant instances.
326/// System fields (id, metadata, timestamps) are auto-initialized.
327#[derive(Debug, Clone, Default)]
328pub struct QuantBuilder {
329    item_id: Option<Uuid>,
330    location_id: Option<Uuid>,
331    lot_id: Option<Uuid>,
332    package_id: Option<Uuid>,
333    owner_id: Option<Uuid>,
334    quantity: Option<Decimal>,
335    reserved_quantity: Option<Decimal>,
336    available_quantity: Option<Decimal>,
337    in_date: Option<DateTime<Utc>>,
338    inventory_quantity: Option<Decimal>,
339    inventory_diff_quantity: Option<Decimal>,
340    inventory_quantity_set: Option<bool>,
341    inventory_date: Option<NaiveDate>,
342    sn_duplicated: Option<bool>,
343}
344
345impl QuantBuilder {
346    /// Set the item_id field (required)
347    pub fn item_id(mut self, value: Uuid) -> Self {
348        self.item_id = Some(value);
349        self
350    }
351
352    /// Set the location_id field (required)
353    pub fn location_id(mut self, value: Uuid) -> Self {
354        self.location_id = Some(value);
355        self
356    }
357
358    /// Set the lot_id field (optional)
359    pub fn lot_id(mut self, value: Uuid) -> Self {
360        self.lot_id = Some(value);
361        self
362    }
363
364    /// Set the package_id field (optional)
365    pub fn package_id(mut self, value: Uuid) -> Self {
366        self.package_id = Some(value);
367        self
368    }
369
370    /// Set the owner_id field (optional)
371    pub fn owner_id(mut self, value: Uuid) -> Self {
372        self.owner_id = Some(value);
373        self
374    }
375
376    /// Set the quantity field (default: `Decimal::from(0)`)
377    pub fn quantity(mut self, value: Decimal) -> Self {
378        self.quantity = Some(value);
379        self
380    }
381
382    /// Set the reserved_quantity field (default: `Decimal::from(0)`)
383    pub fn reserved_quantity(mut self, value: Decimal) -> Self {
384        self.reserved_quantity = Some(value);
385        self
386    }
387
388    /// Set the available_quantity field (default: `Decimal::from(0)`)
389    pub fn available_quantity(mut self, value: Decimal) -> Self {
390        self.available_quantity = Some(value);
391        self
392    }
393
394    /// Set the in_date field (optional)
395    pub fn in_date(mut self, value: DateTime<Utc>) -> Self {
396        self.in_date = Some(value);
397        self
398    }
399
400    /// Set the inventory_quantity field (optional)
401    pub fn inventory_quantity(mut self, value: Decimal) -> Self {
402        self.inventory_quantity = Some(value);
403        self
404    }
405
406    /// Set the inventory_diff_quantity field (optional)
407    pub fn inventory_diff_quantity(mut self, value: Decimal) -> Self {
408        self.inventory_diff_quantity = Some(value);
409        self
410    }
411
412    /// Set the inventory_quantity_set field (default: `false`)
413    pub fn inventory_quantity_set(mut self, value: bool) -> Self {
414        self.inventory_quantity_set = Some(value);
415        self
416    }
417
418    /// Set the inventory_date field (optional)
419    pub fn inventory_date(mut self, value: NaiveDate) -> Self {
420        self.inventory_date = Some(value);
421        self
422    }
423
424    /// Set the sn_duplicated field (default: `false`)
425    pub fn sn_duplicated(mut self, value: bool) -> Self {
426        self.sn_duplicated = Some(value);
427        self
428    }
429
430    /// Build the Quant entity
431    ///
432    /// Returns Err if any required field without a default is missing.
433    pub fn build(self) -> Result<Quant, String> {
434        let item_id = self.item_id.ok_or_else(|| "item_id is required".to_string())?;
435        let location_id = self.location_id.ok_or_else(|| "location_id is required".to_string())?;
436
437        Ok(Quant {
438            id: Uuid::new_v4(),
439            item_id,
440            location_id,
441            lot_id: self.lot_id,
442            package_id: self.package_id,
443            owner_id: self.owner_id,
444            quantity: self.quantity.unwrap_or(Decimal::from(0)),
445            reserved_quantity: self.reserved_quantity.unwrap_or(Decimal::from(0)),
446            available_quantity: self.available_quantity.unwrap_or(Decimal::from(0)),
447            in_date: self.in_date,
448            inventory_quantity: self.inventory_quantity,
449            inventory_diff_quantity: self.inventory_diff_quantity,
450            inventory_quantity_set: self.inventory_quantity_set.unwrap_or(false),
451            inventory_date: self.inventory_date,
452            sn_duplicated: self.sn_duplicated.unwrap_or(false),
453            metadata: AuditMetadata::default(),
454        })
455    }
456}