1use 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#[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#[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#[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 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#[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#[derive(Debug, Clone, Serialize)]
248#[cfg_attr(feature = "openapi", derive(ToSchema))]
249#[serde(rename_all = "camelCase")]
250pub struct TransferListResponseDto {
251 pub items: Vec<TransferResponseDto>,
253 pub total: u64,
255 pub page: u32,
257 pub per_page: u32,
259 pub total_pages: u32,
261 pub has_next: bool,
263 pub has_prev: bool,
265}
266
267impl TransferListResponseDto {
268 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#[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
299impl 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