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