koprogo_api/infrastructure/web/handlers/
age_request_handlers.rs

1use crate::application::dto::age_request_dto::{
2    AddCosignatoryDto, CreateAgeRequestDto, SyndicResponseDto,
3};
4use crate::infrastructure::web::{AppState, AuthenticatedUser};
5use actix_web::{delete, get, post, put, web, HttpResponse, Responder};
6use uuid::Uuid;
7
8/// POST /buildings/:id/age-requests — Créer une demande d'AGE
9#[post("/buildings/{building_id}/age-requests")]
10pub async fn create_age_request(
11    state: web::Data<AppState>,
12    user: AuthenticatedUser,
13    path: web::Path<Uuid>,
14    body: web::Json<CreateAgeRequestDtoWithoutBuildingId>,
15) -> impl Responder {
16    let organization_id = match user.require_organization() {
17        Ok(id) => id,
18        Err(e) => {
19            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
20        }
21    };
22
23    let building_id = path.into_inner();
24    let dto = CreateAgeRequestDto {
25        building_id,
26        title: body.title.clone(),
27        description: body.description.clone(),
28    };
29
30    match state
31        .age_request_use_cases
32        .create(organization_id, user.user_id, dto)
33        .await
34    {
35        Ok(req) => HttpResponse::Created().json(req),
36        Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
37    }
38}
39
40/// GET /buildings/:id/age-requests — Lister les demandes d'AGE d'un bâtiment
41#[get("/buildings/{building_id}/age-requests")]
42pub async fn list_age_requests(
43    state: web::Data<AppState>,
44    user: AuthenticatedUser,
45    path: web::Path<Uuid>,
46) -> impl Responder {
47    let organization_id = match user.require_organization() {
48        Ok(id) => id,
49        Err(e) => {
50            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
51        }
52    };
53
54    match state
55        .age_request_use_cases
56        .list_by_building(path.into_inner(), organization_id)
57        .await
58    {
59        Ok(reqs) => HttpResponse::Ok().json(reqs),
60        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
61    }
62}
63
64/// GET /age-requests/:id — Détail d'une demande d'AGE
65#[get("/age-requests/{id}")]
66pub async fn get_age_request(
67    state: web::Data<AppState>,
68    user: AuthenticatedUser,
69    path: web::Path<Uuid>,
70) -> impl Responder {
71    let organization_id = match user.require_organization() {
72        Ok(id) => id,
73        Err(e) => {
74            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
75        }
76    };
77
78    match state
79        .age_request_use_cases
80        .get(path.into_inner(), organization_id)
81        .await
82    {
83        Ok(req) => HttpResponse::Ok().json(req),
84        Err(e) => {
85            if e.contains("introuvable") {
86                HttpResponse::NotFound().json(serde_json::json!({"error": e}))
87            } else if e.contains("refusé") {
88                HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
89            } else {
90                HttpResponse::InternalServerError().json(serde_json::json!({"error": e}))
91            }
92        }
93    }
94}
95
96/// PUT /age-requests/:id/open — Ouvrir la demande pour signatures
97#[put("/age-requests/{id}/open")]
98pub async fn open_age_request(
99    state: web::Data<AppState>,
100    user: AuthenticatedUser,
101    path: web::Path<Uuid>,
102) -> impl Responder {
103    let organization_id = match user.require_organization() {
104        Ok(id) => id,
105        Err(e) => {
106            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
107        }
108    };
109
110    match state
111        .age_request_use_cases
112        .open(path.into_inner(), organization_id, user.user_id)
113        .await
114    {
115        Ok(req) => HttpResponse::Ok().json(req),
116        Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
117    }
118}
119
120/// POST /age-requests/:id/cosignatories — Ajouter un cosignataire
121#[post("/age-requests/{id}/cosignatories")]
122pub async fn add_cosignatory(
123    state: web::Data<AppState>,
124    user: AuthenticatedUser,
125    path: web::Path<Uuid>,
126    body: web::Json<AddCosignatoryDto>,
127) -> impl Responder {
128    let organization_id = match user.require_organization() {
129        Ok(id) => id,
130        Err(e) => {
131            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
132        }
133    };
134
135    match state
136        .age_request_use_cases
137        .add_cosignatory(path.into_inner(), organization_id, body.into_inner())
138        .await
139    {
140        Ok(req) => HttpResponse::Ok().json(req),
141        Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
142    }
143}
144
145/// DELETE /age-requests/:id/cosignatories/:owner_id — Retirer un cosignataire
146#[delete("/age-requests/{id}/cosignatories/{owner_id}")]
147pub async fn remove_cosignatory(
148    state: web::Data<AppState>,
149    user: AuthenticatedUser,
150    path: web::Path<(Uuid, Uuid)>,
151) -> impl Responder {
152    let organization_id = match user.require_organization() {
153        Ok(id) => id,
154        Err(e) => {
155            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
156        }
157    };
158
159    let (id, owner_id) = path.into_inner();
160    match state
161        .age_request_use_cases
162        .remove_cosignatory(id, owner_id, organization_id)
163        .await
164    {
165        Ok(req) => HttpResponse::Ok().json(req),
166        Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
167    }
168}
169
170/// POST /age-requests/:id/submit — Soumettre la demande au syndic
171#[post("/age-requests/{id}/submit")]
172pub async fn submit_age_request(
173    state: web::Data<AppState>,
174    user: AuthenticatedUser,
175    path: web::Path<Uuid>,
176) -> impl Responder {
177    let organization_id = match user.require_organization() {
178        Ok(id) => id,
179        Err(e) => {
180            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
181        }
182    };
183
184    match state
185        .age_request_use_cases
186        .submit_to_syndic(path.into_inner(), organization_id, user.user_id)
187        .await
188    {
189        Ok(req) => HttpResponse::Ok().json(req),
190        Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
191    }
192}
193
194/// PUT /age-requests/:id/syndic-response — Réponse du syndic (accept/reject)
195#[put("/age-requests/{id}/syndic-response")]
196pub async fn syndic_response(
197    state: web::Data<AppState>,
198    user: AuthenticatedUser,
199    path: web::Path<Uuid>,
200    body: web::Json<SyndicResponseDto>,
201) -> impl Responder {
202    let organization_id = match user.require_organization() {
203        Ok(id) => id,
204        Err(e) => {
205            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
206        }
207    };
208
209    match state
210        .age_request_use_cases
211        .syndic_response(path.into_inner(), organization_id, body.into_inner())
212        .await
213    {
214        Ok(req) => HttpResponse::Ok().json(req),
215        Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
216    }
217}
218
219/// POST /age-requests/:id/auto-convocation — Déclencher l'auto-convocation
220/// (si délai syndic dépassé, Art. 3.87 §2 CC)
221#[post("/age-requests/{id}/auto-convocation")]
222pub async fn trigger_auto_convocation(
223    state: web::Data<AppState>,
224    user: AuthenticatedUser,
225    path: web::Path<Uuid>,
226) -> impl Responder {
227    let organization_id = match user.require_organization() {
228        Ok(id) => id,
229        Err(e) => {
230            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
231        }
232    };
233
234    match state
235        .age_request_use_cases
236        .trigger_auto_convocation(path.into_inner(), organization_id)
237        .await
238    {
239        Ok(req) => HttpResponse::Ok().json(req),
240        Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
241    }
242}
243
244/// POST /age-requests/:id/withdraw — Retirer la demande
245#[post("/age-requests/{id}/withdraw")]
246pub async fn withdraw_age_request(
247    state: web::Data<AppState>,
248    user: AuthenticatedUser,
249    path: web::Path<Uuid>,
250) -> impl Responder {
251    let organization_id = match user.require_organization() {
252        Ok(id) => id,
253        Err(e) => {
254            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
255        }
256    };
257
258    match state
259        .age_request_use_cases
260        .withdraw(path.into_inner(), organization_id, user.user_id)
261        .await
262    {
263        Ok(req) => HttpResponse::Ok().json(req),
264        Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
265    }
266}
267
268/// DELETE /age-requests/:id — Supprimer une demande (Draft/Withdrawn seulement)
269#[delete("/age-requests/{id}")]
270pub async fn delete_age_request(
271    state: web::Data<AppState>,
272    user: AuthenticatedUser,
273    path: web::Path<Uuid>,
274) -> impl Responder {
275    let organization_id = match user.require_organization() {
276        Ok(id) => id,
277        Err(e) => {
278            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
279        }
280    };
281
282    match state
283        .age_request_use_cases
284        .delete(path.into_inner(), organization_id, user.user_id)
285        .await
286    {
287        Ok(()) => HttpResponse::NoContent().finish(),
288        Err(e) => {
289            if e.contains("introuvable") {
290                HttpResponse::NotFound().json(serde_json::json!({"error": e}))
291            } else if e.contains("refusé") {
292                HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
293            } else {
294                HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
295            }
296        }
297    }
298}
299
300/// DTO de création sans le building_id (qui vient du path)
301#[derive(serde::Deserialize)]
302pub struct CreateAgeRequestDtoWithoutBuildingId {
303    pub title: String,
304    pub description: Option<String>,
305}