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