Skip to main content

koprogo_api/application/dto/
ticket_dto.rs

1use crate::domain::entities::{
2    Ticket, TicketCategory, TicketKind, TicketPriority, TicketSeverity, TicketStatus,
3};
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7
8#[derive(Debug, Serialize, Deserialize, Clone, utoipa::ToSchema)]
9pub struct TicketResponse {
10    pub id: Uuid,
11    pub organization_id: Uuid,
12    pub building_id: Uuid,
13    pub unit_id: Option<Uuid>,
14    pub created_by: Uuid,
15    pub assigned_to: Option<Uuid>,
16    pub title: String,
17    pub description: String,
18    pub category: TicketCategory,
19    pub priority: TicketPriority,
20    pub status: TicketStatus,
21    pub resolution_notes: Option<String>,
22    pub work_order_sent_at: Option<DateTime<Utc>>,
23    /// Story 3.6 (FR31) — Request (default) vs Complaint.
24    #[serde(default)]
25    pub kind: TicketKind,
26    /// Story 3.6 (FR31) — Triage severity (mandatory for Complaint).
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub severity: Option<TicketSeverity>,
29    /// Story 3.6 (FR31) — Optional incident timestamp.
30    #[serde(default, skip_serializing_if = "Option::is_none")]
31    pub incident_date: Option<DateTime<Utc>>,
32    /// Story 3.6 (FR31) — URLs / S3-MinIO references attached as evidence.
33    #[serde(default)]
34    pub evidence_attachments: Vec<String>,
35    /// Story 3.6 (FR31) — user_ids témoins.
36    #[serde(default)]
37    pub witnesses: Vec<Uuid>,
38    /// Story 3.7 (FR32) — Syndic SLA deadline (computed from `severity`).
39    #[serde(default, skip_serializing_if = "Option::is_none")]
40    pub sla_due_at: Option<DateTime<Utc>>,
41    /// Story 3.7 (FR32) — Timestamp at which SLA was consumed (response in
42    /// time or cron escalation).
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    pub sla_escalated_at: Option<DateTime<Utc>>,
45    pub created_at: DateTime<Utc>,
46    pub updated_at: DateTime<Utc>,
47    pub resolved_at: Option<DateTime<Utc>>,
48    pub closed_at: Option<DateTime<Utc>>,
49    #[serde(default, skip_serializing_if = "Option::is_none")]
50    pub requester_name: Option<String>,
51    #[serde(default, skip_serializing_if = "Option::is_none")]
52    pub assigned_to_name: Option<String>,
53}
54
55impl From<Ticket> for TicketResponse {
56    fn from(ticket: Ticket) -> Self {
57        Self {
58            id: ticket.id,
59            organization_id: ticket.organization_id,
60            building_id: ticket.building_id,
61            unit_id: ticket.unit_id,
62            created_by: ticket.created_by,
63            assigned_to: ticket.assigned_to,
64            title: ticket.title,
65            description: ticket.description,
66            category: ticket.category,
67            priority: ticket.priority,
68            status: ticket.status,
69            resolution_notes: ticket.resolution_notes,
70            work_order_sent_at: ticket.work_order_sent_at,
71            kind: ticket.kind,
72            severity: ticket.severity,
73            incident_date: ticket.incident_date,
74            evidence_attachments: ticket.evidence_attachments,
75            witnesses: ticket.witnesses,
76            sla_due_at: ticket.sla_due_at,
77            sla_escalated_at: ticket.sla_escalated_at,
78            created_at: ticket.created_at,
79            updated_at: ticket.updated_at,
80            resolved_at: ticket.resolved_at,
81            closed_at: ticket.closed_at,
82            requester_name: None,
83            assigned_to_name: None,
84        }
85    }
86}
87
88#[derive(Debug, Deserialize, utoipa::ToSchema)]
89pub struct CreateTicketRequest {
90    pub building_id: Uuid,
91    pub unit_id: Option<Uuid>,
92    pub title: String,
93    pub description: String,
94    pub category: TicketCategory,
95    pub priority: TicketPriority,
96    /// Story 3.6 (FR31) — Request (default) vs Complaint. Optional for
97    /// backward compatibility with pre-3.6 clients (defaults to Request).
98    #[serde(default)]
99    pub kind: Option<TicketKind>,
100    /// Story 3.6 (FR31) — REQUIRED when `kind = Complaint`.
101    #[serde(default)]
102    pub severity: Option<TicketSeverity>,
103    /// Story 3.6 (FR31) — Optional incident timestamp.
104    #[serde(default)]
105    pub incident_date: Option<DateTime<Utc>>,
106    /// Story 3.6 (FR31) — URL / S3-MinIO references. Up to 10.
107    #[serde(default)]
108    pub evidence_attachments: Vec<String>,
109    /// Story 3.6 (FR31) — Up to 10 witness user_ids (no duplicates).
110    #[serde(default)]
111    pub witnesses: Vec<Uuid>,
112}
113
114/// Story 3.6 (FR31 / INV-24) — Partial update applicable only inside the
115/// 5-minute editability window. After that, the use-case returns
116/// `AppError::TicketImmutable` (403).
117#[derive(Debug, Default, Deserialize, utoipa::ToSchema)]
118pub struct UpdateTicketRequest {
119    #[serde(default)]
120    pub title: Option<String>,
121    #[serde(default)]
122    pub description: Option<String>,
123    #[serde(default)]
124    pub category: Option<TicketCategory>,
125    #[serde(default)]
126    pub priority: Option<TicketPriority>,
127    #[serde(default)]
128    pub severity: Option<TicketSeverity>,
129    #[serde(default)]
130    pub incident_date: Option<DateTime<Utc>>,
131    #[serde(default)]
132    pub evidence_attachments: Option<Vec<String>>,
133    #[serde(default)]
134    pub witnesses: Option<Vec<Uuid>>,
135}
136
137#[derive(Debug, Deserialize, utoipa::ToSchema)]
138pub struct AssignTicketRequest {
139    pub assigned_to: Uuid,
140}
141
142#[derive(Debug, Deserialize, utoipa::ToSchema)]
143pub struct ResolveTicketRequest {
144    pub resolution_notes: String,
145}
146
147#[derive(Debug, Deserialize, utoipa::ToSchema)]
148pub struct CancelTicketRequest {
149    pub reason: String,
150}
151
152#[derive(Debug, Deserialize, utoipa::ToSchema)]
153pub struct ReopenTicketRequest {
154    pub reason: String,
155}