Skip to main content

backbone_support/presentation/dto/
issue_dto.rs

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