Skip to main content

koprogo_api/infrastructure/web/handlers/
stats_handlers.rs

1use crate::infrastructure::web::{AppState, AuthenticatedUser};
2use actix_web::{get, web, HttpResponse, Responder};
3
4/// GET /api/v1/stats/dashboard
5/// Get dashboard statistics (SuperAdmin only)
6#[get("/stats/dashboard")]
7pub async fn get_dashboard_stats(
8    state: web::Data<AppState>,
9    user: AuthenticatedUser,
10) -> impl Responder {
11    if !user.is_superadmin() {
12        return HttpResponse::Forbidden().json(serde_json::json!({
13            "error": "Only SuperAdmin can access dashboard statistics"
14        }));
15    }
16
17    match state.stats_use_cases.get_admin_dashboard_stats().await {
18        Ok(stats) => HttpResponse::Ok().json(stats),
19        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({
20            "error": format!("Failed to fetch dashboard statistics: {}", e)
21        })),
22    }
23}
24
25/// GET /api/v1/stats/owner
26/// Get owner dashboard statistics (Owner role)
27#[get("/stats/owner")]
28pub async fn get_owner_stats(
29    state: web::Data<AppState>,
30    user: AuthenticatedUser,
31) -> impl Responder {
32    if user.role != "owner" && !user.is_superadmin() {
33        return HttpResponse::Forbidden().json(serde_json::json!({
34            "error": "Only Owner can access these statistics"
35        }));
36    }
37
38    match state
39        .stats_use_cases
40        .get_owner_stats_by_user_id(user.user_id)
41        .await
42    {
43        Ok(stats) => HttpResponse::Ok().json(stats),
44        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({
45            "error": format!("Failed to fetch owner dashboard statistics: {}", e)
46        })),
47    }
48}
49
50/// GET /api/v1/stats/owner/dues-by-acp
51///
52/// Ce que le copropriétaire doit, **ventilé par association**.
53///
54/// Séparée de `/stats/owner` parce qu'elle répond à une autre question : celle
55/// -là dit « combien », celle-ci dit « à QUI ». Un copropriétaire peut détenir
56/// des lots dans plusieurs ACP, et chacune est une personne morale avec son
57/// propre compte bancaire (Art. 3.86 § 1er et § 3).
58///
59/// Un montant global laisse croire qu'un virement unique suffit ; il paierait
60/// la mauvaise personne morale pour une partie de la somme (#867).
61#[get("/stats/owner/dues-by-acp")]
62pub async fn get_owner_dues_by_acp(
63    state: web::Data<AppState>,
64    user: AuthenticatedUser,
65) -> impl Responder {
66    // Même garde que `/stats/owner` : ce sont les dettes d'une personne
67    // nommée, et personne d'autre n'a à les lire.
68    if user.role != "owner" && !user.is_superadmin() {
69        return HttpResponse::Forbidden().json(serde_json::json!({
70            "error": "Only Owner can access these statistics"
71        }));
72    }
73
74    match state
75        .stats_use_cases
76        .get_owner_dues_by_acp(user.user_id)
77        .await
78    {
79        Ok(dues) => HttpResponse::Ok().json(dues),
80        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({
81            "error": format!("Failed to fetch owner dues by ACP: {}", e)
82        })),
83    }
84}
85
86/// GET /api/v1/stats/syndic
87/// Get Syndic dashboard statistics (Syndic and Accountant roles)
88#[get("/stats/syndic")]
89pub async fn get_syndic_stats(
90    state: web::Data<AppState>,
91    user: AuthenticatedUser,
92) -> impl Responder {
93    if user.role != "syndic" && user.role != "accountant" && !user.is_superadmin() {
94        return HttpResponse::Forbidden().json(serde_json::json!({
95            "error": "Only Syndic and Accountant can access these statistics"
96        }));
97    }
98
99    let Some(org_id) = user.organization_id else {
100        return HttpResponse::BadRequest().json(serde_json::json!({
101            "error": "User has no organization"
102        }));
103    };
104
105    match state.stats_use_cases.get_syndic_stats(org_id).await {
106        Ok(stats) => HttpResponse::Ok().json(stats),
107        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({
108            "error": format!("Failed to fetch syndic dashboard statistics: {}", e)
109        })),
110    }
111}
112
113/// GET /api/v1/stats/syndic/urgent-tasks
114/// Get urgent tasks for Syndic dashboard (Syndic and Accountant roles)
115#[get("/stats/syndic/urgent-tasks")]
116pub async fn get_syndic_urgent_tasks(
117    state: web::Data<AppState>,
118    user: AuthenticatedUser,
119) -> impl Responder {
120    if user.role != "syndic" && user.role != "accountant" && !user.is_superadmin() {
121        return HttpResponse::Forbidden().json(serde_json::json!({
122            "error": "Only Syndic and Accountant can access these tasks"
123        }));
124    }
125
126    let Some(org_id) = user.organization_id else {
127        return HttpResponse::BadRequest().json(serde_json::json!({
128            "error": "User has no organization"
129        }));
130    };
131
132    match state.stats_use_cases.get_syndic_urgent_tasks(org_id).await {
133        Ok(tasks) => HttpResponse::Ok().json(tasks),
134        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({
135            "error": format!("Failed to fetch urgent tasks: {}", e)
136        })),
137    }
138}
139
140/// GET /api/v1/stats/seed-data
141/// Get seed data statistics (SuperAdmin only)
142#[get("/stats/seed-data")]
143pub async fn get_seed_data_stats(
144    state: web::Data<AppState>,
145    user: AuthenticatedUser,
146) -> impl Responder {
147    if !user.is_superadmin() {
148        return HttpResponse::Forbidden().json(serde_json::json!({
149            "error": "Only SuperAdmin can access seed data statistics"
150        }));
151    }
152
153    match state.stats_use_cases.get_seed_data_stats().await {
154        Ok(stats) => HttpResponse::Ok().json(stats),
155        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({
156            "error": format!("Failed to fetch seed data statistics: {}", e)
157        })),
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    #[test]
164    fn test_role_check_superadmin_passes() {
165        let allowed_roles = ["superadmin"];
166        assert!(allowed_roles.contains(&"superadmin"));
167        assert!(!allowed_roles.contains(&"owner"));
168    }
169
170    #[test]
171    fn test_role_check_syndic_and_accountant() {
172        let allowed_roles = ["syndic", "accountant", "superadmin"];
173        assert!(allowed_roles.contains(&"syndic"));
174        assert!(allowed_roles.contains(&"accountant"));
175        assert!(!allowed_roles.contains(&"owner"));
176    }
177
178    #[test]
179    fn test_role_check_owner() {
180        let allowed_roles = ["owner", "superadmin"];
181        assert!(allowed_roles.contains(&"owner"));
182        assert!(!allowed_roles.contains(&"syndic"));
183    }
184}