koprogo_api/infrastructure/web/handlers/
board_decision_handlers.rs

1use crate::application::dto::{
2    AddDecisionNotesDto, CreateBoardDecisionDto, UpdateBoardDecisionDto,
3};
4use crate::infrastructure::audit::{AuditEventType, AuditLogEntry};
5use crate::infrastructure::web::{AppState, AuthenticatedUser};
6use actix_web::{get, post, put, web, HttpResponse, Responder};
7use uuid::Uuid;
8
9/// Créer une nouvelle décision à suivre suite à une AG
10#[post("/board-decisions")]
11pub async fn create_decision(
12    state: web::Data<AppState>,
13    user: AuthenticatedUser,
14    request: web::Json<CreateBoardDecisionDto>,
15) -> impl Responder {
16    let organization_id = match user.require_organization() {
17        Ok(org_id) => org_id,
18        Err(e) => {
19            return HttpResponse::Unauthorized().json(serde_json::json!({
20                "error": e.to_string()
21            }))
22        }
23    };
24
25    match state
26        .board_decision_use_cases
27        .create_decision(request.into_inner())
28        .await
29    {
30        Ok(decision) => {
31            // Audit log: successful decision creation
32            if let Ok(decision_uuid) = Uuid::parse_str(&decision.id) {
33                AuditLogEntry::new(
34                    AuditEventType::BoardDecisionCreated,
35                    Some(user.user_id),
36                    Some(organization_id),
37                )
38                .with_resource("BoardDecision", decision_uuid)
39                .log();
40            }
41
42            HttpResponse::Created().json(decision)
43        }
44        Err(err) => {
45            // Audit log: failed decision creation
46            AuditLogEntry::new(
47                AuditEventType::BoardDecisionCreated,
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/// Récupérer une décision par ID
62#[get("/board-decisions/{id}")]
63pub async fn get_decision(
64    state: web::Data<AppState>,
65    user: AuthenticatedUser,
66    id: web::Path<Uuid>,
67) -> impl Responder {
68    let _organization_id = match user.require_organization() {
69        Ok(org_id) => org_id,
70        Err(e) => {
71            return HttpResponse::Unauthorized().json(serde_json::json!({
72                "error": e.to_string()
73            }))
74        }
75    };
76
77    match state.board_decision_use_cases.get_decision(*id).await {
78        Ok(decision) => HttpResponse::Ok().json(decision),
79        Err(err) => HttpResponse::NotFound().json(serde_json::json!({
80            "error": err
81        })),
82    }
83}
84
85/// Lister toutes les décisions d'un immeuble
86#[get("/buildings/{building_id}/board-decisions")]
87pub async fn list_decisions_by_building(
88    state: web::Data<AppState>,
89    user: AuthenticatedUser,
90    building_id: web::Path<Uuid>,
91) -> impl Responder {
92    let _organization_id = match user.require_organization() {
93        Ok(org_id) => org_id,
94        Err(e) => {
95            return HttpResponse::Unauthorized().json(serde_json::json!({
96                "error": e.to_string()
97            }))
98        }
99    };
100
101    match state
102        .board_decision_use_cases
103        .list_decisions_by_building(*building_id)
104        .await
105    {
106        Ok(decisions) => HttpResponse::Ok().json(decisions),
107        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
108            "error": err
109        })),
110    }
111}
112
113/// Lister les décisions par statut pour un immeuble
114#[get("/buildings/{building_id}/board-decisions/status/{status}")]
115pub async fn list_decisions_by_status(
116    state: web::Data<AppState>,
117    user: AuthenticatedUser,
118    path: web::Path<(Uuid, String)>,
119) -> impl Responder {
120    let (building_id, status) = path.into_inner();
121
122    let _organization_id = match user.require_organization() {
123        Ok(org_id) => org_id,
124        Err(e) => {
125            return HttpResponse::Unauthorized().json(serde_json::json!({
126                "error": e.to_string()
127            }))
128        }
129    };
130
131    match state
132        .board_decision_use_cases
133        .list_decisions_by_status(building_id, &status)
134        .await
135    {
136        Ok(decisions) => HttpResponse::Ok().json(decisions),
137        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({
138            "error": err
139        })),
140    }
141}
142
143/// Lister les décisions en retard pour un immeuble
144#[get("/buildings/{building_id}/board-decisions/overdue")]
145pub async fn list_overdue_decisions(
146    state: web::Data<AppState>,
147    user: AuthenticatedUser,
148    building_id: web::Path<Uuid>,
149) -> impl Responder {
150    let _organization_id = match user.require_organization() {
151        Ok(org_id) => org_id,
152        Err(e) => {
153            return HttpResponse::Unauthorized().json(serde_json::json!({
154                "error": e.to_string()
155            }))
156        }
157    };
158
159    match state
160        .board_decision_use_cases
161        .list_overdue_decisions(*building_id)
162        .await
163    {
164        Ok(decisions) => HttpResponse::Ok().json(decisions),
165        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
166            "error": err
167        })),
168    }
169}
170
171/// Mettre à jour le statut d'une décision
172#[put("/board-decisions/{id}")]
173pub async fn update_decision_status(
174    state: web::Data<AppState>,
175    user: AuthenticatedUser,
176    id: web::Path<Uuid>,
177    request: web::Json<UpdateBoardDecisionDto>,
178) -> impl Responder {
179    let organization_id = match user.require_organization() {
180        Ok(org_id) => org_id,
181        Err(e) => {
182            return HttpResponse::Unauthorized().json(serde_json::json!({
183                "error": e.to_string()
184            }))
185        }
186    };
187
188    match state
189        .board_decision_use_cases
190        .update_decision_status(*id, request.into_inner())
191        .await
192    {
193        Ok(decision) => {
194            // Audit log: successful decision update
195            if let Ok(decision_uuid) = Uuid::parse_str(&decision.id) {
196                AuditLogEntry::new(
197                    AuditEventType::BoardDecisionUpdated,
198                    Some(user.user_id),
199                    Some(organization_id),
200                )
201                .with_resource("BoardDecision", decision_uuid)
202                .log();
203            }
204
205            HttpResponse::Ok().json(decision)
206        }
207        Err(err) => {
208            // Audit log: failed decision update
209            AuditLogEntry::new(
210                AuditEventType::BoardDecisionUpdated,
211                Some(user.user_id),
212                Some(organization_id),
213            )
214            .with_error(err.clone())
215            .log();
216
217            HttpResponse::BadRequest().json(serde_json::json!({
218                "error": err
219            }))
220        }
221    }
222}
223
224/// Ajouter des notes à une décision
225#[post("/board-decisions/{id}/notes")]
226pub async fn add_notes(
227    state: web::Data<AppState>,
228    user: AuthenticatedUser,
229    id: web::Path<Uuid>,
230    request: web::Json<AddDecisionNotesDto>,
231) -> impl Responder {
232    let organization_id = match user.require_organization() {
233        Ok(org_id) => org_id,
234        Err(e) => {
235            return HttpResponse::Unauthorized().json(serde_json::json!({
236                "error": e.to_string()
237            }))
238        }
239    };
240
241    match state
242        .board_decision_use_cases
243        .add_notes(*id, request.into_inner())
244        .await
245    {
246        Ok(decision) => {
247            // Audit log: successful notes addition
248            if let Ok(decision_uuid) = Uuid::parse_str(&decision.id) {
249                AuditLogEntry::new(
250                    AuditEventType::BoardDecisionNotesAdded,
251                    Some(user.user_id),
252                    Some(organization_id),
253                )
254                .with_resource("BoardDecision", decision_uuid)
255                .log();
256            }
257
258            HttpResponse::Ok().json(decision)
259        }
260        Err(err) => {
261            // Audit log: failed notes addition
262            AuditLogEntry::new(
263                AuditEventType::BoardDecisionNotesAdded,
264                Some(user.user_id),
265                Some(organization_id),
266            )
267            .with_error(err.clone())
268            .log();
269
270            HttpResponse::BadRequest().json(serde_json::json!({
271                "error": err
272            }))
273        }
274    }
275}
276
277/// Marquer une décision comme complétée
278#[put("/board-decisions/{id}/complete")]
279pub async fn complete_decision(
280    state: web::Data<AppState>,
281    user: AuthenticatedUser,
282    id: web::Path<Uuid>,
283) -> impl Responder {
284    let organization_id = match user.require_organization() {
285        Ok(org_id) => org_id,
286        Err(e) => {
287            return HttpResponse::Unauthorized().json(serde_json::json!({
288                "error": e.to_string()
289            }))
290        }
291    };
292
293    match state.board_decision_use_cases.complete_decision(*id).await {
294        Ok(decision) => {
295            // Audit log: successful decision completion
296            if let Ok(decision_uuid) = Uuid::parse_str(&decision.id) {
297                AuditLogEntry::new(
298                    AuditEventType::BoardDecisionCompleted,
299                    Some(user.user_id),
300                    Some(organization_id),
301                )
302                .with_resource("BoardDecision", decision_uuid)
303                .log();
304            }
305
306            HttpResponse::Ok().json(decision)
307        }
308        Err(err) => {
309            // Audit log: failed decision completion
310            AuditLogEntry::new(
311                AuditEventType::BoardDecisionCompleted,
312                Some(user.user_id),
313                Some(organization_id),
314            )
315            .with_error(err.clone())
316            .log();
317
318            HttpResponse::BadRequest().json(serde_json::json!({
319                "error": err
320            }))
321        }
322    }
323}
324
325/// Obtenir des statistiques sur les décisions d'un immeuble
326#[get("/buildings/{building_id}/board-decisions/stats")]
327pub async fn get_decision_stats(
328    state: web::Data<AppState>,
329    user: AuthenticatedUser,
330    building_id: web::Path<Uuid>,
331) -> impl Responder {
332    let _organization_id = match user.require_organization() {
333        Ok(org_id) => org_id,
334        Err(e) => {
335            return HttpResponse::Unauthorized().json(serde_json::json!({
336                "error": e.to_string()
337            }))
338        }
339    };
340
341    match state
342        .board_decision_use_cases
343        .get_decision_stats(*building_id)
344        .await
345    {
346        Ok(stats) => HttpResponse::Ok().json(stats),
347        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
348            "error": err
349        })),
350    }
351}