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 organization_id: web::Path<Uuid>,
117) -> impl Responder {
118 match state
119 .work_report_use_cases
120 .list_work_reports_by_organization(*organization_id)
121 .await
122 {
123 Ok(work_reports) => HttpResponse::Ok().json(work_reports),
124 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
125 }
126}
127
128#[get("/work-reports")]
130pub async fn list_work_reports_paginated(
131 state: web::Data<AppState>,
132 page_request: web::Query<PageRequest>,
133 filters: web::Query<WorkReportFilters>,
134) -> impl Responder {
135 match state
136 .work_report_use_cases
137 .list_work_reports_paginated(&page_request.into_inner(), &filters.into_inner())
138 .await
139 {
140 Ok(response) => HttpResponse::Ok().json(response),
141 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
142 }
143}
144
145#[put("/work-reports/{id}")]
147pub async fn update_work_report(
148 state: web::Data<AppState>,
149 user: AuthenticatedUser,
150 id: web::Path<Uuid>,
151 request: web::Json<UpdateWorkReportDto>,
152) -> impl Responder {
153 let organization_id = match user.require_organization() {
154 Ok(org_id) => org_id,
155 Err(e) => {
156 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
157 }
158 };
159
160 match state
161 .work_report_use_cases
162 .update_work_report(*id, request.into_inner())
163 .await
164 {
165 Ok(work_report) => {
166 AuditLogEntry::new(
167 AuditEventType::WorkReportUpdated,
168 Some(user.user_id),
169 Some(organization_id),
170 )
171 .with_resource("WorkReport", *id)
172 .log();
173
174 HttpResponse::Ok().json(work_report)
175 }
176 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
177 }
178}
179
180#[delete("/work-reports/{id}")]
182pub async fn delete_work_report(
183 state: web::Data<AppState>,
184 user: AuthenticatedUser,
185 id: web::Path<Uuid>,
186) -> impl Responder {
187 let organization_id = match user.require_organization() {
188 Ok(org_id) => org_id,
189 Err(e) => {
190 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
191 }
192 };
193
194 match state.work_report_use_cases.delete_work_report(*id).await {
195 Ok(deleted) => {
196 if deleted {
197 AuditLogEntry::new(
198 AuditEventType::WorkReportDeleted,
199 Some(user.user_id),
200 Some(organization_id),
201 )
202 .with_resource("WorkReport", *id)
203 .log();
204
205 HttpResponse::NoContent().finish()
206 } else {
207 HttpResponse::NotFound().json(serde_json::json!({
208 "error": "Work report not found"
209 }))
210 }
211 }
212 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
213 }
214}
215
216#[get("/buildings/{building_id}/work-reports/warranties/active")]
220pub async fn get_active_warranties(
221 state: web::Data<AppState>,
222 building_id: web::Path<Uuid>,
223) -> impl Responder {
224 match state
225 .work_report_use_cases
226 .get_active_warranties(*building_id)
227 .await
228 {
229 Ok(warranties) => HttpResponse::Ok().json(warranties),
230 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
231 }
232}
233
234#[get("/buildings/{building_id}/work-reports/warranties/expiring")]
236pub async fn get_expiring_warranties(
237 state: web::Data<AppState>,
238 path: web::Path<Uuid>,
239 query: web::Query<serde_json::Value>,
240) -> impl Responder {
241 let building_id = path.into_inner();
242 let days = query.get("days").and_then(|v| v.as_i64()).unwrap_or(90) as i32;
243
244 match state
245 .work_report_use_cases
246 .get_expiring_warranties(building_id, days)
247 .await
248 {
249 Ok(warranties) => HttpResponse::Ok().json(warranties),
250 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
251 }
252}
253
254#[post("/work-reports/{id}/photos")]
258pub async fn add_photo(
259 state: web::Data<AppState>,
260 user: AuthenticatedUser,
261 id: web::Path<Uuid>,
262 request: web::Json<AddPhotoDto>,
263) -> impl Responder {
264 let organization_id = match user.require_organization() {
265 Ok(org_id) => org_id,
266 Err(e) => {
267 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
268 }
269 };
270
271 match state
272 .work_report_use_cases
273 .add_photo(*id, request.into_inner())
274 .await
275 {
276 Ok(work_report) => {
277 AuditLogEntry::new(
278 AuditEventType::WorkReportPhotoAdded,
279 Some(user.user_id),
280 Some(organization_id),
281 )
282 .with_resource("WorkReport", *id)
283 .log();
284
285 HttpResponse::Ok().json(work_report)
286 }
287 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
288 }
289}
290
291#[post("/work-reports/{id}/documents")]
293pub async fn add_document(
294 state: web::Data<AppState>,
295 user: AuthenticatedUser,
296 id: web::Path<Uuid>,
297 request: web::Json<AddDocumentDto>,
298) -> impl Responder {
299 let organization_id = match user.require_organization() {
300 Ok(org_id) => org_id,
301 Err(e) => {
302 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
303 }
304 };
305
306 match state
307 .work_report_use_cases
308 .add_document(*id, request.into_inner())
309 .await
310 {
311 Ok(work_report) => {
312 AuditLogEntry::new(
313 AuditEventType::WorkReportDocumentAdded,
314 Some(user.user_id),
315 Some(organization_id),
316 )
317 .with_resource("WorkReport", *id)
318 .log();
319
320 HttpResponse::Ok().json(work_report)
321 }
322 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
323 }
324}