koprogo_api/application/use_cases/
expense_use_cases.rs1use 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}