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