1use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10use chrono::{DateTime, Utc};
11
12#[cfg(feature = "openapi")]
13#[cfg(feature = "openapi")]
14use utoipa::ToSchema;
15
16#[cfg(feature = "validation")]
17use validator::Validate;
18
19use crate::domain::entity::Transfer;
20use crate::domain::entity::AuditMetadata;
21use crate::domain::entity::MoveType;
22use crate::domain::entity::Priority;
23use crate::domain::entity::TransferState;
24
25#[derive(Debug, Clone, Deserialize)]
34#[cfg_attr(feature = "openapi", derive(ToSchema))]
35#[cfg_attr(feature = "validation", derive(Validate))]
36#[serde(rename_all = "camelCase")]
37pub struct CreateTransferDto {
38 #[cfg_attr(feature = "validation", validate(length(max = 40)))]
39 #[cfg_attr(feature = "openapi", schema(example = "example"))]
40 pub name: String,
41 #[cfg_attr(feature = "validation", validate(length(max = 200)))]
42 #[serde(default, skip_serializing_if = "Option::is_none")]
43 pub origin: Option<String>,
44 #[cfg_attr(feature = "validation", validate(length(max = 2000)))]
45 #[serde(default, skip_serializing_if = "Option::is_none")]
46 pub note: Option<String>,
47 pub priority: Priority,
48 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
49 #[serde(alias = "picking_type_id")]
50 pub picking_type_id: Uuid,
51 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
52 #[serde(alias = "location_id")]
53 pub location_id: Uuid,
54 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
55 #[serde(alias = "location_dest_id")]
56 pub location_dest_id: Uuid,
57 #[serde(default, skip_serializing_if = "Option::is_none", alias = "partner_id")]
58 pub partner_id: Option<Uuid>,
59 #[serde(alias = "move_type")]
60 pub move_type: MoveType,
61 #[cfg_attr(feature = "openapi", schema(example = "2024-01-01T00:00:00Z"))]
62 #[serde(alias = "scheduled_date")]
63 pub scheduled_date: DateTime<Utc>,
64 #[serde(default, skip_serializing_if = "Option::is_none", alias = "date_done")]
65 pub date_done: Option<DateTime<Utc>>,
66 pub state: TransferState,
67 #[cfg_attr(feature = "openapi", schema(example = true))]
68 #[serde(alias = "is_locked")]
69 pub is_locked: bool,
70 #[serde(default, skip_serializing_if = "Option::is_none", alias = "backorder_id")]
71 pub backorder_id: Option<Uuid>,
72 #[serde(default, skip_serializing_if = "Option::is_none", alias = "return_id")]
73 pub return_id: Option<Uuid>,
74 #[serde(default, skip_serializing_if = "Option::is_none", alias = "batch_id")]
75 pub batch_id: Option<Uuid>,
76}
77
78#[derive(Debug, Clone, Deserialize)]
87#[cfg_attr(feature = "openapi", derive(ToSchema))]
88#[cfg_attr(feature = "validation", derive(Validate))]
89#[serde(rename_all = "camelCase")]
90pub struct UpdateTransferDto {
91 #[cfg_attr(feature = "validation", validate(length(max = 40)))]
92 #[cfg_attr(feature = "openapi", schema(example = "example"))]
93 pub name: String,
94 #[cfg_attr(feature = "validation", validate(length(max = 200)))]
95 #[serde(default, skip_serializing_if = "Option::is_none")]
96 pub origin: Option<String>,
97 #[cfg_attr(feature = "validation", validate(length(max = 2000)))]
98 #[serde(default, skip_serializing_if = "Option::is_none")]
99 pub note: Option<String>,
100 pub priority: Priority,
101 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
102 #[serde(alias = "picking_type_id")]
103 pub picking_type_id: Uuid,
104 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
105 #[serde(alias = "location_id")]
106 pub location_id: Uuid,
107 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
108 #[serde(alias = "location_dest_id")]
109 pub location_dest_id: Uuid,
110 #[serde(default, skip_serializing_if = "Option::is_none", alias = "partner_id")]
111 pub partner_id: Option<Uuid>,
112 #[serde(alias = "move_type")]
113 pub move_type: MoveType,
114 #[cfg_attr(feature = "openapi", schema(example = "2024-01-01T00:00:00Z"))]
115 #[serde(alias = "scheduled_date")]
116 pub scheduled_date: DateTime<Utc>,
117 #[serde(default, skip_serializing_if = "Option::is_none", alias = "date_done")]
118 pub date_done: Option<DateTime<Utc>>,
119 pub state: TransferState,
120 #[cfg_attr(feature = "openapi", schema(example = true))]
121 #[serde(alias = "is_locked")]
122 pub is_locked: bool,
123 #[serde(default, skip_serializing_if = "Option::is_none", alias = "backorder_id")]
124 pub backorder_id: Option<Uuid>,
125 #[serde(default, skip_serializing_if = "Option::is_none", alias = "return_id")]
126 pub return_id: Option<Uuid>,
127 #[serde(default, skip_serializing_if = "Option::is_none", alias = "batch_id")]
128 pub batch_id: Option<Uuid>,
129}
130
131#[derive(Debug, Clone, Default, Deserialize)]
140#[cfg_attr(feature = "openapi", derive(ToSchema))]
141#[cfg_attr(feature = "validation", derive(Validate))]
142#[serde(rename_all = "camelCase")]
143pub struct PatchTransferDto {
144 #[cfg_attr(feature = "validation", validate(length(max = 40)))]
145 #[cfg_attr(feature = "openapi", schema(example = "example"))]
146 #[serde(skip_serializing_if = "Option::is_none")]
147 pub name: Option<String>,
148 #[cfg_attr(feature = "validation", validate(length(max = 200)))]
149 #[serde(skip_serializing_if = "Option::is_none")]
150 pub origin: Option<String>,
151 #[cfg_attr(feature = "validation", validate(length(max = 2000)))]
152 #[serde(skip_serializing_if = "Option::is_none")]
153 pub note: Option<String>,
154 #[serde(skip_serializing_if = "Option::is_none")]
155 pub priority: Option<Priority>,
156 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
157 #[serde(skip_serializing_if = "Option::is_none", alias = "picking_type_id")]
158 pub picking_type_id: Option<Uuid>,
159 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
160 #[serde(skip_serializing_if = "Option::is_none", alias = "location_id")]
161 pub location_id: Option<Uuid>,
162 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
163 #[serde(skip_serializing_if = "Option::is_none", alias = "location_dest_id")]
164 pub location_dest_id: Option<Uuid>,
165 #[serde(skip_serializing_if = "Option::is_none", alias = "partner_id")]
166 pub partner_id: Option<Uuid>,
167 #[serde(skip_serializing_if = "Option::is_none", alias = "move_type")]
168 pub move_type: Option<MoveType>,
169 #[cfg_attr(feature = "openapi", schema(example = "2024-01-01T00:00:00Z"))]
170 #[serde(skip_serializing_if = "Option::is_none", alias = "scheduled_date")]
171 pub scheduled_date: Option<DateTime<Utc>>,
172 #[serde(skip_serializing_if = "Option::is_none", alias = "date_done")]
173 pub date_done: Option<DateTime<Utc>>,
174 #[serde(skip_serializing_if = "Option::is_none")]
175 pub state: Option<TransferState>,
176 #[cfg_attr(feature = "openapi", schema(example = true))]
177 #[serde(skip_serializing_if = "Option::is_none", alias = "is_locked")]
178 pub is_locked: Option<bool>,
179 #[serde(skip_serializing_if = "Option::is_none", alias = "backorder_id")]
180 pub backorder_id: Option<Uuid>,
181 #[serde(skip_serializing_if = "Option::is_none", alias = "return_id")]
182 pub return_id: Option<Uuid>,
183 #[serde(skip_serializing_if = "Option::is_none", alias = "batch_id")]
184 pub batch_id: Option<Uuid>,
185}
186
187impl PatchTransferDto {
188 pub fn has_changes(&self) -> bool {
190 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()
191 }
192}
193
194#[derive(Debug, Clone, Serialize)]
203#[cfg_attr(feature = "openapi", derive(ToSchema))]
204#[serde(rename_all = "camelCase")]
205pub struct TransferResponseDto {
206 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
207 pub id: Uuid,
208 #[cfg_attr(feature = "openapi", schema(example = "example"))]
209 pub name: String,
210 pub origin: Option<String>,
211 pub note: Option<String>,
212 pub priority: Priority,
213 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
214 pub picking_type_id: Uuid,
215 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
216 pub location_id: Uuid,
217 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
218 pub location_dest_id: Uuid,
219 pub partner_id: Option<Uuid>,
220 pub move_type: MoveType,
221 #[cfg_attr(feature = "openapi", schema(example = "2024-01-01T00:00:00Z"))]
222 pub scheduled_date: DateTime<Utc>,
223 pub date_done: Option<DateTime<Utc>>,
224 pub state: TransferState,
225 #[cfg_attr(feature = "openapi", schema(example = true))]
226 pub is_locked: bool,
227 pub backorder_id: Option<Uuid>,
228 pub return_id: Option<Uuid>,
229 pub batch_id: Option<Uuid>,
230 pub metadata: AuditMetadata,
231}
232
233#[derive(Debug, Clone, Serialize)]
242#[cfg_attr(feature = "openapi", derive(ToSchema))]
243#[serde(rename_all = "camelCase")]
244pub struct TransferListResponseDto {
245 pub items: Vec<TransferResponseDto>,
247 pub total: u64,
249 pub page: u32,
251 pub per_page: u32,
253 pub total_pages: u32,
255 pub has_next: bool,
257 pub has_prev: bool,
259}
260
261impl TransferListResponseDto {
262 pub fn new(items: Vec<TransferResponseDto>, total: u64, page: u32, per_page: u32) -> Self {
264 let total_pages = if per_page > 0 {
265 ((total as f64) / (per_page as f64)).ceil() as u32
266 } else {
267 0
268 };
269 Self {
270 items,
271 total,
272 page,
273 per_page,
274 total_pages,
275 has_next: page < total_pages,
276 has_prev: page > 1,
277 }
278 }
279}
280
281#[derive(Debug, Clone, Serialize)]
283#[cfg_attr(feature = "openapi", derive(ToSchema))]
284#[serde(rename_all = "camelCase")]
285pub struct TransferSummaryDto {
286 pub id: Uuid,
287 pub name: String,
288 pub origin: Option<String>,
289 pub note: Option<String>,
290 pub created_at: Option<DateTime<Utc>>,
291}
292
293impl From<Transfer> for TransferResponseDto {
298 fn from(entity: Transfer) -> Self {
299 Self {
300 id: entity.id,
301 name: entity.name,
302 origin: entity.origin,
303 note: entity.note,
304 priority: entity.priority,
305 picking_type_id: entity.picking_type_id,
306 location_id: entity.location_id,
307 location_dest_id: entity.location_dest_id,
308 partner_id: entity.partner_id,
309 move_type: entity.move_type,
310 scheduled_date: entity.scheduled_date,
311 date_done: entity.date_done,
312 state: entity.state,
313 is_locked: entity.is_locked,
314 backorder_id: entity.backorder_id,
315 return_id: entity.return_id,
316 batch_id: entity.batch_id,
317 metadata: entity.metadata,
318 }
319 }
320}
321
322impl From<Transfer> for TransferSummaryDto {
323 fn from(entity: Transfer) -> Self {
324 let created_at = backbone_core::PersistentEntity::created_at(&entity);
325 Self {
326 id: entity.id,
327 name: entity.name,
328 origin: entity.origin,
329 note: entity.note,
330 created_at,
331 }
332 }
333}
334
335impl From<CreateTransferDto> for Transfer {
336 fn from(dto: CreateTransferDto) -> Self {
337 Self {
338 id: Uuid::new_v4(),
339 name: dto.name,
340 origin: dto.origin,
341 note: dto.note,
342 priority: dto.priority,
343 picking_type_id: dto.picking_type_id,
344 location_id: dto.location_id,
345 location_dest_id: dto.location_dest_id,
346 partner_id: dto.partner_id,
347 move_type: dto.move_type,
348 scheduled_date: dto.scheduled_date,
349 date_done: dto.date_done,
350 state: dto.state,
351 is_locked: dto.is_locked,
352 backorder_id: dto.backorder_id,
353 return_id: dto.return_id,
354 batch_id: dto.batch_id,
355 metadata: AuditMetadata::default(),
356 }
357 }
358}
359
360impl From<&Transfer> for TransferResponseDto {
361 fn from(entity: &Transfer) -> Self {
362 Self {
363 id: entity.id.clone(),
364 name: entity.name.clone(),
365 origin: entity.origin.clone(),
366 note: entity.note.clone(),
367 priority: entity.priority.clone(),
368 picking_type_id: entity.picking_type_id.clone(),
369 location_id: entity.location_id.clone(),
370 location_dest_id: entity.location_dest_id.clone(),
371 partner_id: entity.partner_id.clone(),
372 move_type: entity.move_type.clone(),
373 scheduled_date: entity.scheduled_date.clone(),
374 date_done: entity.date_done.clone(),
375 state: entity.state.clone(),
376 is_locked: entity.is_locked.clone(),
377 backorder_id: entity.backorder_id.clone(),
378 return_id: entity.return_id.clone(),
379 batch_id: entity.batch_id.clone(),
380 metadata: entity.metadata.clone(),
381 }
382 }
383}
384
385impl backbone_core::FromCreateDto<CreateTransferDto> for Transfer {
386 fn from_create_dto(dto: CreateTransferDto) -> backbone_core::ServiceResult<Self> {
387 Ok(Transfer::from(dto))
388 }
389}
390
391impl backbone_core::ApplyUpdateDto<UpdateTransferDto> for Transfer {
392 fn apply_update(mut self, dto: UpdateTransferDto) -> backbone_core::ServiceResult<Self> {
393 self.name = dto.name;
394 self.origin = dto.origin;
395 self.note = dto.note;
396 self.priority = dto.priority;
397 self.picking_type_id = dto.picking_type_id;
398 self.location_id = dto.location_id;
399 self.location_dest_id = dto.location_dest_id;
400 self.partner_id = dto.partner_id;
401 self.move_type = dto.move_type;
402 self.scheduled_date = dto.scheduled_date;
403 self.date_done = dto.date_done;
404 self.state = dto.state;
405 self.is_locked = dto.is_locked;
406 self.backorder_id = dto.backorder_id;
407 self.return_id = dto.return_id;
408 self.batch_id = dto.batch_id;
409 Ok(self)
410 }
411}
412
413