Skip to main content

backbone_inventory/presentation/dto/
scrap_dto.rs

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