koprogo_api/application/use_cases/
ticket_use_cases.rs1use crate::application::dto::{
2 AssignTicketRequest, CancelTicketRequest, CreateTicketRequest, ReopenTicketRequest,
3 ResolveTicketRequest, TicketResponse,
4};
5use crate::application::ports::TicketRepository;
6use crate::domain::entities::{Ticket, TicketStatus};
7use std::sync::Arc;
8use uuid::Uuid;
9
10pub struct TicketUseCases {
11 ticket_repository: Arc<dyn TicketRepository>,
12}
13
14impl TicketUseCases {
15 pub fn new(ticket_repository: Arc<dyn TicketRepository>) -> Self {
16 Self { ticket_repository }
17 }
18
19 pub async fn create_ticket(
21 &self,
22 organization_id: Uuid,
23 created_by: Uuid,
24 request: CreateTicketRequest,
25 ) -> Result<TicketResponse, String> {
26 let ticket = Ticket::new(
27 organization_id,
28 request.building_id,
29 request.unit_id,
30 created_by,
31 request.title,
32 request.description,
33 request.category,
34 request.priority,
35 )?;
36
37 let created = self.ticket_repository.create(&ticket).await?;
38 Ok(TicketResponse::from(created))
39 }
40
41 pub async fn get_ticket(&self, id: Uuid) -> Result<Option<TicketResponse>, String> {
43 match self.ticket_repository.find_by_id(id).await? {
44 Some(ticket) => Ok(Some(TicketResponse::from(ticket))),
45 None => Ok(None),
46 }
47 }
48
49 pub async fn list_tickets_by_building(
51 &self,
52 building_id: Uuid,
53 ) -> Result<Vec<TicketResponse>, String> {
54 let tickets = self.ticket_repository.find_by_building(building_id).await?;
55 Ok(tickets.into_iter().map(TicketResponse::from).collect())
56 }
57
58 pub async fn list_tickets_by_organization(
60 &self,
61 organization_id: Uuid,
62 ) -> Result<Vec<TicketResponse>, String> {
63 let tickets = self
64 .ticket_repository
65 .find_by_organization(organization_id)
66 .await?;
67 Ok(tickets.into_iter().map(TicketResponse::from).collect())
68 }
69
70 pub async fn list_my_tickets(&self, created_by: Uuid) -> Result<Vec<TicketResponse>, String> {
72 let tickets = self
73 .ticket_repository
74 .find_by_created_by(created_by)
75 .await?;
76 Ok(tickets.into_iter().map(TicketResponse::from).collect())
77 }
78
79 pub async fn list_assigned_tickets(
81 &self,
82 assigned_to: Uuid,
83 ) -> Result<Vec<TicketResponse>, String> {
84 let tickets = self
85 .ticket_repository
86 .find_by_assigned_to(assigned_to)
87 .await?;
88 Ok(tickets.into_iter().map(TicketResponse::from).collect())
89 }
90
91 pub async fn list_tickets_by_status(
93 &self,
94 building_id: Uuid,
95 status: TicketStatus,
96 ) -> Result<Vec<TicketResponse>, String> {
97 let tickets = self
98 .ticket_repository
99 .find_by_status(building_id, status)
100 .await?;
101 Ok(tickets.into_iter().map(TicketResponse::from).collect())
102 }
103
104 pub async fn assign_ticket(
106 &self,
107 id: Uuid,
108 request: AssignTicketRequest,
109 ) -> Result<TicketResponse, String> {
110 let mut ticket = self
111 .ticket_repository
112 .find_by_id(id)
113 .await?
114 .ok_or_else(|| "Ticket not found".to_string())?;
115
116 ticket.assign(request.assigned_to)?;
117
118 let updated = self.ticket_repository.update(&ticket).await?;
119 Ok(TicketResponse::from(updated))
120 }
121
122 pub async fn start_work(&self, id: Uuid) -> Result<TicketResponse, String> {
124 let mut ticket = self
125 .ticket_repository
126 .find_by_id(id)
127 .await?
128 .ok_or_else(|| "Ticket not found".to_string())?;
129
130 ticket.start_work()?;
131
132 let updated = self.ticket_repository.update(&ticket).await?;
133 Ok(TicketResponse::from(updated))
134 }
135
136 pub async fn resolve_ticket(
138 &self,
139 id: Uuid,
140 request: ResolveTicketRequest,
141 ) -> Result<TicketResponse, String> {
142 let mut ticket = self
143 .ticket_repository
144 .find_by_id(id)
145 .await?
146 .ok_or_else(|| "Ticket not found".to_string())?;
147
148 ticket.resolve(request.resolution_notes)?;
149
150 let updated = self.ticket_repository.update(&ticket).await?;
151 Ok(TicketResponse::from(updated))
152 }
153
154 pub async fn close_ticket(&self, id: Uuid) -> Result<TicketResponse, String> {
156 let mut ticket = self
157 .ticket_repository
158 .find_by_id(id)
159 .await?
160 .ok_or_else(|| "Ticket not found".to_string())?;
161
162 ticket.close()?;
163
164 let updated = self.ticket_repository.update(&ticket).await?;
165 Ok(TicketResponse::from(updated))
166 }
167
168 pub async fn cancel_ticket(
170 &self,
171 id: Uuid,
172 request: CancelTicketRequest,
173 ) -> Result<TicketResponse, String> {
174 let mut ticket = self
175 .ticket_repository
176 .find_by_id(id)
177 .await?
178 .ok_or_else(|| "Ticket not found".to_string())?;
179
180 ticket.cancel(request.reason)?;
181
182 let updated = self.ticket_repository.update(&ticket).await?;
183 Ok(TicketResponse::from(updated))
184 }
185
186 pub async fn reopen_ticket(
188 &self,
189 id: Uuid,
190 request: ReopenTicketRequest,
191 ) -> Result<TicketResponse, String> {
192 let mut ticket = self
193 .ticket_repository
194 .find_by_id(id)
195 .await?
196 .ok_or_else(|| "Ticket not found".to_string())?;
197
198 ticket.reopen(request.reason)?;
199
200 let updated = self.ticket_repository.update(&ticket).await?;
201 Ok(TicketResponse::from(updated))
202 }
203
204 pub async fn delete_ticket(&self, id: Uuid) -> Result<bool, String> {
206 let ticket = self
207 .ticket_repository
208 .find_by_id(id)
209 .await?
210 .ok_or_else(|| "Ticket not found".to_string())?;
211
212 if ticket.status != TicketStatus::Open {
214 return Err("Can only delete tickets with Open status".to_string());
215 }
216
217 self.ticket_repository.delete(id).await
218 }
219
220 pub async fn get_ticket_statistics(
222 &self,
223 building_id: Uuid,
224 ) -> Result<TicketStatistics, String> {
225 let total = self
226 .ticket_repository
227 .count_by_building(building_id)
228 .await?;
229
230 let open = self
231 .ticket_repository
232 .count_by_status(building_id, TicketStatus::Open)
233 .await?;
234
235 let in_progress = self
236 .ticket_repository
237 .count_by_status(building_id, TicketStatus::InProgress)
238 .await?;
239
240 let resolved = self
241 .ticket_repository
242 .count_by_status(building_id, TicketStatus::Resolved)
243 .await?;
244
245 let closed = self
246 .ticket_repository
247 .count_by_status(building_id, TicketStatus::Closed)
248 .await?;
249
250 let cancelled = self
251 .ticket_repository
252 .count_by_status(building_id, TicketStatus::Cancelled)
253 .await?;
254
255 Ok(TicketStatistics {
256 total,
257 open,
258 in_progress,
259 resolved,
260 closed,
261 cancelled,
262 })
263 }
264
265 pub async fn get_overdue_tickets(
267 &self,
268 building_id: Uuid,
269 max_days: i64,
270 ) -> Result<Vec<TicketResponse>, String> {
271 let tickets = self.ticket_repository.find_by_building(building_id).await?;
272
273 let overdue: Vec<TicketResponse> = tickets
274 .into_iter()
275 .filter(|ticket| ticket.is_overdue(max_days))
276 .map(TicketResponse::from)
277 .collect();
278
279 Ok(overdue)
280 }
281}
282
283#[derive(Debug, serde::Serialize)]
285pub struct TicketStatistics {
286 pub total: i64,
287 pub open: i64,
288 pub in_progress: i64,
289 pub resolved: i64,
290 pub closed: i64,
291 pub cancelled: i64,
292}