koprogo_api/infrastructure/web/handlers/
work_report_handlers.rs1use crate::application::dto::{
2 AddDocumentDto, AddPhotoDto, CreateWorkReportDto, PageRequest, UpdateWorkReportDto,
3 WorkReportFilters,
4};
5use crate::infrastructure::audit::{AuditEventType, AuditLogEntry};
6use crate::infrastructure::web::{AppState, AuthenticatedUser};
7use actix_web::{delete, get, post, put, web, HttpResponse, Responder};
8use uuid::Uuid;
9
10#[post("/work-reports")]
14pub async fn create_work_report(
15 state: web::Data<AppState>,
16 user: AuthenticatedUser,
17 request: web::Json<CreateWorkReportDto>,
18) -> impl Responder {
19 let organization_id = match user.require_organization() {
20 Ok(org_id) => org_id,
21 Err(e) => {
22 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
23 }
24 };
25
26 match state
27 .work_report_use_cases
28 .create_work_report(request.into_inner())
29 .await
30 {
31 Ok(work_report) => {
32 AuditLogEntry::new(
33 AuditEventType::WorkReportCreated,
34 Some(user.user_id),
35 Some(organization_id),
36 )
37 .with_resource("WorkReport", work_report.id.parse().unwrap())
38 .log();
39
40 HttpResponse::Created().json(work_report)
41 }
42 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
43 }
44}
45
46#[get("/work-reports/{id}")]
48pub async fn get_work_report(
49 state: web::Data<AppState>,
50 user: AuthenticatedUser,
51 id: web::Path<Uuid>,
52) -> impl Responder {
53 match state.work_report_use_cases.get_work_report(*id).await {
54 Ok(Some(work_report)) => {
55 let building_id = match uuid::Uuid::parse_str(&work_report.building_id) {
57 Ok(bid) => bid,
58 Err(_) => {
59 return HttpResponse::InternalServerError()
60 .json(serde_json::json!({"error": "Invalid building_id format"}))
61 }
62 };
63
64 match state.building_use_cases.get_building(building_id).await {
65 Ok(Some(building)) => {
66 let org_id = match uuid::Uuid::parse_str(&building.organization_id) {
68 Ok(oid) => oid,
69 Err(_) => {
70 return HttpResponse::InternalServerError().json(
71 serde_json::json!({"error": "Invalid organization_id format"}),
72 )
73 }
74 };
75
76 if let Err(err) = user.verify_org_access(org_id) {
77 return HttpResponse::Forbidden().json(serde_json::json!({"error": err}));
78 }
79 HttpResponse::Ok().json(work_report)
80 }
81 Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
82 "error": "Building not found"
83 })),
84 Err(err) => {
85 HttpResponse::InternalServerError().json(serde_json::json!({"error": err}))
86 }
87 }
88 }
89 Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
90 "error": "Work report not found"
91 })),
92 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
93 }
94}
95
96#[get("/buildings/{building_id}/work-reports")]
98pub async fn list_building_work_reports(
99 state: web::Data<AppState>,
100 building_id: web::Path<Uuid>,
101) -> impl Responder {
102 match state
103 .work_report_use_cases
104 .list_work_reports_by_building(*building_id)
105 .await
106 {
107 Ok(work_reports) => HttpResponse::Ok().json(work_reports),
108 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
109 }
110}
111
112#[get("/organizations/{organization_id}/work-reports")]
114pub async fn list_organization_work_reports(
115 state: web::Data<AppState>,
116 user: AuthenticatedUser,
117 organization_id: web::Path<Uuid>,
118) -> impl Responder {
119 if let Err(e) = user.verify_org_access(*organization_id) {
120 return HttpResponse::Forbidden().json(serde_json::json!({"error": e}));
121 }
122 match state
123 .work_report_use_cases
124 .list_work_reports_by_organization(*organization_id)
125 .await
126 {
127 Ok(work_reports) => HttpResponse::Ok().json(work_reports),
128 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
129 }
130}
131
132#[get("/work-reports")]
134pub async fn list_work_reports_paginated(
135 state: web::Data<AppState>,
136 page_request: web::Query<PageRequest>,
137 filters: web::Query<WorkReportFilters>,
138) -> impl Responder {
139 match state
140 .work_report_use_cases
141 .list_work_reports_paginated(&page_request.into_inner(), &filters.into_inner())
142 .await
143 {
144 Ok(response) => HttpResponse::Ok().json(response),
145 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
146 }
147}
148
149#[put("/work-reports/{id}")]
151pub async fn update_work_report(
152 state: web::Data<AppState>,
153 user: AuthenticatedUser,
154 id: web::Path<Uuid>,
155 request: web::Json<UpdateWorkReportDto>,
156) -> impl Responder {
157 let organization_id = match user.require_organization() {
158 Ok(org_id) => org_id,
159 Err(e) => {
160 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
161 }
162 };
163
164 match state
165 .work_report_use_cases
166 .update_work_report(*id, request.into_inner())
167 .await
168 {
169 Ok(work_report) => {
170 AuditLogEntry::new(
171 AuditEventType::WorkReportUpdated,
172 Some(user.user_id),
173 Some(organization_id),
174 )
175 .with_resource("WorkReport", *id)
176 .log();
177
178 HttpResponse::Ok().json(work_report)
179 }
180 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
181 }
182}
183
184#[delete("/work-reports/{id}")]
186pub async fn delete_work_report(
187 state: web::Data<AppState>,
188 user: AuthenticatedUser,
189 id: web::Path<Uuid>,
190) -> impl Responder {
191 let organization_id = match user.require_organization() {
192 Ok(org_id) => org_id,
193 Err(e) => {
194 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
195 }
196 };
197
198 match state.work_report_use_cases.delete_work_report(*id).await {
199 Ok(deleted) => {
200 if deleted {
201 AuditLogEntry::new(
202 AuditEventType::WorkReportDeleted,
203 Some(user.user_id),
204 Some(organization_id),
205 )
206 .with_resource("WorkReport", *id)
207 .log();
208
209 HttpResponse::NoContent().finish()
210 } else {
211 HttpResponse::NotFound().json(serde_json::json!({
212 "error": "Work report not found"
213 }))
214 }
215 }
216 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
217 }
218}
219
220#[get("/buildings/{building_id}/work-reports/warranties/active")]
224pub async fn get_active_warranties(
225 state: web::Data<AppState>,
226 building_id: web::Path<Uuid>,
227) -> impl Responder {
228 match state
229 .work_report_use_cases
230 .get_active_warranties(*building_id)
231 .await
232 {
233 Ok(warranties) => HttpResponse::Ok().json(warranties),
234 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
235 }
236}
237
238#[get("/buildings/{building_id}/work-reports/warranties/expiring")]
240pub async fn get_expiring_warranties(
241 state: web::Data<AppState>,
242 path: web::Path<Uuid>,
243 query: web::Query<serde_json::Value>,
244) -> impl Responder {
245 let building_id = path.into_inner();
246 let days = query.get("days").and_then(|v| v.as_i64()).unwrap_or(90) as i32;
247
248 match state
249 .work_report_use_cases
250 .get_expiring_warranties(building_id, days)
251 .await
252 {
253 Ok(warranties) => HttpResponse::Ok().json(warranties),
254 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
255 }
256}
257
258#[post("/work-reports/{id}/photos")]
262pub async fn add_photo(
263 state: web::Data<AppState>,
264 user: AuthenticatedUser,
265 id: web::Path<Uuid>,
266 request: web::Json<AddPhotoDto>,
267) -> impl Responder {
268 let organization_id = match user.require_organization() {
269 Ok(org_id) => org_id,
270 Err(e) => {
271 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
272 }
273 };
274
275 match state
276 .work_report_use_cases
277 .add_photo(*id, request.into_inner())
278 .await
279 {
280 Ok(work_report) => {
281 AuditLogEntry::new(
282 AuditEventType::WorkReportPhotoAdded,
283 Some(user.user_id),
284 Some(organization_id),
285 )
286 .with_resource("WorkReport", *id)
287 .log();
288
289 HttpResponse::Ok().json(work_report)
290 }
291 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
292 }
293}
294
295#[post("/work-reports/{id}/documents")]
297pub async fn add_document(
298 state: web::Data<AppState>,
299 user: AuthenticatedUser,
300 id: web::Path<Uuid>,
301 request: web::Json<AddDocumentDto>,
302) -> impl Responder {
303 let organization_id = match user.require_organization() {
304 Ok(org_id) => org_id,
305 Err(e) => {
306 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
307 }
308 };
309
310 match state
311 .work_report_use_cases
312 .add_document(*id, request.into_inner())
313 .await
314 {
315 Ok(work_report) => {
316 AuditLogEntry::new(
317 AuditEventType::WorkReportDocumentAdded,
318 Some(user.user_id),
319 Some(organization_id),
320 )
321 .with_resource("WorkReport", *id)
322 .log();
323
324 HttpResponse::Ok().json(work_report)
325 }
326 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
327 }
328}