koprogo_api/infrastructure/database/repositories/
unit_owner_repository_impl.rs1use crate::application::ports::UnitOwnerRepository;
2use crate::domain::entities::UnitOwner;
3use async_trait::async_trait;
4use sqlx::PgPool;
5use uuid::Uuid;
6
7pub struct PostgresUnitOwnerRepository {
8 pool: PgPool,
9}
10
11impl PostgresUnitOwnerRepository {
12 pub fn new(pool: PgPool) -> Self {
13 Self { pool }
14 }
15}
16
17#[async_trait]
18impl UnitOwnerRepository for PostgresUnitOwnerRepository {
19 async fn create(&self, unit_owner: &UnitOwner) -> Result<UnitOwner, String> {
20 let result = sqlx::query!(
21 r#"
22 INSERT INTO unit_owners (
23 id, unit_id, owner_id, ownership_percentage,
24 start_date, end_date, is_primary_contact,
25 created_at, updated_at
26 )
27 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
28 RETURNING *
29 "#,
30 unit_owner.id,
31 unit_owner.unit_id,
32 unit_owner.owner_id,
33 unit_owner.ownership_percentage,
34 unit_owner.start_date,
35 unit_owner.end_date,
36 unit_owner.is_primary_contact,
37 unit_owner.created_at,
38 unit_owner.updated_at
39 )
40 .fetch_one(&self.pool)
41 .await
42 .map_err(|e| format!("Failed to create unit_owner: {}", e))?;
43
44 Ok(UnitOwner {
45 id: result.id,
46 unit_id: result.unit_id,
47 owner_id: result.owner_id,
48 ownership_percentage: result.ownership_percentage,
49 start_date: result.start_date,
50 end_date: result.end_date,
51 is_primary_contact: result.is_primary_contact,
52 created_at: result.created_at,
53 updated_at: result.updated_at,
54 })
55 }
56
57 async fn find_by_id(&self, id: Uuid) -> Result<Option<UnitOwner>, String> {
58 let result = sqlx::query!(
59 r#"
60 SELECT * FROM unit_owners WHERE id = $1
61 "#,
62 id
63 )
64 .fetch_optional(&self.pool)
65 .await
66 .map_err(|e| format!("Failed to find unit_owner: {}", e))?;
67
68 Ok(result.map(|row| UnitOwner {
69 id: row.id,
70 unit_id: row.unit_id,
71 owner_id: row.owner_id,
72 ownership_percentage: row.ownership_percentage,
73 start_date: row.start_date,
74 end_date: row.end_date,
75 is_primary_contact: row.is_primary_contact,
76 created_at: row.created_at,
77 updated_at: row.updated_at,
78 }))
79 }
80
81 async fn find_current_owners_by_unit(&self, unit_id: Uuid) -> Result<Vec<UnitOwner>, String> {
82 let results = sqlx::query!(
83 r#"
84 SELECT * FROM unit_owners
85 WHERE unit_id = $1 AND end_date IS NULL
86 ORDER BY is_primary_contact DESC, created_at ASC
87 "#,
88 unit_id
89 )
90 .fetch_all(&self.pool)
91 .await
92 .map_err(|e| format!("Failed to find owners by unit: {}", e))?;
93
94 Ok(results
95 .into_iter()
96 .map(|row| UnitOwner {
97 id: row.id,
98 unit_id: row.unit_id,
99 owner_id: row.owner_id,
100 ownership_percentage: row.ownership_percentage,
101 start_date: row.start_date,
102 end_date: row.end_date,
103 is_primary_contact: row.is_primary_contact,
104 created_at: row.created_at,
105 updated_at: row.updated_at,
106 })
107 .collect())
108 }
109
110 async fn find_current_units_by_owner(&self, owner_id: Uuid) -> Result<Vec<UnitOwner>, String> {
111 let results = sqlx::query!(
112 r#"
113 SELECT * FROM unit_owners
114 WHERE owner_id = $1 AND end_date IS NULL
115 ORDER BY created_at ASC
116 "#,
117 owner_id
118 )
119 .fetch_all(&self.pool)
120 .await
121 .map_err(|e| format!("Failed to find units by owner: {}", e))?;
122
123 Ok(results
124 .into_iter()
125 .map(|row| UnitOwner {
126 id: row.id,
127 unit_id: row.unit_id,
128 owner_id: row.owner_id,
129 ownership_percentage: row.ownership_percentage,
130 start_date: row.start_date,
131 end_date: row.end_date,
132 is_primary_contact: row.is_primary_contact,
133 created_at: row.created_at,
134 updated_at: row.updated_at,
135 })
136 .collect())
137 }
138
139 async fn find_all_owners_by_unit(&self, unit_id: Uuid) -> Result<Vec<UnitOwner>, String> {
140 let results = sqlx::query!(
141 r#"
142 SELECT * FROM unit_owners
143 WHERE unit_id = $1
144 ORDER BY start_date DESC
145 "#,
146 unit_id
147 )
148 .fetch_all(&self.pool)
149 .await
150 .map_err(|e| format!("Failed to find all owners by unit: {}", e))?;
151
152 Ok(results
153 .into_iter()
154 .map(|row| UnitOwner {
155 id: row.id,
156 unit_id: row.unit_id,
157 owner_id: row.owner_id,
158 ownership_percentage: row.ownership_percentage,
159 start_date: row.start_date,
160 end_date: row.end_date,
161 is_primary_contact: row.is_primary_contact,
162 created_at: row.created_at,
163 updated_at: row.updated_at,
164 })
165 .collect())
166 }
167
168 async fn find_all_units_by_owner(&self, owner_id: Uuid) -> Result<Vec<UnitOwner>, String> {
169 let results = sqlx::query!(
170 r#"
171 SELECT * FROM unit_owners
172 WHERE owner_id = $1
173 ORDER BY start_date DESC
174 "#,
175 owner_id
176 )
177 .fetch_all(&self.pool)
178 .await
179 .map_err(|e| format!("Failed to find all units by owner: {}", e))?;
180
181 Ok(results
182 .into_iter()
183 .map(|row| UnitOwner {
184 id: row.id,
185 unit_id: row.unit_id,
186 owner_id: row.owner_id,
187 ownership_percentage: row.ownership_percentage,
188 start_date: row.start_date,
189 end_date: row.end_date,
190 is_primary_contact: row.is_primary_contact,
191 created_at: row.created_at,
192 updated_at: row.updated_at,
193 })
194 .collect())
195 }
196
197 async fn update(&self, unit_owner: &UnitOwner) -> Result<UnitOwner, String> {
198 let result = sqlx::query!(
199 r#"
200 UPDATE unit_owners
201 SET ownership_percentage = $2,
202 end_date = $3,
203 is_primary_contact = $4,
204 updated_at = $5
205 WHERE id = $1
206 RETURNING *
207 "#,
208 unit_owner.id,
209 unit_owner.ownership_percentage,
210 unit_owner.end_date,
211 unit_owner.is_primary_contact,
212 unit_owner.updated_at
213 )
214 .fetch_one(&self.pool)
215 .await
216 .map_err(|e| format!("Failed to update unit_owner: {}", e))?;
217
218 Ok(UnitOwner {
219 id: result.id,
220 unit_id: result.unit_id,
221 owner_id: result.owner_id,
222 ownership_percentage: result.ownership_percentage,
223 start_date: result.start_date,
224 end_date: result.end_date,
225 is_primary_contact: result.is_primary_contact,
226 created_at: result.created_at,
227 updated_at: result.updated_at,
228 })
229 }
230
231 async fn delete(&self, id: Uuid) -> Result<(), String> {
232 sqlx::query!(
233 r#"
234 DELETE FROM unit_owners WHERE id = $1
235 "#,
236 id
237 )
238 .execute(&self.pool)
239 .await
240 .map_err(|e| format!("Failed to delete unit_owner: {}", e))?;
241
242 Ok(())
243 }
244
245 async fn has_active_owners(&self, unit_id: Uuid) -> Result<bool, String> {
246 let result = sqlx::query!(
247 r#"
248 SELECT EXISTS(SELECT 1 FROM unit_owners WHERE unit_id = $1 AND end_date IS NULL) as "exists!"
249 "#,
250 unit_id
251 )
252 .fetch_one(&self.pool)
253 .await
254 .map_err(|e| format!("Failed to check active owners: {}", e))?;
255
256 Ok(result.exists)
257 }
258
259 async fn get_total_ownership_percentage(&self, unit_id: Uuid) -> Result<f64, String> {
260 let result = sqlx::query!(
261 r#"
262 SELECT COALESCE(SUM(ownership_percentage), 0) as "total!"
263 FROM unit_owners
264 WHERE unit_id = $1 AND end_date IS NULL
265 "#,
266 unit_id
267 )
268 .fetch_one(&self.pool)
269 .await
270 .map_err(|e| format!("Failed to get total ownership percentage: {}", e))?;
271
272 Ok(result.total)
273 }
274
275 async fn find_active_by_unit_and_owner(
276 &self,
277 unit_id: Uuid,
278 owner_id: Uuid,
279 ) -> Result<Option<UnitOwner>, String> {
280 let result = sqlx::query!(
281 r#"
282 SELECT * FROM unit_owners
283 WHERE unit_id = $1 AND owner_id = $2 AND end_date IS NULL
284 "#,
285 unit_id,
286 owner_id
287 )
288 .fetch_optional(&self.pool)
289 .await
290 .map_err(|e| format!("Failed to find active unit_owner: {}", e))?;
291
292 Ok(result.map(|row| UnitOwner {
293 id: row.id,
294 unit_id: row.unit_id,
295 owner_id: row.owner_id,
296 ownership_percentage: row.ownership_percentage,
297 start_date: row.start_date,
298 end_date: row.end_date,
299 is_primary_contact: row.is_primary_contact,
300 created_at: row.created_at,
301 updated_at: row.updated_at,
302 }))
303 }
304
305 async fn find_active_by_building(
306 &self,
307 building_id: Uuid,
308 ) -> Result<Vec<(Uuid, Uuid, f64)>, String> {
309 let results = sqlx::query!(
310 r#"
311 SELECT uo.unit_id, uo.owner_id, uo.ownership_percentage
312 FROM unit_owners uo
313 JOIN units u ON uo.unit_id = u.id
314 WHERE u.building_id = $1 AND uo.end_date IS NULL
315 "#,
316 building_id
317 )
318 .fetch_all(&self.pool)
319 .await
320 .map_err(|e| format!("Failed to find active unit_owners by building: {}", e))?;
321
322 Ok(results
323 .into_iter()
324 .map(|row| (row.unit_id, row.owner_id, row.ownership_percentage))
325 .collect())
326 }
327}