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