koprogo_api/application/use_cases/
expense_use_cases.rs

1use crate::application::dto::{CreateExpenseDto, ExpenseFilters, ExpenseResponseDto, PageRequest};
2use crate::application::ports::ExpenseRepository;
3use crate::domain::entities::Expense;
4use chrono::DateTime;
5use std::sync::Arc;
6use uuid::Uuid;
7
8pub struct ExpenseUseCases {
9    repository: Arc<dyn ExpenseRepository>,
10}
11
12impl ExpenseUseCases {
13    pub fn new(repository: Arc<dyn ExpenseRepository>) -> Self {
14        Self { repository }
15    }
16
17    pub async fn create_expense(
18        &self,
19        dto: CreateExpenseDto,
20    ) -> Result<ExpenseResponseDto, String> {
21        let organization_id = Uuid::parse_str(&dto.organization_id)
22            .map_err(|_| "Invalid organization_id format".to_string())?;
23        let building_id = Uuid::parse_str(&dto.building_id)
24            .map_err(|_| "Invalid building ID format".to_string())?;
25
26        let expense_date = DateTime::parse_from_rfc3339(&dto.expense_date)
27            .map_err(|_| "Invalid date format".to_string())?
28            .with_timezone(&chrono::Utc);
29
30        let expense = Expense::new(
31            organization_id,
32            building_id,
33            dto.category,
34            dto.description,
35            dto.amount,
36            expense_date,
37            dto.supplier,
38            dto.invoice_number,
39        )?;
40
41        let created = self.repository.create(&expense).await?;
42        Ok(self.to_response_dto(&created))
43    }
44
45    pub async fn get_expense(&self, id: Uuid) -> Result<Option<ExpenseResponseDto>, String> {
46        let expense = self.repository.find_by_id(id).await?;
47        Ok(expense.map(|e| self.to_response_dto(&e)))
48    }
49
50    pub async fn list_expenses_by_building(
51        &self,
52        building_id: Uuid,
53    ) -> Result<Vec<ExpenseResponseDto>, String> {
54        let expenses = self.repository.find_by_building(building_id).await?;
55        Ok(expenses.iter().map(|e| self.to_response_dto(e)).collect())
56    }
57
58    pub async fn list_expenses_paginated(
59        &self,
60        page_request: &PageRequest,
61        organization_id: Option<Uuid>,
62    ) -> Result<(Vec<ExpenseResponseDto>, i64), String> {
63        let filters = ExpenseFilters {
64            organization_id,
65            ..Default::default()
66        };
67
68        let (expenses, total) = self
69            .repository
70            .find_all_paginated(page_request, &filters)
71            .await?;
72
73        let dtos = expenses.iter().map(|e| self.to_response_dto(e)).collect();
74        Ok((dtos, total))
75    }
76
77    pub async fn mark_as_paid(&self, id: Uuid) -> Result<ExpenseResponseDto, String> {
78        let mut expense = self
79            .repository
80            .find_by_id(id)
81            .await?
82            .ok_or_else(|| "Expense not found".to_string())?;
83
84        expense.mark_as_paid();
85
86        let updated = self.repository.update(&expense).await?;
87        Ok(self.to_response_dto(&updated))
88    }
89
90    fn to_response_dto(&self, expense: &Expense) -> ExpenseResponseDto {
91        ExpenseResponseDto {
92            id: expense.id.to_string(),
93            building_id: expense.building_id.to_string(),
94            category: expense.category.clone(),
95            description: expense.description.clone(),
96            amount: expense.amount,
97            expense_date: expense.expense_date.to_rfc3339(),
98            payment_status: expense.payment_status.clone(),
99            supplier: expense.supplier.clone(),
100            invoice_number: expense.invoice_number.clone(),
101        }
102    }
103}