koprogo_api/infrastructure/web/handlers/
contractor_report_handlers.rs1use crate::application::dto::contractor_report_dto::{
2 CreateContractorReportDto, GenerateMagicLinkDto, RejectReportDto, RequestCorrectionsDto,
3 UpdateContractorReportDto,
4};
5use crate::infrastructure::web::{AppState, AuthenticatedUser};
6use actix_web::{delete, get, post, put, web, HttpRequest, HttpResponse, Responder};
7use serde::Deserialize;
8use uuid::Uuid;
9
10#[post("/contractor-reports")]
16pub async fn create_contractor_report(
17 state: web::Data<AppState>,
18 user: AuthenticatedUser,
19 body: web::Json<CreateContractorReportDto>,
20) -> impl Responder {
21 let organization_id = match user.require_organization() {
22 Ok(id) => id,
23 Err(e) => {
24 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
25 }
26 };
27
28 match state
29 .contractor_report_use_cases
30 .create(organization_id, body.into_inner())
31 .await
32 {
33 Ok(r) => HttpResponse::Created().json(r),
34 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
35 }
36}
37
38#[get("/contractor-reports/{id}")]
40pub async fn get_contractor_report(
41 state: web::Data<AppState>,
42 user: AuthenticatedUser,
43 path: web::Path<Uuid>,
44) -> impl Responder {
45 let organization_id = match user.require_organization() {
46 Ok(id) => id,
47 Err(e) => {
48 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
49 }
50 };
51
52 match state
53 .contractor_report_use_cases
54 .get(path.into_inner(), organization_id)
55 .await
56 {
57 Ok(r) => HttpResponse::Ok().json(r),
58 Err(e) => {
59 if e.contains("introuvable") {
60 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
61 } else if e.contains("refusé") {
62 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
63 } else {
64 HttpResponse::InternalServerError().json(serde_json::json!({"error": e}))
65 }
66 }
67 }
68}
69
70#[get("/buildings/{building_id}/contractor-reports")]
72pub async fn list_contractor_reports_by_building(
73 state: web::Data<AppState>,
74 user: AuthenticatedUser,
75 path: web::Path<Uuid>,
76) -> impl Responder {
77 let organization_id = match user.require_organization() {
78 Ok(id) => id,
79 Err(e) => {
80 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
81 }
82 };
83
84 match state
85 .contractor_report_use_cases
86 .list_by_building(path.into_inner(), organization_id)
87 .await
88 {
89 Ok(r) => HttpResponse::Ok().json(r),
90 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
91 }
92}
93
94#[get("/tickets/{ticket_id}/contractor-reports")]
96pub async fn list_contractor_reports_by_ticket(
97 state: web::Data<AppState>,
98 user: AuthenticatedUser,
99 path: web::Path<Uuid>,
100) -> impl Responder {
101 let organization_id = match user.require_organization() {
102 Ok(id) => id,
103 Err(e) => {
104 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
105 }
106 };
107
108 match state
109 .contractor_report_use_cases
110 .list_by_ticket(path.into_inner(), organization_id)
111 .await
112 {
113 Ok(r) => HttpResponse::Ok().json(r),
114 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
115 }
116}
117
118#[put("/contractor-reports/{id}")]
120pub async fn update_contractor_report(
121 state: web::Data<AppState>,
122 user: AuthenticatedUser,
123 path: web::Path<Uuid>,
124 body: web::Json<UpdateContractorReportDto>,
125) -> impl Responder {
126 let organization_id = match user.require_organization() {
127 Ok(id) => id,
128 Err(e) => {
129 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
130 }
131 };
132
133 match state
134 .contractor_report_use_cases
135 .update(path.into_inner(), organization_id, body.into_inner())
136 .await
137 {
138 Ok(r) => HttpResponse::Ok().json(r),
139 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
140 }
141}
142
143#[post("/contractor-reports/{id}/submit")]
145pub async fn submit_contractor_report(
146 state: web::Data<AppState>,
147 user: AuthenticatedUser,
148 path: web::Path<Uuid>,
149) -> impl Responder {
150 let organization_id = match user.require_organization() {
151 Ok(id) => id,
152 Err(e) => {
153 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
154 }
155 };
156
157 match state
158 .contractor_report_use_cases
159 .submit(path.into_inner(), organization_id)
160 .await
161 {
162 Ok(r) => HttpResponse::Ok().json(r),
163 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
164 }
165}
166
167#[put("/contractor-reports/{id}/validate")]
169pub async fn validate_contractor_report(
170 state: web::Data<AppState>,
171 user: AuthenticatedUser,
172 path: web::Path<Uuid>,
173) -> impl Responder {
174 let organization_id = match user.require_organization() {
175 Ok(id) => id,
176 Err(e) => {
177 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
178 }
179 };
180
181 match state
182 .contractor_report_use_cases
183 .validate(path.into_inner(), organization_id, user.user_id)
184 .await
185 {
186 Ok(r) => HttpResponse::Ok().json(r),
187 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
188 }
189}
190
191#[put("/contractor-reports/{id}/request-corrections")]
193pub async fn request_corrections(
194 state: web::Data<AppState>,
195 user: AuthenticatedUser,
196 path: web::Path<Uuid>,
197 body: web::Json<RequestCorrectionsDto>,
198) -> impl Responder {
199 let organization_id = match user.require_organization() {
200 Ok(id) => id,
201 Err(e) => {
202 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
203 }
204 };
205
206 match state
207 .contractor_report_use_cases
208 .request_corrections(path.into_inner(), organization_id, body.into_inner())
209 .await
210 {
211 Ok(r) => HttpResponse::Ok().json(r),
212 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
213 }
214}
215
216#[put("/contractor-reports/{id}/reject")]
218pub async fn reject_contractor_report(
219 state: web::Data<AppState>,
220 user: AuthenticatedUser,
221 path: web::Path<Uuid>,
222 body: web::Json<RejectReportDto>,
223) -> impl Responder {
224 let organization_id = match user.require_organization() {
225 Ok(id) => id,
226 Err(e) => {
227 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
228 }
229 };
230
231 match state
232 .contractor_report_use_cases
233 .reject(
234 path.into_inner(),
235 organization_id,
236 body.into_inner(),
237 user.user_id,
238 )
239 .await
240 {
241 Ok(r) => HttpResponse::Ok().json(r),
242 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
243 }
244}
245
246#[post("/contractor-reports/magic-link")]
248pub async fn generate_magic_link(
249 state: web::Data<AppState>,
250 user: AuthenticatedUser,
251 req: HttpRequest,
252 body: web::Json<GenerateMagicLinkDto>,
253) -> impl Responder {
254 let organization_id = match user.require_organization() {
255 Ok(id) => id,
256 Err(e) => {
257 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
258 }
259 };
260
261 let base_url = {
263 let connection_info = req.connection_info();
264 format!("{}://{}", connection_info.scheme(), connection_info.host())
265 };
266
267 match state
268 .contractor_report_use_cases
269 .generate_magic_link(body.report_id, organization_id, &base_url)
270 .await
271 {
272 Ok(r) => HttpResponse::Ok().json(r),
273 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
274 }
275}
276
277#[delete("/contractor-reports/{id}")]
279pub async fn delete_contractor_report(
280 state: web::Data<AppState>,
281 user: AuthenticatedUser,
282 path: web::Path<Uuid>,
283) -> impl Responder {
284 let organization_id = match user.require_organization() {
285 Ok(id) => id,
286 Err(e) => {
287 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
288 }
289 };
290
291 match state
292 .contractor_report_use_cases
293 .delete(path.into_inner(), organization_id)
294 .await
295 {
296 Ok(()) => HttpResponse::NoContent().finish(),
297 Err(e) => {
298 if e.contains("introuvable") {
299 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
300 } else if e.contains("refusé") {
301 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
302 } else {
303 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
304 }
305 }
306 }
307}
308
309#[get("/contractor/token/{token}")]
315pub async fn get_report_by_token(
316 state: web::Data<AppState>,
317 path: web::Path<String>,
318) -> impl Responder {
319 match state
320 .contractor_report_use_cases
321 .get_by_token(&path.into_inner())
322 .await
323 {
324 Ok(r) => HttpResponse::Ok().json(r),
325 Err(e) => HttpResponse::Unauthorized().json(serde_json::json!({"error": e})),
326 }
327}
328
329#[post("/contractor/token/{token}/submit")]
331pub async fn submit_report_by_token(
332 state: web::Data<AppState>,
333 path: web::Path<String>,
334) -> impl Responder {
335 match state
336 .contractor_report_use_cases
337 .submit_by_token(&path.into_inner())
338 .await
339 {
340 Ok(r) => HttpResponse::Ok().json(r),
341 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
342 }
343}
344
345#[get("/contractor-reports/magic/{token}")]
352pub async fn get_report_by_magic_token(
353 state: web::Data<AppState>,
354 path: web::Path<String>,
355) -> impl Responder {
356 match state
357 .contractor_report_use_cases
358 .get_by_token(&path.into_inner())
359 .await
360 {
361 Ok(r) => HttpResponse::Ok().json(r),
362 Err(e) => HttpResponse::Unauthorized().json(serde_json::json!({"error": e})),
363 }
364}
365
366#[derive(Deserialize)]
370pub struct MagicLinkSubmitDto {
371 pub work_date: Option<String>,
372 pub contractor_name: Option<String>,
373 pub compte_rendu: Option<String>,
374 pub parts_replaced: Option<Vec<serde_json::Value>>,
375 pub photos_before: Option<Vec<String>>,
376 pub photos_after: Option<Vec<String>>,
377}
378
379#[post("/contractor-reports/magic/{token}/submit")]
380pub async fn submit_report_by_magic_token(
381 state: web::Data<AppState>,
382 path: web::Path<String>,
383 _body: web::Json<MagicLinkSubmitDto>,
384) -> impl Responder {
385 let token = path.into_inner();
386
387 match state.contractor_report_use_cases.get_by_token(&token).await {
389 Ok(_report) => {
390 match state
392 .contractor_report_use_cases
393 .submit_by_token(&token)
394 .await
395 {
396 Ok(r) => HttpResponse::Ok().json(r),
397 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
398 }
399 }
400 Err(e) => HttpResponse::Unauthorized().json(serde_json::json!({"error": e})),
401 }
402}