koprogo_api/application/use_cases/
stats_use_cases.rs1use crate::application::dto::{
2 AdminDashboardStats, DuAupresDuneAcp, SeedDataStats, SyndicDashboardStats, UrgentTask,
3};
4use crate::application::error::AppError;
5use crate::application::ports::StatsRepository;
6use rust_decimal::Decimal;
7use std::sync::Arc;
8use uuid::Uuid;
9
10pub struct StatsUseCases {
11 repo: Arc<dyn StatsRepository>,
12}
13
14impl StatsUseCases {
15 pub fn new(repo: Arc<dyn StatsRepository>) -> Self {
16 Self { repo }
17 }
18
19 pub async fn get_admin_dashboard_stats(&self) -> Result<AdminDashboardStats, AppError> {
20 self.repo.get_admin_dashboard_stats().await
21 }
22
23 pub async fn get_seed_data_stats(&self) -> Result<SeedDataStats, AppError> {
24 self.repo.get_seed_data_stats().await
25 }
26
27 pub async fn get_syndic_stats(
28 &self,
29 organization_id: Uuid,
30 ) -> Result<SyndicDashboardStats, AppError> {
31 self.repo.get_syndic_stats(organization_id).await
32 }
33
34 pub async fn get_owner_stats_by_user_id(
36 &self,
37 user_id: Uuid,
38 ) -> Result<SyndicDashboardStats, AppError> {
39 match self.repo.find_owner_id_by_user_id(user_id).await? {
40 None => Ok(SyndicDashboardStats {
41 total_buildings: 0,
42 total_units: 0,
43 declared_units: 0,
44 total_owners: 0,
45 pending_expenses_count: 0,
46 pending_expenses_amount: Decimal::ZERO,
47 next_meeting: None,
48 }),
49 Some(owner_id) => self.repo.get_owner_stats(owner_id).await,
50 }
51 }
52
53 pub async fn get_owner_dues_by_acp(
60 &self,
61 user_id: Uuid,
62 ) -> Result<Vec<DuAupresDuneAcp>, AppError> {
63 match self.repo.find_owner_id_by_user_id(user_id).await? {
64 None => Ok(Vec::new()),
65 Some(owner_id) => self.repo.get_owner_dues_by_acp(owner_id).await,
66 }
67 }
68
69 pub async fn get_syndic_urgent_tasks(
70 &self,
71 organization_id: Uuid,
72 ) -> Result<Vec<UrgentTask>, AppError> {
73 self.repo.get_syndic_urgent_tasks(organization_id).await
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use async_trait::async_trait;
81 use rust_decimal_macros::dec;
82
83 struct MockStatsRepository {
84 owner_id: Option<Uuid>,
85 }
86
87 #[async_trait]
88 impl StatsRepository for MockStatsRepository {
89 async fn get_admin_dashboard_stats(&self) -> Result<AdminDashboardStats, AppError> {
90 Ok(AdminDashboardStats {
91 total_organizations: 5,
92 total_users: 50,
93 total_buildings: 10,
94 active_subscriptions: 4,
95 total_owners: 30,
96 total_units: 100,
97 total_expenses: 200,
98 total_meetings: 20,
99 })
100 }
101 async fn get_seed_data_stats(&self) -> Result<SeedDataStats, AppError> {
102 Ok(SeedDataStats {
103 seed_organizations: 1,
104 production_organizations: 4,
105 seed_buildings: 3,
106 seed_units: 15,
107 seed_owners: 10,
108 seed_unit_owners: 15,
109 seed_expenses: 20,
110 seed_meetings: 5,
111 seed_users: 8,
112 })
113 }
114 async fn get_syndic_stats(
115 &self,
116 _organization_id: Uuid,
117 ) -> Result<SyndicDashboardStats, AppError> {
118 Ok(SyndicDashboardStats {
119 total_buildings: 2,
120 total_units: 10,
121 declared_units: 12,
122 total_owners: 8,
123 pending_expenses_count: 3,
124 pending_expenses_amount: dec!(1500.00),
125 next_meeting: None,
126 })
127 }
128 async fn get_owner_dues_by_acp(
129 &self,
130 _owner_id: Uuid,
131 ) -> Result<Vec<crate::application::dto::DuAupresDuneAcp>, AppError> {
132 Ok(vec![
136 crate::application::dto::DuAupresDuneAcp {
137 acp_id: "acp-1".to_string(),
138 acp_name: "Les Érables".to_string(),
139 bce_number: Some("0123.456.789".to_string()),
140 charges_en_attente: 2,
141 montant: dec!(842.50),
142 },
143 crate::application::dto::DuAupresDuneAcp {
144 acp_id: "acp-2".to_string(),
145 acp_name: "Les Glycines".to_string(),
146 bce_number: Some("0987.654.321".to_string()),
147 charges_en_attente: 1,
148 montant: dec!(420.00),
149 },
150 ])
151 }
152
153 async fn get_owner_stats(&self, _owner_id: Uuid) -> Result<SyndicDashboardStats, AppError> {
154 Ok(SyndicDashboardStats {
155 total_buildings: 1,
156 total_units: 2,
157 declared_units: 2,
158 total_owners: 5,
159 pending_expenses_count: 1,
160 pending_expenses_amount: dec!(500.00),
161 next_meeting: None,
162 })
163 }
164 async fn find_owner_id_by_user_id(&self, _user_id: Uuid) -> Result<Option<Uuid>, AppError> {
165 Ok(self.owner_id)
166 }
167 async fn get_syndic_urgent_tasks(
168 &self,
169 _organization_id: Uuid,
170 ) -> Result<Vec<UrgentTask>, AppError> {
171 Ok(vec![])
172 }
173 }
174
175 #[tokio::test]
176 async fn test_get_admin_dashboard_stats() {
177 let repo = Arc::new(MockStatsRepository { owner_id: None });
178 let use_cases = StatsUseCases::new(repo);
179 let stats = use_cases.get_admin_dashboard_stats().await.unwrap();
180 assert_eq!(stats.total_organizations, 5);
181 assert_eq!(stats.total_buildings, 10);
182 }
183
184 #[tokio::test]
185 async fn test_get_owner_stats_no_owner_record_returns_empty() {
186 let repo = Arc::new(MockStatsRepository { owner_id: None });
187 let use_cases = StatsUseCases::new(repo);
188 let stats = use_cases
189 .get_owner_stats_by_user_id(Uuid::new_v4())
190 .await
191 .unwrap();
192 assert_eq!(stats.total_buildings, 0);
193 assert_eq!(stats.total_units, 0);
194 assert!(stats.next_meeting.is_none());
195 }
196
197 #[tokio::test]
198 async fn test_get_owner_stats_with_owner_record() {
199 let owner_id = Uuid::new_v4();
200 let repo = Arc::new(MockStatsRepository {
201 owner_id: Some(owner_id),
202 });
203 let use_cases = StatsUseCases::new(repo);
204 let stats = use_cases
205 .get_owner_stats_by_user_id(Uuid::new_v4())
206 .await
207 .unwrap();
208 assert_eq!(stats.total_buildings, 1);
209 assert_eq!(stats.pending_expenses_count, 1);
210 }
211}