Skip to main content

backbone_inventory/presentation/dto/
transfer_dto.rs

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