Skip to main content

backbone_inventory/presentation/dto/
quant_dto.rs

1//! Quant DTOs (Data Transfer Objects)
2//!
3//! Generated by metaphor-schema. Do not edit manually.
4//!
5//! DTOs provide a clean separation between domain entities and API
6//! representations, with OpenAPI documentation support.
7
8use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10use chrono::{DateTime, Utc, NaiveDate};
11use rust_decimal::Decimal;
12
13#[cfg(feature = "openapi")]
14#[cfg(feature = "openapi")]
15use utoipa::ToSchema;
16
17use crate::domain::entity::Quant;
18use crate::domain::entity::AuditMetadata;
19
20// =============================================================================
21// Create DTO
22// =============================================================================
23
24/// Request DTO for creating a new Quant
25///
26/// Used in POST requests to create new entities.
27/// Excludes auto-generated fields (id, created_at, updated_at, deleted_at).
28#[derive(Debug, Clone, Deserialize)]
29#[cfg_attr(feature = "openapi", derive(ToSchema))]
30#[serde(rename_all = "camelCase")]
31pub struct CreateQuantDto {
32    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
33    #[serde(alias = "item_id")]
34    pub item_id: Uuid,
35    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
36    #[serde(alias = "location_id")]
37    pub location_id: Uuid,
38    #[serde(default, skip_serializing_if = "Option::is_none", alias = "lot_id")]
39    pub lot_id: Option<Uuid>,
40    #[serde(default, skip_serializing_if = "Option::is_none", alias = "package_id")]
41    pub package_id: Option<Uuid>,
42    #[serde(default, skip_serializing_if = "Option::is_none", alias = "owner_id")]
43    pub owner_id: Option<Uuid>,
44    #[serde(default)]
45    pub quantity: Decimal,
46    #[serde(default, alias = "reserved_quantity")]
47    pub reserved_quantity: Decimal,
48    #[serde(default, alias = "available_quantity")]
49    pub available_quantity: Decimal,
50    #[serde(default, skip_serializing_if = "Option::is_none", alias = "in_date")]
51    pub in_date: Option<DateTime<Utc>>,
52    #[serde(default, skip_serializing_if = "Option::is_none", alias = "inventory_quantity")]
53    pub inventory_quantity: Option<Decimal>,
54    #[serde(default, skip_serializing_if = "Option::is_none", alias = "inventory_diff_quantity")]
55    pub inventory_diff_quantity: Option<Decimal>,
56    #[cfg_attr(feature = "openapi", schema(example = true))]
57    #[serde(default, alias = "inventory_quantity_set")]
58    pub inventory_quantity_set: bool,
59    #[serde(default, skip_serializing_if = "Option::is_none", alias = "inventory_date")]
60    pub inventory_date: Option<NaiveDate>,
61    #[cfg_attr(feature = "openapi", schema(example = true))]
62    #[serde(default, alias = "sn_duplicated")]
63    pub sn_duplicated: bool,
64}
65
66// =============================================================================
67// Update DTO
68// =============================================================================
69
70/// Request DTO for full update of a Quant
71///
72/// Used in PUT requests for full entity replacement.
73/// All fields are required (except auto-generated ones).
74#[derive(Debug, Clone, Deserialize)]
75#[cfg_attr(feature = "openapi", derive(ToSchema))]
76#[serde(rename_all = "camelCase")]
77pub struct UpdateQuantDto {
78    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
79    #[serde(alias = "item_id")]
80    pub item_id: Uuid,
81    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
82    #[serde(alias = "location_id")]
83    pub location_id: Uuid,
84    #[serde(default, skip_serializing_if = "Option::is_none", alias = "lot_id")]
85    pub lot_id: Option<Uuid>,
86    #[serde(default, skip_serializing_if = "Option::is_none", alias = "package_id")]
87    pub package_id: Option<Uuid>,
88    #[serde(default, skip_serializing_if = "Option::is_none", alias = "owner_id")]
89    pub owner_id: Option<Uuid>,
90    pub quantity: Decimal,
91    #[serde(alias = "reserved_quantity")]
92    pub reserved_quantity: Decimal,
93    #[serde(alias = "available_quantity")]
94    pub available_quantity: Decimal,
95    #[serde(default, skip_serializing_if = "Option::is_none", alias = "in_date")]
96    pub in_date: Option<DateTime<Utc>>,
97    #[serde(default, skip_serializing_if = "Option::is_none", alias = "inventory_quantity")]
98    pub inventory_quantity: Option<Decimal>,
99    #[serde(default, skip_serializing_if = "Option::is_none", alias = "inventory_diff_quantity")]
100    pub inventory_diff_quantity: Option<Decimal>,
101    #[cfg_attr(feature = "openapi", schema(example = true))]
102    #[serde(alias = "inventory_quantity_set")]
103    pub inventory_quantity_set: bool,
104    #[serde(default, skip_serializing_if = "Option::is_none", alias = "inventory_date")]
105    pub inventory_date: Option<NaiveDate>,
106    #[cfg_attr(feature = "openapi", schema(example = true))]
107    #[serde(alias = "sn_duplicated")]
108    pub sn_duplicated: bool,
109}
110
111// =============================================================================
112// Patch DTO
113// =============================================================================
114
115/// Request DTO for partial update of a Quant
116///
117/// Used in PATCH requests for partial updates.
118/// All fields are optional - only provided fields will be updated.
119#[derive(Debug, Clone, Default, Deserialize)]
120#[cfg_attr(feature = "openapi", derive(ToSchema))]
121#[serde(rename_all = "camelCase")]
122pub struct PatchQuantDto {
123    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
124    #[serde(skip_serializing_if = "Option::is_none", alias = "item_id")]
125    pub item_id: Option<Uuid>,
126    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
127    #[serde(skip_serializing_if = "Option::is_none", alias = "location_id")]
128    pub location_id: Option<Uuid>,
129    #[serde(default, deserialize_with = "crate::presentation::dto::double_option", alias = "lot_id")]
130    pub lot_id: Option<Option<Uuid>>,
131    #[serde(default, deserialize_with = "crate::presentation::dto::double_option", alias = "package_id")]
132    pub package_id: Option<Option<Uuid>>,
133    #[serde(default, deserialize_with = "crate::presentation::dto::double_option", alias = "owner_id")]
134    pub owner_id: Option<Option<Uuid>>,
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub quantity: Option<Decimal>,
137    #[serde(skip_serializing_if = "Option::is_none", alias = "reserved_quantity")]
138    pub reserved_quantity: Option<Decimal>,
139    #[serde(skip_serializing_if = "Option::is_none", alias = "available_quantity")]
140    pub available_quantity: Option<Decimal>,
141    #[serde(default, deserialize_with = "crate::presentation::dto::double_option", alias = "in_date")]
142    pub in_date: Option<Option<DateTime<Utc>>>,
143    #[serde(default, deserialize_with = "crate::presentation::dto::double_option", alias = "inventory_quantity")]
144    pub inventory_quantity: Option<Option<Decimal>>,
145    #[serde(default, deserialize_with = "crate::presentation::dto::double_option", alias = "inventory_diff_quantity")]
146    pub inventory_diff_quantity: Option<Option<Decimal>>,
147    #[cfg_attr(feature = "openapi", schema(example = true))]
148    #[serde(skip_serializing_if = "Option::is_none", alias = "inventory_quantity_set")]
149    pub inventory_quantity_set: Option<bool>,
150    #[serde(default, deserialize_with = "crate::presentation::dto::double_option", alias = "inventory_date")]
151    pub inventory_date: Option<Option<NaiveDate>>,
152    #[cfg_attr(feature = "openapi", schema(example = true))]
153    #[serde(skip_serializing_if = "Option::is_none", alias = "sn_duplicated")]
154    pub sn_duplicated: Option<bool>,
155}
156
157impl PatchQuantDto {
158    /// Check if any field is set
159    pub fn has_changes(&self) -> bool {
160        self.item_id.is_some() || self.location_id.is_some() || self.lot_id.is_some() || self.package_id.is_some() || self.owner_id.is_some() || self.quantity.is_some() || self.reserved_quantity.is_some() || self.available_quantity.is_some() || self.in_date.is_some() || self.inventory_quantity.is_some() || self.inventory_diff_quantity.is_some() || self.inventory_quantity_set.is_some() || self.inventory_date.is_some() || self.sn_duplicated.is_some()
161    }
162}
163
164// =============================================================================
165// Response DTO
166// =============================================================================
167
168/// Response DTO for Quant entity
169///
170/// Used in API responses for single entity.
171/// Includes all fields including metadata.
172#[derive(Debug, Clone, Serialize)]
173#[cfg_attr(feature = "openapi", derive(ToSchema))]
174#[serde(rename_all = "camelCase")]
175pub struct QuantResponseDto {
176    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
177    pub id: Uuid,
178    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
179    pub item_id: Uuid,
180    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
181    pub location_id: Uuid,
182    pub lot_id: Option<Uuid>,
183    pub package_id: Option<Uuid>,
184    pub owner_id: Option<Uuid>,
185    pub quantity: Decimal,
186    pub reserved_quantity: Decimal,
187    pub available_quantity: Decimal,
188    pub in_date: Option<DateTime<Utc>>,
189    pub inventory_quantity: Option<Decimal>,
190    pub inventory_diff_quantity: Option<Decimal>,
191    #[cfg_attr(feature = "openapi", schema(example = true))]
192    pub inventory_quantity_set: bool,
193    pub inventory_date: Option<NaiveDate>,
194    #[cfg_attr(feature = "openapi", schema(example = true))]
195    pub sn_duplicated: bool,
196    pub metadata: AuditMetadata,
197}
198
199// =============================================================================
200// List Response DTO
201// =============================================================================
202
203/// Paginated list response for Quant entities
204///
205/// Used in API responses for list endpoints.
206/// Includes pagination metadata.
207#[derive(Debug, Clone, Serialize)]
208#[cfg_attr(feature = "openapi", derive(ToSchema))]
209#[serde(rename_all = "camelCase")]
210pub struct QuantListResponseDto {
211    /// List of items
212    pub items: Vec<QuantResponseDto>,
213    /// Total number of items (across all pages)
214    pub total: u64,
215    /// Current page number (1-indexed)
216    pub page: u32,
217    /// Number of items per page
218    pub per_page: u32,
219    /// Total number of pages
220    pub total_pages: u32,
221    /// Whether there is a next page
222    pub has_next: bool,
223    /// Whether there is a previous page
224    pub has_prev: bool,
225}
226
227impl QuantListResponseDto {
228    /// Create a new list response from items and pagination info
229    pub fn new(items: Vec<QuantResponseDto>, total: u64, page: u32, per_page: u32) -> Self {
230        let total_pages = if per_page > 0 {
231            ((total as f64) / (per_page as f64)).ceil() as u32
232        } else {
233            0
234        };
235        Self {
236            items,
237            total,
238            page,
239            per_page,
240            total_pages,
241            has_next: page < total_pages,
242            has_prev: page > 1,
243        }
244    }
245}
246
247/// Summary DTO for list views (compact version)
248#[derive(Debug, Clone, Serialize)]
249#[cfg_attr(feature = "openapi", derive(ToSchema))]
250#[serde(rename_all = "camelCase")]
251pub struct QuantSummaryDto {
252    pub id: Uuid,
253    pub item_id: Uuid,
254    pub location_id: Uuid,
255    pub lot_id: Option<Uuid>,
256    pub created_at: Option<DateTime<Utc>>,
257}
258
259// =============================================================================
260// Conversions
261// =============================================================================
262
263impl From<Quant> for QuantResponseDto {
264    fn from(entity: Quant) -> Self {
265        Self {
266            id: entity.id,
267            item_id: entity.item_id,
268            location_id: entity.location_id,
269            lot_id: entity.lot_id,
270            package_id: entity.package_id,
271            owner_id: entity.owner_id,
272            quantity: entity.quantity,
273            reserved_quantity: entity.reserved_quantity,
274            available_quantity: entity.available_quantity,
275            in_date: entity.in_date,
276            inventory_quantity: entity.inventory_quantity,
277            inventory_diff_quantity: entity.inventory_diff_quantity,
278            inventory_quantity_set: entity.inventory_quantity_set,
279            inventory_date: entity.inventory_date,
280            sn_duplicated: entity.sn_duplicated,
281            metadata: entity.metadata,
282        }
283    }
284}
285
286impl From<Quant> for QuantSummaryDto {
287    fn from(entity: Quant) -> Self {
288        let created_at = backbone_core::PersistentEntity::created_at(&entity);
289        Self {
290            id: entity.id,
291            item_id: entity.item_id,
292            location_id: entity.location_id,
293            lot_id: entity.lot_id,
294            created_at,
295        }
296    }
297}
298
299impl From<CreateQuantDto> for Quant {
300    fn from(dto: CreateQuantDto) -> Self {
301        Self {
302            id: Uuid::new_v4(),
303            item_id: dto.item_id,
304            location_id: dto.location_id,
305            lot_id: dto.lot_id,
306            package_id: dto.package_id,
307            owner_id: dto.owner_id,
308            quantity: dto.quantity,
309            reserved_quantity: dto.reserved_quantity,
310            available_quantity: dto.available_quantity,
311            in_date: dto.in_date,
312            inventory_quantity: dto.inventory_quantity,
313            inventory_diff_quantity: dto.inventory_diff_quantity,
314            inventory_quantity_set: dto.inventory_quantity_set,
315            inventory_date: dto.inventory_date,
316            sn_duplicated: dto.sn_duplicated,
317            metadata: AuditMetadata::default(),
318        }
319    }
320}
321
322impl From<&Quant> for QuantResponseDto {
323    fn from(entity: &Quant) -> Self {
324        Self {
325            id: entity.id.clone(),
326            item_id: entity.item_id.clone(),
327            location_id: entity.location_id.clone(),
328            lot_id: entity.lot_id.clone(),
329            package_id: entity.package_id.clone(),
330            owner_id: entity.owner_id.clone(),
331            quantity: entity.quantity.clone(),
332            reserved_quantity: entity.reserved_quantity.clone(),
333            available_quantity: entity.available_quantity.clone(),
334            in_date: entity.in_date.clone(),
335            inventory_quantity: entity.inventory_quantity.clone(),
336            inventory_diff_quantity: entity.inventory_diff_quantity.clone(),
337            inventory_quantity_set: entity.inventory_quantity_set.clone(),
338            inventory_date: entity.inventory_date.clone(),
339            sn_duplicated: entity.sn_duplicated.clone(),
340            metadata: entity.metadata.clone(),
341        }
342    }
343}
344
345impl backbone_core::FromCreateDto<CreateQuantDto> for Quant {
346    fn from_create_dto(dto: CreateQuantDto) -> backbone_core::ServiceResult<Self> {
347        Ok(Quant::from(dto))
348    }
349}
350
351impl backbone_core::ApplyUpdateDto<UpdateQuantDto> for Quant {
352    fn apply_update(mut self, dto: UpdateQuantDto) -> backbone_core::ServiceResult<Self> {
353        self.item_id = dto.item_id;
354        self.location_id = dto.location_id;
355        self.lot_id = dto.lot_id;
356        self.package_id = dto.package_id;
357        self.owner_id = dto.owner_id;
358        self.quantity = dto.quantity;
359        self.reserved_quantity = dto.reserved_quantity;
360        self.available_quantity = dto.available_quantity;
361        self.in_date = dto.in_date;
362        self.inventory_quantity = dto.inventory_quantity;
363        self.inventory_diff_quantity = dto.inventory_diff_quantity;
364        self.inventory_quantity_set = dto.inventory_quantity_set;
365        self.inventory_date = dto.inventory_date;
366        self.sn_duplicated = dto.sn_duplicated;
367        Ok(self)
368    }
369}
370