koprogo_api/application/use_cases/
building_use_cases.rs1use crate::application::dto::{
2 BuildingFilters, BuildingResponseDto, CreateBuildingDto, PageRequest, UpdateBuildingDto,
3};
4use crate::application::ports::BuildingRepository;
5use crate::domain::entities::Building;
6use std::sync::Arc;
7use uuid::Uuid;
8
9pub struct BuildingUseCases {
10 repository: Arc<dyn BuildingRepository>,
11}
12
13impl BuildingUseCases {
14 pub fn new(repository: Arc<dyn BuildingRepository>) -> Self {
15 Self { repository }
16 }
17
18 pub async fn create_building(
19 &self,
20 dto: CreateBuildingDto,
21 ) -> Result<BuildingResponseDto, String> {
22 let organization_id = Uuid::parse_str(&dto.organization_id)
23 .map_err(|_| "Invalid organization_id format".to_string())?;
24
25 let building = Building::new(
26 organization_id,
27 dto.name,
28 dto.address,
29 dto.city,
30 dto.postal_code,
31 dto.country,
32 dto.total_units,
33 dto.total_tantiemes.unwrap_or(1000),
34 dto.construction_year,
35 )?;
36
37 let created = self.repository.create(&building).await?;
38 Ok(self.to_response_dto(&created))
39 }
40
41 pub async fn get_building(&self, id: Uuid) -> Result<Option<BuildingResponseDto>, String> {
42 let building = self.repository.find_by_id(id).await?;
43 Ok(building.map(|b| self.to_response_dto(&b)))
44 }
45
46 pub async fn list_buildings(&self) -> Result<Vec<BuildingResponseDto>, String> {
47 let buildings = self.repository.find_all().await?;
48 Ok(buildings.iter().map(|b| self.to_response_dto(b)).collect())
49 }
50
51 pub async fn list_buildings_paginated(
52 &self,
53 page_request: &PageRequest,
54 organization_id: Option<Uuid>,
55 ) -> Result<(Vec<BuildingResponseDto>, i64), String> {
56 let filters = BuildingFilters {
57 organization_id,
58 ..Default::default()
59 };
60
61 let (buildings, total) = self
62 .repository
63 .find_all_paginated(page_request, &filters)
64 .await?;
65
66 let dtos = buildings.iter().map(|b| self.to_response_dto(b)).collect();
67 Ok((dtos, total))
68 }
69
70 pub async fn list_buildings_paginated_for_user(
73 &self,
74 page_request: &PageRequest,
75 organization_id: Option<Uuid>,
76 owner_user_id: Option<Uuid>,
77 ) -> Result<(Vec<BuildingResponseDto>, i64), String> {
78 let filters = BuildingFilters {
79 organization_id,
80 owner_user_id,
81 ..Default::default()
82 };
83
84 let (buildings, total) = self
85 .repository
86 .find_all_paginated(page_request, &filters)
87 .await?;
88
89 let dtos = buildings.iter().map(|b| self.to_response_dto(b)).collect();
90 Ok((dtos, total))
91 }
92
93 pub async fn update_building(
94 &self,
95 id: Uuid,
96 dto: UpdateBuildingDto,
97 ) -> Result<BuildingResponseDto, String> {
98 let mut building = self
99 .repository
100 .find_by_id(id)
101 .await?
102 .ok_or_else(|| "Building not found".to_string())?;
103
104 if let Some(org_id_str) = dto.organization_id {
106 let org_id = Uuid::parse_str(&org_id_str)
107 .map_err(|_| "Invalid organization_id format".to_string())?;
108 building.organization_id = org_id;
109 }
110
111 building.update_info(
112 dto.name,
113 dto.address,
114 dto.city,
115 dto.postal_code,
116 dto.country,
117 dto.total_units,
118 dto.total_tantiemes.unwrap_or(1000),
119 dto.construction_year,
120 );
121
122 let updated = self.repository.update(&building).await?;
123 Ok(self.to_response_dto(&updated))
124 }
125
126 pub async fn delete_building(&self, id: Uuid) -> Result<bool, String> {
127 self.repository.delete(id).await
128 }
129
130 pub async fn find_by_slug(&self, slug: &str) -> Result<Option<Building>, String> {
132 self.repository.find_by_slug(slug).await
133 }
134
135 fn to_response_dto(&self, building: &Building) -> BuildingResponseDto {
136 BuildingResponseDto {
137 id: building.id.to_string(),
138 organization_id: building.organization_id.to_string(),
139 name: building.name.clone(),
140 address: building.address.clone(),
141 city: building.city.clone(),
142 postal_code: building.postal_code.clone(),
143 country: building.country.clone(),
144 total_units: building.total_units,
145 total_tantiemes: building.total_tantiemes,
146 construction_year: building.construction_year,
147 created_at: building.created_at.to_rfc3339(),
148 updated_at: building.updated_at.to_rfc3339(),
149 }
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use crate::application::ports::BuildingRepository;
157 use async_trait::async_trait;
158 use mockall::mock;
159
160 mock! {
161 BuildingRepo {}
162
163 #[async_trait]
164 impl BuildingRepository for BuildingRepo {
165 async fn create(&self, building: &Building) -> Result<Building, String>;
166 async fn find_by_id(&self, id: Uuid) -> Result<Option<Building>, String>;
167 async fn find_all(&self) -> Result<Vec<Building>, String>;
168 async fn find_all_paginated(
169 &self,
170 page_request: &PageRequest,
171 filters: &BuildingFilters,
172 ) -> Result<(Vec<Building>, i64), String>;
173 async fn update(&self, building: &Building) -> Result<Building, String>;
174 async fn delete(&self, id: Uuid) -> Result<bool, String>;
175 async fn find_by_slug(&self, slug: &str) -> Result<Option<Building>, String>;
176 }
177 }
178
179 #[tokio::test]
180 async fn test_create_building_success() {
181 let mut mock_repo = MockBuildingRepo::new();
182
183 mock_repo.expect_create().returning(|b| Ok(b.clone()));
184
185 let use_cases = BuildingUseCases::new(Arc::new(mock_repo));
186
187 let dto = CreateBuildingDto {
188 organization_id: Uuid::new_v4().to_string(),
189 name: "Test Building".to_string(),
190 address: "123 Test St".to_string(),
191 city: "Paris".to_string(),
192 postal_code: "75001".to_string(),
193 country: "France".to_string(),
194 total_units: 10,
195 total_tantiemes: Some(1000),
196 construction_year: Some(2000),
197 };
198
199 let result = use_cases.create_building(dto).await;
200 assert!(result.is_ok());
201 }
202
203 #[tokio::test]
204 async fn test_create_building_validation_fails() {
205 let mock_repo = MockBuildingRepo::new();
206 let use_cases = BuildingUseCases::new(Arc::new(mock_repo));
207
208 let dto = CreateBuildingDto {
209 organization_id: Uuid::new_v4().to_string(),
210 name: "".to_string(), address: "123 Test St".to_string(),
212 city: "Paris".to_string(),
213 postal_code: "75001".to_string(),
214 country: "France".to_string(),
215 total_units: 10,
216 total_tantiemes: Some(1000),
217 construction_year: Some(2000),
218 };
219
220 let result = use_cases.create_building(dto).await;
221 assert!(result.is_err());
222 }
223}