koprogo_api/infrastructure/database/repositories/
owner_contribution_repository_impl.rs

1use crate::application::ports::OwnerContributionRepository;
2use crate::domain::entities::{
3    ContributionPaymentMethod, ContributionPaymentStatus, ContributionType, OwnerContribution,
4};
5use async_trait::async_trait;
6use sqlx::PgPool;
7use uuid::Uuid;
8
9pub struct PostgresOwnerContributionRepository {
10    pool: PgPool,
11}
12
13impl PostgresOwnerContributionRepository {
14    pub fn new(pool: PgPool) -> Self {
15        Self { pool }
16    }
17
18    // Helper to convert DB row to domain entity
19    fn row_to_entity(row: sqlx::postgres::PgRow) -> Result<OwnerContribution, String> {
20        use sqlx::Row;
21
22        let contribution_type_str: String = row.get("contribution_type");
23        let contribution_type = match contribution_type_str.as_str() {
24            "regular" => ContributionType::Regular,
25            "extraordinary" => ContributionType::Extraordinary,
26            "advance" => ContributionType::Advance,
27            "adjustment" => ContributionType::Adjustment,
28            _ => {
29                return Err(format!(
30                    "Unknown contribution type: {}",
31                    contribution_type_str
32                ))
33            }
34        };
35
36        let payment_status_str: String = row.get("payment_status");
37        let payment_status = match payment_status_str.as_str() {
38            "pending" => ContributionPaymentStatus::Pending,
39            "paid" => ContributionPaymentStatus::Paid,
40            "partial" => ContributionPaymentStatus::Partial,
41            "cancelled" => ContributionPaymentStatus::Cancelled,
42            _ => return Err(format!("Unknown payment status: {}", payment_status_str)),
43        };
44
45        let payment_method: Option<String> = row.get("payment_method");
46        let payment_method = payment_method
47            .map(|pm| match pm.as_str() {
48                "bank_transfer" => Ok(ContributionPaymentMethod::BankTransfer),
49                "cash" => Ok(ContributionPaymentMethod::Cash),
50                "check" => Ok(ContributionPaymentMethod::Check),
51                "domiciliation" => Ok(ContributionPaymentMethod::Domiciliation),
52                _ => Err(format!("Unknown payment method: {}", pm)),
53            })
54            .transpose()?;
55
56        let amount: sqlx::types::Decimal = row.get("amount");
57        let amount = amount
58            .to_string()
59            .parse::<f64>()
60            .map_err(|e| format!("Failed to parse amount: {}", e))?;
61
62        Ok(OwnerContribution {
63            id: row.get("id"),
64            organization_id: row.get("organization_id"),
65            owner_id: row.get("owner_id"),
66            unit_id: row.get("unit_id"),
67            description: row.get("description"),
68            amount,
69            account_code: row.get("account_code"),
70            contribution_type,
71            contribution_date: row.get("contribution_date"),
72            payment_date: row.get("payment_date"),
73            payment_method,
74            payment_reference: row.get("payment_reference"),
75            payment_status,
76            call_for_funds_id: row.get("call_for_funds_id"),
77            notes: row.get("notes"),
78            created_at: row.get("created_at"),
79            updated_at: row.get("updated_at"),
80            created_by: row.get("created_by"),
81        })
82    }
83}
84
85#[async_trait]
86impl OwnerContributionRepository for PostgresOwnerContributionRepository {
87    async fn create(&self, contribution: &OwnerContribution) -> Result<OwnerContribution, String> {
88        let contribution_type = match contribution.contribution_type {
89            ContributionType::Regular => "regular",
90            ContributionType::Extraordinary => "extraordinary",
91            ContributionType::Advance => "advance",
92            ContributionType::Adjustment => "adjustment",
93        };
94
95        let payment_status = match contribution.payment_status {
96            ContributionPaymentStatus::Pending => "pending",
97            ContributionPaymentStatus::Paid => "paid",
98            ContributionPaymentStatus::Partial => "partial",
99            ContributionPaymentStatus::Cancelled => "cancelled",
100        };
101
102        let payment_method = contribution.payment_method.as_ref().map(|pm| match pm {
103            ContributionPaymentMethod::BankTransfer => "bank_transfer",
104            ContributionPaymentMethod::Cash => "cash",
105            ContributionPaymentMethod::Check => "check",
106            ContributionPaymentMethod::Domiciliation => "domiciliation",
107        });
108
109        let row = sqlx::query(
110            r#"
111            INSERT INTO owner_contributions (
112                id, organization_id, owner_id, unit_id,
113                description, amount, account_code,
114                contribution_type, contribution_date, payment_date,
115                payment_method, payment_reference, payment_status,
116                call_for_funds_id, notes, created_at, updated_at, created_by
117            ) VALUES (
118                $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18
119            )
120            RETURNING *
121            "#,
122        )
123        .bind(contribution.id)
124        .bind(contribution.organization_id)
125        .bind(contribution.owner_id)
126        .bind(contribution.unit_id)
127        .bind(&contribution.description)
128        .bind(contribution.amount)
129        .bind(&contribution.account_code)
130        .bind(contribution_type)
131        .bind(contribution.contribution_date)
132        .bind(contribution.payment_date)
133        .bind(payment_method)
134        .bind(&contribution.payment_reference)
135        .bind(payment_status)
136        .bind(contribution.call_for_funds_id)
137        .bind(&contribution.notes)
138        .bind(contribution.created_at)
139        .bind(contribution.updated_at)
140        .bind(contribution.created_by)
141        .fetch_one(&self.pool)
142        .await
143        .map_err(|e| format!("Failed to create owner contribution: {}", e))?;
144
145        Self::row_to_entity(row)
146    }
147
148    async fn find_by_id(&self, id: Uuid) -> Result<Option<OwnerContribution>, String> {
149        let row = sqlx::query("SELECT * FROM owner_contributions WHERE id = $1")
150            .bind(id)
151            .fetch_optional(&self.pool)
152            .await
153            .map_err(|e| format!("Failed to find owner contribution by id: {}", e))?;
154
155        match row {
156            Some(r) => Ok(Some(Self::row_to_entity(r)?)),
157            None => Ok(None),
158        }
159    }
160
161    async fn find_by_organization(
162        &self,
163        organization_id: Uuid,
164    ) -> Result<Vec<OwnerContribution>, String> {
165        let rows = sqlx::query(
166            "SELECT * FROM owner_contributions WHERE organization_id = $1 ORDER BY contribution_date DESC",
167        )
168        .bind(organization_id)
169        .fetch_all(&self.pool)
170        .await
171        .map_err(|e| format!("Failed to find owner contributions by organization: {}", e))?;
172
173        rows.into_iter().map(Self::row_to_entity).collect()
174    }
175
176    async fn find_by_owner(&self, owner_id: Uuid) -> Result<Vec<OwnerContribution>, String> {
177        let rows = sqlx::query(
178            "SELECT * FROM owner_contributions WHERE owner_id = $1 ORDER BY contribution_date DESC",
179        )
180        .bind(owner_id)
181        .fetch_all(&self.pool)
182        .await
183        .map_err(|e| format!("Failed to find owner contributions by owner: {}", e))?;
184
185        rows.into_iter().map(Self::row_to_entity).collect()
186    }
187
188    async fn update(&self, contribution: &OwnerContribution) -> Result<OwnerContribution, String> {
189        let contribution_type = match contribution.contribution_type {
190            ContributionType::Regular => "regular",
191            ContributionType::Extraordinary => "extraordinary",
192            ContributionType::Advance => "advance",
193            ContributionType::Adjustment => "adjustment",
194        };
195
196        let payment_status = match contribution.payment_status {
197            ContributionPaymentStatus::Pending => "pending",
198            ContributionPaymentStatus::Paid => "paid",
199            ContributionPaymentStatus::Partial => "partial",
200            ContributionPaymentStatus::Cancelled => "cancelled",
201        };
202
203        let payment_method = contribution.payment_method.as_ref().map(|pm| match pm {
204            ContributionPaymentMethod::BankTransfer => "bank_transfer",
205            ContributionPaymentMethod::Cash => "cash",
206            ContributionPaymentMethod::Check => "check",
207            ContributionPaymentMethod::Domiciliation => "domiciliation",
208        });
209
210        let row = sqlx::query(
211            r#"
212            UPDATE owner_contributions SET
213                organization_id = $2,
214                owner_id = $3,
215                unit_id = $4,
216                description = $5,
217                amount = $6,
218                account_code = $7,
219                contribution_type = $8,
220                contribution_date = $9,
221                payment_date = $10,
222                payment_method = $11,
223                payment_reference = $12,
224                payment_status = $13,
225                notes = $14,
226                updated_at = $15,
227                created_by = $16
228            WHERE id = $1
229            RETURNING *
230            "#,
231        )
232        .bind(contribution.id)
233        .bind(contribution.organization_id)
234        .bind(contribution.owner_id)
235        .bind(contribution.unit_id)
236        .bind(&contribution.description)
237        .bind(contribution.amount)
238        .bind(&contribution.account_code)
239        .bind(contribution_type)
240        .bind(contribution.contribution_date)
241        .bind(contribution.payment_date)
242        .bind(payment_method)
243        .bind(&contribution.payment_reference)
244        .bind(payment_status)
245        .bind(&contribution.notes)
246        .bind(contribution.updated_at)
247        .bind(contribution.created_by)
248        .fetch_one(&self.pool)
249        .await
250        .map_err(|e| format!("Failed to update owner contribution: {}", e))?;
251
252        Self::row_to_entity(row)
253    }
254}