Skip to main content

backbone_inventory/presentation/dto/
reordering_rule_dto.rs

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