Skip to main content

koprogo_api/application/ports/
expense_repository.rs

1use crate::application::dto::{ExpenseFilters, PageRequest};
2use crate::domain::entities::Expense;
3use async_trait::async_trait;
4use rust_decimal::Decimal;
5use uuid::Uuid;
6
7#[async_trait]
8pub trait ExpenseRepository: Send + Sync {
9    async fn create(&self, expense: &Expense) -> Result<Expense, String>;
10
11    /// Enregistre le détail d'une facture saisie ligne par ligne.
12    ///
13    /// Pas de méthode par défaut, et c'est délibéré : une implémentation par
14    /// défaut qui ne ferait rien reproduirait exactement le défaut qu'on
15    /// corrige — accepter la donnée et la perdre en silence. Chaque
16    /// implémentation doit dire ce qu'elle en fait, quitte à dire qu'elle
17    /// n'en fait rien parce qu'elle est un mock.
18    async fn enregistrer_lignes_de_facture(
19        &self,
20        expense_id: Uuid,
21        lignes: &[LigneDeFacture],
22    ) -> Result<(), String>;
23    async fn find_by_id(&self, id: Uuid) -> Result<Option<Expense>, String>;
24    async fn find_by_building(&self, building_id: Uuid) -> Result<Vec<Expense>, String>;
25
26    /// Find all expenses with pagination and filters
27    /// Returns tuple of (expenses, total_count)
28    async fn find_all_paginated(
29        &self,
30        page_request: &PageRequest,
31        filters: &ExpenseFilters,
32    ) -> Result<(Vec<Expense>, i64), String>;
33
34    async fn update(&self, expense: &Expense) -> Result<Expense, String>;
35    async fn delete(&self, id: Uuid) -> Result<bool, String>;
36}
37
38/// Une ligne de facture, telle que le domaine la connaît.
39///
40/// Les montants dérivés — hors TVA, TVA, TTC — sont calculés à partir de la
41/// quantité, du prix unitaire et du taux, jamais transmis par l'appelant :
42/// un client qui enverrait un total incohérent avec ses composants ferait
43/// mentir la facture.
44#[derive(Debug, Clone, PartialEq)]
45pub struct LigneDeFacture {
46    pub description: String,
47    pub quantity: Decimal,
48    pub unit_price: Decimal,
49    pub vat_rate: Decimal,
50}
51
52impl LigneDeFacture {
53    /// Le montant hors TVA : quantité × prix unitaire.
54    pub fn montant_hors_tva(&self) -> Decimal {
55        self.quantity * self.unit_price
56    }
57
58    /// La TVA due sur la ligne.
59    ///
60    /// Multiplication avant division, comme partout où un `Decimal` traverse
61    /// une fraction : diviser d'abord laisse une traîne d'arrondi qui finit
62    /// par déséquilibrer un total.
63    pub fn montant_tva(&self) -> Decimal {
64        self.montant_hors_tva() * self.vat_rate / Decimal::from(100)
65    }
66
67    /// Le montant TVA comprise.
68    pub fn montant_tva_comprise(&self) -> Decimal {
69        self.montant_hors_tva() + self.montant_tva()
70    }
71}