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