1use 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#[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#[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#[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 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#[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#[derive(Debug, Clone, Serialize)]
209#[cfg_attr(feature = "openapi", derive(ToSchema))]
210#[serde(rename_all = "camelCase")]
211pub struct ScrapListResponseDto {
212 pub items: Vec<ScrapResponseDto>,
214 pub total: u64,
216 pub page: u32,
218 pub per_page: u32,
220 pub total_pages: u32,
222 pub has_next: bool,
224 pub has_prev: bool,
226}
227
228impl ScrapListResponseDto {
229 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#[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
260impl 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