koprogo_api/infrastructure/web/handlers/
etat_date_handlers.rs

1use crate::application::dto::{
2    CreateEtatDateRequest, PageRequest, PageResponse, UpdateEtatDateAdditionalDataRequest,
3    UpdateEtatDateFinancialRequest,
4};
5use crate::domain::entities::EtatDateStatus;
6use crate::infrastructure::audit::{AuditEventType, AuditLogEntry};
7use crate::infrastructure::web::{AppState, AuthenticatedUser};
8use actix_web::{delete, get, post, put, web, HttpResponse, Responder};
9use serde::Deserialize;
10use uuid::Uuid;
11
12#[derive(Debug, Deserialize)]
13pub struct EtatDateListQuery {
14    #[serde(default = "default_page")]
15    pub page: i64,
16    #[serde(default = "default_per_page")]
17    pub per_page: i64,
18    pub status: Option<String>,
19}
20
21fn default_page() -> i64 {
22    1
23}
24fn default_per_page() -> i64 {
25    10
26}
27
28/// Create a new état daté request
29#[post("/etats-dates")]
30pub async fn create_etat_date(
31    state: web::Data<AppState>,
32    user: AuthenticatedUser,
33    mut request: web::Json<CreateEtatDateRequest>,
34) -> impl Responder {
35    // Override organization_id from JWT token (security)
36    let organization_id = match user.require_organization() {
37        Ok(org_id) => org_id,
38        Err(e) => {
39            return HttpResponse::Unauthorized().json(serde_json::json!({
40                "error": e.to_string()
41            }))
42        }
43    };
44    request.organization_id = organization_id;
45
46    match state
47        .etat_date_use_cases
48        .create_etat_date(request.into_inner())
49        .await
50    {
51        Ok(etat_date) => {
52            AuditLogEntry::new(
53                AuditEventType::EtatDateCreated,
54                Some(user.user_id),
55                Some(organization_id),
56            )
57            .with_resource("EtatDate", etat_date.id)
58            .log();
59
60            HttpResponse::Created().json(etat_date)
61        }
62        Err(err) => {
63            AuditLogEntry::new(
64                AuditEventType::EtatDateCreated,
65                Some(user.user_id),
66                Some(organization_id),
67            )
68            .with_error(err.clone())
69            .log();
70
71            HttpResponse::BadRequest().json(serde_json::json!({
72                "error": err
73            }))
74        }
75    }
76}
77
78/// Get état daté by ID
79#[get("/etats-dates/{id}")]
80pub async fn get_etat_date(
81    state: web::Data<AppState>,
82    user: AuthenticatedUser,
83    id: web::Path<Uuid>,
84) -> impl Responder {
85    match state.etat_date_use_cases.get_etat_date(*id).await {
86        Ok(Some(etat_date)) => {
87            // Verify organization access
88            if let Err(err) = user.verify_org_access(etat_date.organization_id) {
89                return HttpResponse::Forbidden().json(serde_json::json!({"error": err}));
90            }
91            HttpResponse::Ok().json(etat_date)
92        }
93        Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
94            "error": "État daté not found"
95        })),
96        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
97            "error": err
98        })),
99    }
100}
101
102/// Get état daté by reference number
103#[get("/etats-dates/reference/{reference_number}")]
104pub async fn get_by_reference_number(
105    state: web::Data<AppState>,
106    reference_number: web::Path<String>,
107) -> impl Responder {
108    match state
109        .etat_date_use_cases
110        .get_by_reference_number(&reference_number)
111        .await
112    {
113        Ok(Some(etat_date)) => HttpResponse::Ok().json(etat_date),
114        Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
115            "error": "État daté not found"
116        })),
117        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
118            "error": err
119        })),
120    }
121}
122
123/// List états datés paginated
124#[get("/etats-dates")]
125pub async fn list_etats_dates(
126    state: web::Data<AppState>,
127    user: AuthenticatedUser,
128    query: web::Query<EtatDateListQuery>,
129) -> impl Responder {
130    let organization_id = user.organization_id;
131
132    // Parse status filter
133    let status = query.status.as_ref().and_then(|s| match s.as_str() {
134        "requested" => Some(EtatDateStatus::Requested),
135        "in_progress" => Some(EtatDateStatus::InProgress),
136        "generated" => Some(EtatDateStatus::Generated),
137        "delivered" => Some(EtatDateStatus::Delivered),
138        "expired" => Some(EtatDateStatus::Expired),
139        _ => None,
140    });
141
142    let page_request = PageRequest {
143        page: query.page,
144        per_page: query.per_page,
145        sort_by: None,
146        order: Default::default(),
147    };
148
149    match state
150        .etat_date_use_cases
151        .list_paginated(&page_request, organization_id, status)
152        .await
153    {
154        Ok((etats, total)) => {
155            let response =
156                PageResponse::new(etats, page_request.page, page_request.per_page, total);
157            HttpResponse::Ok().json(response)
158        }
159        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
160            "error": err
161        })),
162    }
163}
164
165/// List états datés by unit
166#[get("/units/{unit_id}/etats-dates")]
167pub async fn list_etats_dates_by_unit(
168    state: web::Data<AppState>,
169    unit_id: web::Path<Uuid>,
170) -> impl Responder {
171    match state.etat_date_use_cases.list_by_unit(*unit_id).await {
172        Ok(etats) => HttpResponse::Ok().json(etats),
173        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
174            "error": err
175        })),
176    }
177}
178
179/// List états datés by building
180#[get("/buildings/{building_id}/etats-dates")]
181pub async fn list_etats_dates_by_building(
182    state: web::Data<AppState>,
183    building_id: web::Path<Uuid>,
184) -> impl Responder {
185    match state
186        .etat_date_use_cases
187        .list_by_building(*building_id)
188        .await
189    {
190        Ok(etats) => HttpResponse::Ok().json(etats),
191        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
192            "error": err
193        })),
194    }
195}
196
197/// Mark état daté as in progress
198#[put("/etats-dates/{id}/mark-in-progress")]
199pub async fn mark_in_progress(
200    state: web::Data<AppState>,
201    user: AuthenticatedUser,
202    id: web::Path<Uuid>,
203) -> impl Responder {
204    match state.etat_date_use_cases.mark_in_progress(*id).await {
205        Ok(etat_date) => {
206            AuditLogEntry::new(
207                AuditEventType::EtatDateInProgress,
208                Some(user.user_id),
209                user.organization_id,
210            )
211            .with_resource("EtatDate", etat_date.id)
212            .log();
213
214            HttpResponse::Ok().json(etat_date)
215        }
216        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({
217            "error": err
218        })),
219    }
220}
221
222/// Mark état daté as generated (with PDF file path)
223#[put("/etats-dates/{id}/mark-generated")]
224pub async fn mark_generated(
225    state: web::Data<AppState>,
226    user: AuthenticatedUser,
227    id: web::Path<Uuid>,
228    pdf_path: web::Json<serde_json::Value>,
229) -> impl Responder {
230    let pdf_file_path = match pdf_path.get("pdf_file_path") {
231        Some(serde_json::Value::String(path)) => path.clone(),
232        _ => {
233            return HttpResponse::BadRequest().json(serde_json::json!({
234                "error": "pdf_file_path is required as a string"
235            }))
236        }
237    };
238
239    match state
240        .etat_date_use_cases
241        .mark_generated(*id, pdf_file_path)
242        .await
243    {
244        Ok(etat_date) => {
245            AuditLogEntry::new(
246                AuditEventType::EtatDateGenerated,
247                Some(user.user_id),
248                user.organization_id,
249            )
250            .with_resource("EtatDate", etat_date.id)
251            .log();
252
253            HttpResponse::Ok().json(etat_date)
254        }
255        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({
256            "error": err
257        })),
258    }
259}
260
261/// Mark état daté as delivered to notary
262#[put("/etats-dates/{id}/mark-delivered")]
263pub async fn mark_delivered(
264    state: web::Data<AppState>,
265    user: AuthenticatedUser,
266    id: web::Path<Uuid>,
267) -> impl Responder {
268    match state.etat_date_use_cases.mark_delivered(*id).await {
269        Ok(etat_date) => {
270            AuditLogEntry::new(
271                AuditEventType::EtatDateDelivered,
272                Some(user.user_id),
273                user.organization_id,
274            )
275            .with_resource("EtatDate", etat_date.id)
276            .log();
277
278            HttpResponse::Ok().json(etat_date)
279        }
280        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({
281            "error": err
282        })),
283    }
284}
285
286/// Update financial data
287#[put("/etats-dates/{id}/financial")]
288pub async fn update_financial_data(
289    state: web::Data<AppState>,
290    user: AuthenticatedUser,
291    id: web::Path<Uuid>,
292    request: web::Json<UpdateEtatDateFinancialRequest>,
293) -> impl Responder {
294    match state
295        .etat_date_use_cases
296        .update_financial_data(*id, request.into_inner())
297        .await
298    {
299        Ok(etat_date) => {
300            AuditLogEntry::new(
301                AuditEventType::EtatDateFinancialUpdate,
302                Some(user.user_id),
303                user.organization_id,
304            )
305            .with_resource("EtatDate", etat_date.id)
306            .log();
307
308            HttpResponse::Ok().json(etat_date)
309        }
310        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({
311            "error": err
312        })),
313    }
314}
315
316/// Update additional data (sections 7-16)
317#[put("/etats-dates/{id}/additional-data")]
318pub async fn update_additional_data(
319    state: web::Data<AppState>,
320    user: AuthenticatedUser,
321    id: web::Path<Uuid>,
322    request: web::Json<UpdateEtatDateAdditionalDataRequest>,
323) -> impl Responder {
324    match state
325        .etat_date_use_cases
326        .update_additional_data(*id, request.into_inner())
327        .await
328    {
329        Ok(etat_date) => {
330            AuditLogEntry::new(
331                AuditEventType::EtatDateAdditionalDataUpdate,
332                Some(user.user_id),
333                user.organization_id,
334            )
335            .with_resource("EtatDate", etat_date.id)
336            .log();
337
338            HttpResponse::Ok().json(etat_date)
339        }
340        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({
341            "error": err
342        })),
343    }
344}
345
346/// List overdue états datés (>10 days, not generated yet)
347#[get("/etats-dates/overdue")]
348pub async fn list_overdue(state: web::Data<AppState>, user: AuthenticatedUser) -> impl Responder {
349    let organization_id = match user.require_organization() {
350        Ok(org_id) => org_id,
351        Err(e) => {
352            return HttpResponse::Unauthorized().json(serde_json::json!({
353                "error": e.to_string()
354            }))
355        }
356    };
357
358    match state
359        .etat_date_use_cases
360        .list_overdue(organization_id)
361        .await
362    {
363        Ok(etats) => HttpResponse::Ok().json(etats),
364        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
365            "error": err
366        })),
367    }
368}
369
370/// List expired états datés (>3 months from reference date)
371#[get("/etats-dates/expired")]
372pub async fn list_expired(state: web::Data<AppState>, user: AuthenticatedUser) -> impl Responder {
373    let organization_id = match user.require_organization() {
374        Ok(org_id) => org_id,
375        Err(e) => {
376            return HttpResponse::Unauthorized().json(serde_json::json!({
377                "error": e.to_string()
378            }))
379        }
380    };
381
382    match state
383        .etat_date_use_cases
384        .list_expired(organization_id)
385        .await
386    {
387        Ok(etats) => HttpResponse::Ok().json(etats),
388        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
389            "error": err
390        })),
391    }
392}
393
394/// Get statistics for dashboard
395#[get("/etats-dates/stats")]
396pub async fn get_stats(state: web::Data<AppState>, user: AuthenticatedUser) -> impl Responder {
397    let organization_id = match user.require_organization() {
398        Ok(org_id) => org_id,
399        Err(e) => {
400            return HttpResponse::Unauthorized().json(serde_json::json!({
401                "error": e.to_string()
402            }))
403        }
404    };
405
406    match state.etat_date_use_cases.get_stats(organization_id).await {
407        Ok(stats) => HttpResponse::Ok().json(stats),
408        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
409            "error": err
410        })),
411    }
412}
413
414/// Delete état daté
415#[delete("/etats-dates/{id}")]
416pub async fn delete_etat_date(
417    state: web::Data<AppState>,
418    user: AuthenticatedUser,
419    id: web::Path<Uuid>,
420) -> impl Responder {
421    match state.etat_date_use_cases.delete_etat_date(*id).await {
422        Ok(true) => {
423            AuditLogEntry::new(
424                AuditEventType::EtatDateDeleted,
425                Some(user.user_id),
426                user.organization_id,
427            )
428            .with_resource("EtatDate", *id)
429            .log();
430
431            HttpResponse::NoContent().finish()
432        }
433        Ok(false) => HttpResponse::NotFound().json(serde_json::json!({
434            "error": "État daté not found"
435        })),
436        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
437            "error": err
438        })),
439    }
440}