koprogo_api/application/use_cases/
charge_distribution_use_cases.rs1use crate::application::dto::ChargeDistributionResponseDto;
2use crate::application::ports::{
3 ChargeDistributionRepository, ExpenseRepository, UnitOwnerRepository,
4};
5use crate::domain::entities::{ApprovalStatus, ChargeDistribution};
6use std::sync::Arc;
7use uuid::Uuid;
8
9pub struct ChargeDistributionUseCases {
10 distribution_repository: Arc<dyn ChargeDistributionRepository>,
11 expense_repository: Arc<dyn ExpenseRepository>,
12 unit_owner_repository: Arc<dyn UnitOwnerRepository>,
13}
14
15impl ChargeDistributionUseCases {
16 pub fn new(
17 distribution_repository: Arc<dyn ChargeDistributionRepository>,
18 expense_repository: Arc<dyn ExpenseRepository>,
19 unit_owner_repository: Arc<dyn UnitOwnerRepository>,
20 ) -> Self {
21 Self {
22 distribution_repository,
23 expense_repository,
24 unit_owner_repository,
25 }
26 }
27
28 pub async fn calculate_and_save_distribution(
30 &self,
31 expense_id: Uuid,
32 ) -> Result<Vec<ChargeDistributionResponseDto>, String> {
33 let expense = self
35 .expense_repository
36 .find_by_id(expense_id)
37 .await?
38 .ok_or_else(|| "Expense/Invoice not found".to_string())?;
39
40 if expense.approval_status != ApprovalStatus::Approved {
42 return Err(format!(
43 "Cannot calculate distribution for non-approved invoice (status: {:?})",
44 expense.approval_status
45 ));
46 }
47
48 let total_amount = expense.amount_incl_vat.unwrap_or(expense.amount);
50
51 let unit_ownerships = self
53 .unit_owner_repository
54 .find_active_by_building(expense.building_id)
55 .await?;
56
57 if unit_ownerships.is_empty() {
58 return Err("No active unit-owner relationships found for this building".to_string());
59 }
60
61 let distributions =
63 ChargeDistribution::calculate_distributions(expense_id, total_amount, unit_ownerships)?;
64
65 let saved_distributions = self
67 .distribution_repository
68 .create_bulk(&distributions)
69 .await?;
70
71 Ok(saved_distributions
73 .iter()
74 .map(|d| self.to_response_dto(d))
75 .collect())
76 }
77
78 pub async fn get_distribution_by_expense(
80 &self,
81 expense_id: Uuid,
82 ) -> Result<Vec<ChargeDistributionResponseDto>, String> {
83 let distributions = self
84 .distribution_repository
85 .find_by_expense(expense_id)
86 .await?;
87 Ok(distributions
88 .iter()
89 .map(|d| self.to_response_dto(d))
90 .collect())
91 }
92
93 pub async fn get_distributions_by_owner(
95 &self,
96 owner_id: Uuid,
97 ) -> Result<Vec<ChargeDistributionResponseDto>, String> {
98 let distributions = self.distribution_repository.find_by_owner(owner_id).await?;
99 Ok(distributions
100 .iter()
101 .map(|d| self.to_response_dto(d))
102 .collect())
103 }
104
105 pub async fn get_total_due_by_owner(&self, owner_id: Uuid) -> Result<f64, String> {
107 self.distribution_repository
108 .get_total_due_by_owner(owner_id)
109 .await
110 }
111
112 pub async fn delete_distribution_by_expense(&self, expense_id: Uuid) -> Result<(), String> {
114 self.distribution_repository
115 .delete_by_expense(expense_id)
116 .await
117 }
118
119 fn to_response_dto(&self, distribution: &ChargeDistribution) -> ChargeDistributionResponseDto {
120 ChargeDistributionResponseDto {
121 id: distribution.id.to_string(),
122 expense_id: distribution.expense_id.to_string(),
123 unit_id: distribution.unit_id.to_string(),
124 owner_id: distribution.owner_id.to_string(),
125 quota_percentage: distribution.quota_percentage,
126 amount_due: distribution.amount_due,
127 created_at: distribution.created_at.to_rfc3339(),
128 }
129 }
130}