koprogo_api/infrastructure/database/repositories/
unit_owner_repository_impl.rs

1use 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}