koprogo_api/application/use_cases/
owner_contribution_use_cases.rs

1use crate::application::ports::OwnerContributionRepository;
2use crate::domain::entities::{ContributionPaymentMethod, ContributionType, OwnerContribution};
3use chrono::{DateTime, Utc};
4use std::sync::Arc;
5use uuid::Uuid;
6
7pub struct OwnerContributionUseCases {
8    repository: Arc<dyn OwnerContributionRepository>,
9}
10
11impl OwnerContributionUseCases {
12    pub fn new(repository: Arc<dyn OwnerContributionRepository>) -> Self {
13        Self { repository }
14    }
15
16    /// Create a new owner contribution (appel de fonds)
17    #[allow(clippy::too_many_arguments)]
18    pub async fn create_contribution(
19        &self,
20        organization_id: Uuid,
21        owner_id: Uuid,
22        unit_id: Option<Uuid>,
23        description: String,
24        amount: f64,
25        contribution_type: ContributionType,
26        contribution_date: DateTime<Utc>,
27        account_code: Option<String>,
28    ) -> Result<OwnerContribution, String> {
29        // Create domain entity (validates business rules)
30        let contribution = OwnerContribution::new(
31            organization_id,
32            owner_id,
33            unit_id,
34            description,
35            amount,
36            contribution_type,
37            contribution_date,
38            account_code,
39        )?;
40
41        // Persist
42        self.repository.create(&contribution).await
43    }
44
45    /// Record payment for a contribution
46    pub async fn record_payment(
47        &self,
48        contribution_id: Uuid,
49        payment_date: DateTime<Utc>,
50        payment_method: ContributionPaymentMethod,
51        payment_reference: Option<String>,
52    ) -> Result<OwnerContribution, String> {
53        // Find contribution
54        let mut contribution = self
55            .repository
56            .find_by_id(contribution_id)
57            .await?
58            .ok_or_else(|| format!("Contribution not found: {}", contribution_id))?;
59
60        // Mark as paid (domain logic)
61        contribution.mark_as_paid(payment_date, payment_method, payment_reference);
62
63        // Update
64        self.repository.update(&contribution).await
65    }
66
67    /// Get contribution by ID
68    pub async fn get_contribution(
69        &self,
70        contribution_id: Uuid,
71    ) -> Result<Option<OwnerContribution>, String> {
72        self.repository.find_by_id(contribution_id).await
73    }
74
75    /// Get all contributions for an organization
76    pub async fn get_contributions_by_organization(
77        &self,
78        organization_id: Uuid,
79    ) -> Result<Vec<OwnerContribution>, String> {
80        self.repository.find_by_organization(organization_id).await
81    }
82
83    /// Get all contributions for an owner
84    pub async fn get_contributions_by_owner(
85        &self,
86        owner_id: Uuid,
87    ) -> Result<Vec<OwnerContribution>, String> {
88        self.repository.find_by_owner(owner_id).await
89    }
90
91    /// Get outstanding (unpaid) contributions for an owner
92    pub async fn get_outstanding_contributions(
93        &self,
94        owner_id: Uuid,
95    ) -> Result<Vec<OwnerContribution>, String> {
96        let contributions = self.repository.find_by_owner(owner_id).await?;
97
98        // Filter unpaid
99        Ok(contributions.into_iter().filter(|c| !c.is_paid()).collect())
100    }
101
102    /// Get overdue contributions for an owner
103    pub async fn get_overdue_contributions(
104        &self,
105        owner_id: Uuid,
106    ) -> Result<Vec<OwnerContribution>, String> {
107        let contributions = self.repository.find_by_owner(owner_id).await?;
108
109        // Filter overdue
110        Ok(contributions
111            .into_iter()
112            .filter(|c| c.is_overdue())
113            .collect())
114    }
115
116    /// Get total outstanding amount for an owner
117    pub async fn get_outstanding_amount(&self, owner_id: Uuid) -> Result<f64, String> {
118        let outstanding = self.get_outstanding_contributions(owner_id).await?;
119        Ok(outstanding.iter().map(|c| c.amount).sum())
120    }
121}