koprogo_api/application/use_cases/
call_for_funds_use_cases.rs1use crate::application::ports::{
2 CallForFundsRepository, OwnerContributionRepository, UnitOwnerRepository,
3};
4use crate::domain::entities::{CallForFunds, ContributionType, OwnerContribution};
5use chrono::{DateTime, Utc};
6use std::sync::Arc;
7use uuid::Uuid;
8
9pub struct CallForFundsUseCases {
10 call_for_funds_repository: Arc<dyn CallForFundsRepository>,
11 owner_contribution_repository: Arc<dyn OwnerContributionRepository>,
12 unit_owner_repository: Arc<dyn UnitOwnerRepository>,
13}
14
15impl CallForFundsUseCases {
16 pub fn new(
17 call_for_funds_repository: Arc<dyn CallForFundsRepository>,
18 owner_contribution_repository: Arc<dyn OwnerContributionRepository>,
19 unit_owner_repository: Arc<dyn UnitOwnerRepository>,
20 ) -> Self {
21 Self {
22 call_for_funds_repository,
23 owner_contribution_repository,
24 unit_owner_repository,
25 }
26 }
27
28 #[allow(clippy::too_many_arguments)]
30 pub async fn create_call_for_funds(
31 &self,
32 organization_id: Uuid,
33 building_id: Uuid,
34 title: String,
35 description: String,
36 total_amount: f64,
37 contribution_type: ContributionType,
38 call_date: DateTime<Utc>,
39 due_date: DateTime<Utc>,
40 account_code: Option<String>,
41 created_by: Option<Uuid>,
42 ) -> Result<CallForFunds, String> {
43 let mut call_for_funds = CallForFunds::new(
45 organization_id,
46 building_id,
47 title,
48 description,
49 total_amount,
50 contribution_type.clone(),
51 call_date,
52 due_date,
53 account_code,
54 )?;
55
56 call_for_funds.created_by = created_by;
57
58 self.call_for_funds_repository.create(&call_for_funds).await
60 }
61
62 pub async fn get_call_for_funds(&self, id: Uuid) -> Result<Option<CallForFunds>, String> {
64 self.call_for_funds_repository.find_by_id(id).await
65 }
66
67 pub async fn list_by_building(&self, building_id: Uuid) -> Result<Vec<CallForFunds>, String> {
69 self.call_for_funds_repository
70 .find_by_building(building_id)
71 .await
72 }
73
74 pub async fn list_by_organization(
76 &self,
77 organization_id: Uuid,
78 ) -> Result<Vec<CallForFunds>, String> {
79 self.call_for_funds_repository
80 .find_by_organization(organization_id)
81 .await
82 }
83
84 pub async fn send_call_for_funds(&self, id: Uuid) -> Result<CallForFunds, String> {
87 let mut call_for_funds = self
89 .call_for_funds_repository
90 .find_by_id(id)
91 .await?
92 .ok_or_else(|| "Call for funds not found".to_string())?;
93
94 call_for_funds.mark_as_sent();
96
97 let updated_call = self
99 .call_for_funds_repository
100 .update(&call_for_funds)
101 .await?;
102
103 self.generate_owner_contributions(&updated_call).await?;
105
106 Ok(updated_call)
107 }
108
109 async fn generate_owner_contributions(
111 &self,
112 call_for_funds: &CallForFunds,
113 ) -> Result<Vec<OwnerContribution>, String> {
114 let unit_owners = self
117 .unit_owner_repository
118 .find_active_by_building(call_for_funds.building_id)
119 .await?;
120
121 if unit_owners.is_empty() {
122 return Err("No active owners found for this building".to_string());
123 }
124
125 let mut contributions = Vec::new();
126
127 for (unit_id, owner_id, percentage) in unit_owners {
128 let individual_amount = call_for_funds.total_amount * percentage;
130
131 let description = format!(
133 "{} - Quote-part: {:.2}%",
134 call_for_funds.title,
135 percentage * 100.0
136 );
137
138 let mut contribution = OwnerContribution::new(
140 call_for_funds.organization_id,
141 owner_id,
142 Some(unit_id),
143 description,
144 individual_amount,
145 call_for_funds.contribution_type.clone(),
146 call_for_funds.call_date,
147 call_for_funds.account_code.clone(),
148 )?;
149
150 contribution.call_for_funds_id = Some(call_for_funds.id);
152
153 let saved = self
155 .owner_contribution_repository
156 .create(&contribution)
157 .await?;
158
159 contributions.push(saved);
160 }
161
162 Ok(contributions)
163 }
164
165 pub async fn cancel_call_for_funds(&self, id: Uuid) -> Result<CallForFunds, String> {
167 let mut call_for_funds = self
168 .call_for_funds_repository
169 .find_by_id(id)
170 .await?
171 .ok_or_else(|| "Call for funds not found".to_string())?;
172
173 call_for_funds.cancel();
174
175 self.call_for_funds_repository.update(&call_for_funds).await
176 }
177
178 pub async fn get_overdue_calls(&self) -> Result<Vec<CallForFunds>, String> {
180 self.call_for_funds_repository.find_overdue().await
181 }
182
183 pub async fn delete_call_for_funds(&self, id: Uuid) -> Result<bool, String> {
185 let call_for_funds = self
186 .call_for_funds_repository
187 .find_by_id(id)
188 .await?
189 .ok_or_else(|| "Call for funds not found".to_string())?;
190
191 if call_for_funds.status != crate::domain::entities::CallForFundsStatus::Draft {
193 return Err("Cannot delete a call for funds that has been sent".to_string());
194 }
195
196 self.call_for_funds_repository.delete(id).await
197 }
198}