1use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10use chrono::{DateTime, Utc, NaiveDate};
11use rust_decimal::Decimal;
12
13#[cfg(feature = "openapi")]
14#[cfg(feature = "openapi")]
15use utoipa::ToSchema;
16
17#[cfg(feature = "validation")]
18use validator::Validate;
19
20use crate::domain::entity::MaintenanceRequest;
21use crate::domain::entity::AuditMetadata;
22use crate::domain::entity::MaintenanceType;
23use crate::domain::entity::RepeatType;
24use crate::domain::entity::RepeatUnit;
25use crate::domain::entity::RequestKanbanState;
26use crate::domain::entity::RequestPriority;
27
28#[derive(Debug, Clone, Deserialize)]
37#[cfg_attr(feature = "openapi", derive(ToSchema))]
38#[cfg_attr(feature = "validation", derive(Validate))]
39#[serde(rename_all = "camelCase")]
40pub struct CreateMaintenanceRequestDto {
41 #[cfg_attr(feature = "openapi", schema(example = "example"))]
42 pub name: String,
43 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub description: Option<String>,
45 #[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
46 #[serde(alias = "request_date")]
47 pub request_date: NaiveDate,
48 #[serde(default, skip_serializing_if = "Option::is_none", alias = "schedule_date")]
49 pub schedule_date: Option<DateTime<Utc>>,
50 #[serde(default, skip_serializing_if = "Option::is_none", alias = "schedule_end")]
51 pub schedule_end: Option<DateTime<Utc>>,
52 #[serde(default, skip_serializing_if = "Option::is_none", alias = "close_date")]
53 pub close_date: Option<NaiveDate>,
54 pub duration: Decimal,
55 #[serde(default, skip_serializing_if = "Option::is_none", alias = "owner_user_id")]
56 pub owner_user_id: Option<Uuid>,
57 #[serde(default, skip_serializing_if = "Option::is_none", alias = "user_id")]
58 pub user_id: Option<Uuid>,
59 #[serde(default, skip_serializing_if = "Option::is_none", alias = "asset_id")]
60 pub asset_id: Option<Uuid>,
61 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
62 #[serde(alias = "stage_id")]
63 pub stage_id: Uuid,
64 #[serde(alias = "kanban_state")]
65 pub kanban_state: RequestKanbanState,
66 pub priority: RequestPriority,
67 #[serde(alias = "maintenance_type")]
68 pub maintenance_type: MaintenanceType,
69 #[cfg_attr(feature = "openapi", schema(example = true))]
70 pub recurring: bool,
71 #[cfg_attr(feature = "openapi", schema(example = 42))]
72 #[serde(alias = "repeat_interval")]
73 pub repeat_interval: i32,
74 #[serde(alias = "repeat_unit")]
75 pub repeat_unit: RepeatUnit,
76 #[serde(alias = "repeat_type")]
77 pub repeat_type: RepeatType,
78 #[serde(default, skip_serializing_if = "Option::is_none", alias = "repeat_until")]
79 pub repeat_until: Option<NaiveDate>,
80 #[serde(default, skip_serializing_if = "Option::is_none", alias = "successor_request_id")]
81 pub successor_request_id: Option<Uuid>,
82 #[serde(default, skip_serializing_if = "Option::is_none", alias = "successor_of_request_id")]
83 pub successor_of_request_id: Option<Uuid>,
84}
85
86#[derive(Debug, Clone, Deserialize)]
95#[cfg_attr(feature = "openapi", derive(ToSchema))]
96#[cfg_attr(feature = "validation", derive(Validate))]
97#[serde(rename_all = "camelCase")]
98pub struct UpdateMaintenanceRequestDto {
99 #[cfg_attr(feature = "openapi", schema(example = "example"))]
100 pub name: String,
101 #[serde(default, skip_serializing_if = "Option::is_none")]
102 pub description: Option<String>,
103 #[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
104 #[serde(alias = "request_date")]
105 pub request_date: NaiveDate,
106 #[serde(default, skip_serializing_if = "Option::is_none", alias = "schedule_date")]
107 pub schedule_date: Option<DateTime<Utc>>,
108 #[serde(default, skip_serializing_if = "Option::is_none", alias = "schedule_end")]
109 pub schedule_end: Option<DateTime<Utc>>,
110 #[serde(default, skip_serializing_if = "Option::is_none", alias = "close_date")]
111 pub close_date: Option<NaiveDate>,
112 pub duration: Decimal,
113 #[serde(default, skip_serializing_if = "Option::is_none", alias = "owner_user_id")]
114 pub owner_user_id: Option<Uuid>,
115 #[serde(default, skip_serializing_if = "Option::is_none", alias = "user_id")]
116 pub user_id: Option<Uuid>,
117 #[serde(default, skip_serializing_if = "Option::is_none", alias = "asset_id")]
118 pub asset_id: Option<Uuid>,
119 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
120 #[serde(alias = "stage_id")]
121 pub stage_id: Uuid,
122 #[serde(alias = "kanban_state")]
123 pub kanban_state: RequestKanbanState,
124 pub priority: RequestPriority,
125 #[serde(alias = "maintenance_type")]
126 pub maintenance_type: MaintenanceType,
127 #[cfg_attr(feature = "openapi", schema(example = true))]
128 pub recurring: bool,
129 #[cfg_attr(feature = "openapi", schema(example = 42))]
130 #[serde(alias = "repeat_interval")]
131 pub repeat_interval: i32,
132 #[serde(alias = "repeat_unit")]
133 pub repeat_unit: RepeatUnit,
134 #[serde(alias = "repeat_type")]
135 pub repeat_type: RepeatType,
136 #[serde(default, skip_serializing_if = "Option::is_none", alias = "repeat_until")]
137 pub repeat_until: Option<NaiveDate>,
138 #[serde(default, skip_serializing_if = "Option::is_none", alias = "successor_request_id")]
139 pub successor_request_id: Option<Uuid>,
140 #[serde(default, skip_serializing_if = "Option::is_none", alias = "successor_of_request_id")]
141 pub successor_of_request_id: Option<Uuid>,
142}
143
144#[derive(Debug, Clone, Default, Deserialize)]
153#[cfg_attr(feature = "openapi", derive(ToSchema))]
154#[cfg_attr(feature = "validation", derive(Validate))]
155#[serde(rename_all = "camelCase")]
156pub struct PatchMaintenanceRequestDto {
157 #[cfg_attr(feature = "openapi", schema(example = "example"))]
158 #[serde(skip_serializing_if = "Option::is_none")]
159 pub name: Option<String>,
160 #[serde(skip_serializing_if = "Option::is_none")]
161 pub description: Option<String>,
162 #[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
163 #[serde(skip_serializing_if = "Option::is_none", alias = "request_date")]
164 pub request_date: Option<NaiveDate>,
165 #[serde(skip_serializing_if = "Option::is_none", alias = "schedule_date")]
166 pub schedule_date: Option<DateTime<Utc>>,
167 #[serde(skip_serializing_if = "Option::is_none", alias = "schedule_end")]
168 pub schedule_end: Option<DateTime<Utc>>,
169 #[serde(skip_serializing_if = "Option::is_none", alias = "close_date")]
170 pub close_date: Option<NaiveDate>,
171 #[serde(skip_serializing_if = "Option::is_none")]
172 pub duration: Option<Decimal>,
173 #[serde(skip_serializing_if = "Option::is_none", alias = "owner_user_id")]
174 pub owner_user_id: Option<Uuid>,
175 #[serde(skip_serializing_if = "Option::is_none", alias = "user_id")]
176 pub user_id: Option<Uuid>,
177 #[serde(skip_serializing_if = "Option::is_none", alias = "asset_id")]
178 pub asset_id: Option<Uuid>,
179 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
180 #[serde(skip_serializing_if = "Option::is_none", alias = "stage_id")]
181 pub stage_id: Option<Uuid>,
182 #[serde(skip_serializing_if = "Option::is_none", alias = "kanban_state")]
183 pub kanban_state: Option<RequestKanbanState>,
184 #[serde(skip_serializing_if = "Option::is_none")]
185 pub priority: Option<RequestPriority>,
186 #[serde(skip_serializing_if = "Option::is_none", alias = "maintenance_type")]
187 pub maintenance_type: Option<MaintenanceType>,
188 #[cfg_attr(feature = "openapi", schema(example = true))]
189 #[serde(skip_serializing_if = "Option::is_none")]
190 pub recurring: Option<bool>,
191 #[cfg_attr(feature = "openapi", schema(example = 42))]
192 #[serde(skip_serializing_if = "Option::is_none", alias = "repeat_interval")]
193 pub repeat_interval: Option<i32>,
194 #[serde(skip_serializing_if = "Option::is_none", alias = "repeat_unit")]
195 pub repeat_unit: Option<RepeatUnit>,
196 #[serde(skip_serializing_if = "Option::is_none", alias = "repeat_type")]
197 pub repeat_type: Option<RepeatType>,
198 #[serde(skip_serializing_if = "Option::is_none", alias = "repeat_until")]
199 pub repeat_until: Option<NaiveDate>,
200 #[serde(skip_serializing_if = "Option::is_none", alias = "successor_request_id")]
201 pub successor_request_id: Option<Uuid>,
202 #[serde(skip_serializing_if = "Option::is_none", alias = "successor_of_request_id")]
203 pub successor_of_request_id: Option<Uuid>,
204}
205
206impl PatchMaintenanceRequestDto {
207 pub fn has_changes(&self) -> bool {
209 self.name.is_some() || self.description.is_some() || self.request_date.is_some() || self.schedule_date.is_some() || self.schedule_end.is_some() || self.close_date.is_some() || self.duration.is_some() || self.owner_user_id.is_some() || self.user_id.is_some() || self.asset_id.is_some() || self.stage_id.is_some() || self.kanban_state.is_some() || self.priority.is_some() || self.maintenance_type.is_some() || self.recurring.is_some() || self.repeat_interval.is_some() || self.repeat_unit.is_some() || self.repeat_type.is_some() || self.repeat_until.is_some() || self.successor_request_id.is_some() || self.successor_of_request_id.is_some()
210 }
211}
212
213#[derive(Debug, Clone, Serialize)]
222#[cfg_attr(feature = "openapi", derive(ToSchema))]
223#[serde(rename_all = "camelCase")]
224pub struct MaintenanceRequestResponseDto {
225 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
226 pub id: Uuid,
227 #[cfg_attr(feature = "openapi", schema(example = "example"))]
228 pub name: String,
229 pub description: Option<String>,
230 #[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
231 pub request_date: NaiveDate,
232 pub schedule_date: Option<DateTime<Utc>>,
233 pub schedule_end: Option<DateTime<Utc>>,
234 pub close_date: Option<NaiveDate>,
235 pub duration: Decimal,
236 pub owner_user_id: Option<Uuid>,
237 pub user_id: Option<Uuid>,
238 pub asset_id: Option<Uuid>,
239 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
240 pub stage_id: Uuid,
241 pub kanban_state: RequestKanbanState,
242 pub priority: RequestPriority,
243 pub maintenance_type: MaintenanceType,
244 #[cfg_attr(feature = "openapi", schema(example = true))]
245 pub recurring: bool,
246 #[cfg_attr(feature = "openapi", schema(example = 42))]
247 pub repeat_interval: i32,
248 pub repeat_unit: RepeatUnit,
249 pub repeat_type: RepeatType,
250 pub repeat_until: Option<NaiveDate>,
251 pub successor_request_id: Option<Uuid>,
252 pub successor_of_request_id: Option<Uuid>,
253 pub metadata: AuditMetadata,
254}
255
256#[derive(Debug, Clone, Serialize)]
265#[cfg_attr(feature = "openapi", derive(ToSchema))]
266#[serde(rename_all = "camelCase")]
267pub struct MaintenanceRequestListResponseDto {
268 pub items: Vec<MaintenanceRequestResponseDto>,
270 pub total: u64,
272 pub page: u32,
274 pub per_page: u32,
276 pub total_pages: u32,
278 pub has_next: bool,
280 pub has_prev: bool,
282}
283
284impl MaintenanceRequestListResponseDto {
285 pub fn new(items: Vec<MaintenanceRequestResponseDto>, total: u64, page: u32, per_page: u32) -> Self {
287 let total_pages = if per_page > 0 {
288 ((total as f64) / (per_page as f64)).ceil() as u32
289 } else {
290 0
291 };
292 Self {
293 items,
294 total,
295 page,
296 per_page,
297 total_pages,
298 has_next: page < total_pages,
299 has_prev: page > 1,
300 }
301 }
302}
303
304#[derive(Debug, Clone, Serialize)]
306#[cfg_attr(feature = "openapi", derive(ToSchema))]
307#[serde(rename_all = "camelCase")]
308pub struct MaintenanceRequestSummaryDto {
309 pub id: Uuid,
310 pub name: String,
311 pub description: Option<String>,
312 pub request_date: NaiveDate,
313 pub created_at: Option<DateTime<Utc>>,
314}
315
316impl From<MaintenanceRequest> for MaintenanceRequestResponseDto {
321 fn from(entity: MaintenanceRequest) -> Self {
322 Self {
323 id: entity.id,
324 name: entity.name,
325 description: entity.description,
326 request_date: entity.request_date,
327 schedule_date: entity.schedule_date,
328 schedule_end: entity.schedule_end,
329 close_date: entity.close_date,
330 duration: entity.duration,
331 owner_user_id: entity.owner_user_id,
332 user_id: entity.user_id,
333 asset_id: entity.asset_id,
334 stage_id: entity.stage_id,
335 kanban_state: entity.kanban_state,
336 priority: entity.priority,
337 maintenance_type: entity.maintenance_type,
338 recurring: entity.recurring,
339 repeat_interval: entity.repeat_interval,
340 repeat_unit: entity.repeat_unit,
341 repeat_type: entity.repeat_type,
342 repeat_until: entity.repeat_until,
343 successor_request_id: entity.successor_request_id,
344 successor_of_request_id: entity.successor_of_request_id,
345 metadata: entity.metadata,
346 }
347 }
348}
349
350impl From<MaintenanceRequest> for MaintenanceRequestSummaryDto {
351 fn from(entity: MaintenanceRequest) -> Self {
352 let created_at = backbone_core::PersistentEntity::created_at(&entity);
353 Self {
354 id: entity.id,
355 name: entity.name,
356 description: entity.description,
357 request_date: entity.request_date,
358 created_at,
359 }
360 }
361}
362
363impl From<CreateMaintenanceRequestDto> for MaintenanceRequest {
364 fn from(dto: CreateMaintenanceRequestDto) -> Self {
365 Self {
366 id: Uuid::new_v4(),
367 name: dto.name,
368 description: dto.description,
369 request_date: dto.request_date,
370 schedule_date: dto.schedule_date,
371 schedule_end: dto.schedule_end,
372 close_date: dto.close_date,
373 duration: dto.duration,
374 owner_user_id: dto.owner_user_id,
375 user_id: dto.user_id,
376 asset_id: dto.asset_id,
377 stage_id: dto.stage_id,
378 kanban_state: dto.kanban_state,
379 priority: dto.priority,
380 maintenance_type: dto.maintenance_type,
381 recurring: dto.recurring,
382 repeat_interval: dto.repeat_interval,
383 repeat_unit: dto.repeat_unit,
384 repeat_type: dto.repeat_type,
385 repeat_until: dto.repeat_until,
386 successor_request_id: dto.successor_request_id,
387 successor_of_request_id: dto.successor_of_request_id,
388 metadata: AuditMetadata::default(),
389 }
390 }
391}
392
393impl From<&MaintenanceRequest> for MaintenanceRequestResponseDto {
394 fn from(entity: &MaintenanceRequest) -> Self {
395 Self {
396 id: entity.id.clone(),
397 name: entity.name.clone(),
398 description: entity.description.clone(),
399 request_date: entity.request_date.clone(),
400 schedule_date: entity.schedule_date.clone(),
401 schedule_end: entity.schedule_end.clone(),
402 close_date: entity.close_date.clone(),
403 duration: entity.duration.clone(),
404 owner_user_id: entity.owner_user_id.clone(),
405 user_id: entity.user_id.clone(),
406 asset_id: entity.asset_id.clone(),
407 stage_id: entity.stage_id.clone(),
408 kanban_state: entity.kanban_state.clone(),
409 priority: entity.priority.clone(),
410 maintenance_type: entity.maintenance_type.clone(),
411 recurring: entity.recurring.clone(),
412 repeat_interval: entity.repeat_interval.clone(),
413 repeat_unit: entity.repeat_unit.clone(),
414 repeat_type: entity.repeat_type.clone(),
415 repeat_until: entity.repeat_until.clone(),
416 successor_request_id: entity.successor_request_id.clone(),
417 successor_of_request_id: entity.successor_of_request_id.clone(),
418 metadata: entity.metadata.clone(),
419 }
420 }
421}
422
423impl backbone_core::FromCreateDto<CreateMaintenanceRequestDto> for MaintenanceRequest {
424 fn from_create_dto(dto: CreateMaintenanceRequestDto) -> backbone_core::ServiceResult<Self> {
425 Ok(MaintenanceRequest::from(dto))
426 }
427}
428
429impl backbone_core::ApplyUpdateDto<UpdateMaintenanceRequestDto> for MaintenanceRequest {
430 fn apply_update(mut self, dto: UpdateMaintenanceRequestDto) -> backbone_core::ServiceResult<Self> {
431 self.name = dto.name;
432 self.description = dto.description;
433 self.request_date = dto.request_date;
434 self.schedule_date = dto.schedule_date;
435 self.schedule_end = dto.schedule_end;
436 self.close_date = dto.close_date;
437 self.duration = dto.duration;
438 self.owner_user_id = dto.owner_user_id;
439 self.user_id = dto.user_id;
440 self.asset_id = dto.asset_id;
441 self.stage_id = dto.stage_id;
442 self.kanban_state = dto.kanban_state;
443 self.priority = dto.priority;
444 self.maintenance_type = dto.maintenance_type;
445 self.recurring = dto.recurring;
446 self.repeat_interval = dto.repeat_interval;
447 self.repeat_unit = dto.repeat_unit;
448 self.repeat_type = dto.repeat_type;
449 self.repeat_until = dto.repeat_until;
450 self.successor_request_id = dto.successor_request_id;
451 self.successor_of_request_id = dto.successor_of_request_id;
452 Ok(self)
453 }
454}
455
456