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 update_building(
71 &self,
72 id: Uuid,
73 dto: UpdateBuildingDto,
74 ) -> Result<BuildingResponseDto, String> {
75 let mut building = self
76 .repository
77 .find_by_id(id)
78 .await?
79 .ok_or_else(|| "Building not found".to_string())?;
80
81 if let Some(org_id_str) = dto.organization_id {
83 let org_id = Uuid::parse_str(&org_id_str)
84 .map_err(|_| "Invalid organization_id format".to_string())?;
85 building.organization_id = org_id;
86 }
87
88 building.update_info(
89 dto.name,
90 dto.address,
91 dto.city,
92 dto.postal_code,
93 dto.country,
94 dto.total_units,
95 dto.total_tantiemes.unwrap_or(1000),
96 dto.construction_year,
97 );
98
99 let updated = self.repository.update(&building).await?;
100 Ok(self.to_response_dto(&updated))
101 }
102
103 pub async fn delete_building(&self, id: Uuid) -> Result<bool, String> {
104 self.repository.delete(id).await
105 }
106
107 pub async fn find_by_slug(&self, slug: &str) -> Result<Option<Building>, String> {
109 self.repository.find_by_slug(slug).await
110 }
111
112 fn to_response_dto(&self, building: &Building) -> BuildingResponseDto {
113 BuildingResponseDto {
114 id: building.id.to_string(),
115 organization_id: building.organization_id.to_string(),
116 name: building.name.clone(),
117 address: building.address.clone(),
118 city: building.city.clone(),
119 postal_code: building.postal_code.clone(),
120 country: building.country.clone(),
121 total_units: building.total_units,
122 total_tantiemes: building.total_tantiemes,
123 construction_year: building.construction_year,
124 created_at: building.created_at.to_rfc3339(),
125 updated_at: building.updated_at.to_rfc3339(),
126 }
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use crate::application::ports::BuildingRepository;
134 use async_trait::async_trait;
135 use mockall::mock;
136
137 mock! {
138 BuildingRepo {}
139
140 #[async_trait]
141 impl BuildingRepository for BuildingRepo {
142 async fn create(&self, building: &Building) -> Result<Building, String>;
143 async fn find_by_id(&self, id: Uuid) -> Result<Option<Building>, String>;
144 async fn find_all(&self) -> Result<Vec<Building>, String>;
145 async fn find_all_paginated(
146 &self,
147 page_request: &PageRequest,
148 filters: &BuildingFilters,
149 ) -> Result<(Vec<Building>, i64), String>;
150 async fn update(&self, building: &Building) -> Result<Building, String>;
151 async fn delete(&self, id: Uuid) -> Result<bool, String>;
152 async fn find_by_slug(&self, slug: &str) -> Result<Option<Building>, String>;
153 }
154 }
155
156 #[tokio::test]
157 async fn test_create_building_success() {
158 let mut mock_repo = MockBuildingRepo::new();
159
160 mock_repo.expect_create().returning(|b| Ok(b.clone()));
161
162 let use_cases = BuildingUseCases::new(Arc::new(mock_repo));
163
164 let dto = CreateBuildingDto {
165 organization_id: Uuid::new_v4().to_string(),
166 name: "Test Building".to_string(),
167 address: "123 Test St".to_string(),
168 city: "Paris".to_string(),
169 postal_code: "75001".to_string(),
170 country: "France".to_string(),
171 total_units: 10,
172 total_tantiemes: Some(1000),
173 construction_year: Some(2000),
174 };
175
176 let result = use_cases.create_building(dto).await;
177 assert!(result.is_ok());
178 }
179
180 #[tokio::test]
181 async fn test_create_building_validation_fails() {
182 let mock_repo = MockBuildingRepo::new();
183 let use_cases = BuildingUseCases::new(Arc::new(mock_repo));
184
185 let dto = CreateBuildingDto {
186 organization_id: Uuid::new_v4().to_string(),
187 name: "".to_string(), address: "123 Test St".to_string(),
189 city: "Paris".to_string(),
190 postal_code: "75001".to_string(),
191 country: "France".to_string(),
192 total_units: 10,
193 total_tantiemes: Some(1000),
194 construction_year: Some(2000),
195 };
196
197 let result = use_cases.create_building(dto).await;
198 assert!(result.is_err());
199 }
200}