Skip to main content

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