Skip to main content

koprogo_api/application/dto/
expense_dto.rs

1//! Expense DTOs — monetary fields use `rust_decimal::Decimal` (cf. ADR-0007).
2//!
3//! Note : `validator` crate `#[validate(range(...))]` ne support pas Decimal
4//! avec literals de type f64. Les invariants montants (> 0, taux VAT 0-100)
5//! sont enforced dans `Expense::new` / `Expense::new_with_vat` côté domaine
6//! (cf. `domain/entities/expense.rs`).
7
8use crate::domain::entities::{ApprovalStatus, ExpenseCategory, PaymentStatus};
9use rust_decimal::Decimal;
10use serde::{Deserialize, Serialize};
11use validator::Validate;
12
13// ========== Legacy DTOs (backward compatibility) ==========
14
15#[derive(Debug, Deserialize, Validate, Clone)]
16pub struct CreateExpenseDto {
17    #[serde(default)]
18    pub organization_id: String,
19    pub building_id: String,
20    pub category: ExpenseCategory,
21
22    #[validate(length(min = 1))]
23    pub description: String,
24
25    /// Montant TTC (validé > 0 dans Expense::new). Decimal exact (cf. ADR-0007).
26    pub amount: Decimal,
27
28    pub expense_date: String,
29    pub supplier: Option<String>,
30    pub invoice_number: Option<String>,
31
32    /// Optional Belgian PCMN account code (e.g., "604001" for electricity)
33    /// Must reference an existing account in the organization's chart of accounts
34    #[validate(length(max = 40))]
35    pub account_code: Option<String>,
36}
37
38#[derive(Debug, Serialize)]
39pub struct ExpenseResponseDto {
40    pub id: String,
41    pub building_id: String,
42    pub category: ExpenseCategory,
43    pub description: String,
44    pub amount: Decimal,
45    pub expense_date: String,
46    pub payment_status: PaymentStatus,
47    pub approval_status: ApprovalStatus,
48    pub supplier: Option<String>,
49    pub invoice_number: Option<String>,
50    /// Belgian PCMN account code if linked to chart of accounts
51    pub account_code: Option<String>,
52    /// Contractor report reference for Works category (Issue #309)
53    pub contractor_report_id: Option<String>,
54}
55
56// ========== New Invoice DTOs (with VAT & Workflow) ==========
57
58/// Créer une facture brouillon avec gestion TVA.
59/// Validation des montants > 0 et taux 0-100 effectuée dans `Expense::new_with_vat`.
60#[derive(Debug, Deserialize, Validate, Clone)]
61pub struct CreateInvoiceDraftDto {
62    #[serde(default)]
63    pub organization_id: String,
64    pub building_id: String,
65    pub category: ExpenseCategory,
66
67    #[validate(length(min = 1))]
68    pub description: String,
69
70    /// Montant HT (validé > 0 dans `Expense::new_with_vat`).
71    pub amount_excl_vat: Decimal,
72
73    /// Taux TVA en % (validé 0..=100 dans `Expense::new_with_vat`).
74    pub vat_rate: Decimal,
75
76    pub invoice_date: String,     // ISO 8601
77    pub due_date: Option<String>, // ISO 8601
78    pub supplier: Option<String>,
79    pub invoice_number: Option<String>,
80}
81
82/// Modifier une facture brouillon ou rejetée.
83#[derive(Debug, Deserialize, Validate, Clone)]
84pub struct UpdateInvoiceDraftDto {
85    #[validate(length(min = 1))]
86    pub description: Option<String>,
87
88    pub category: Option<ExpenseCategory>,
89
90    pub amount_excl_vat: Option<Decimal>,
91    pub vat_rate: Option<Decimal>,
92
93    pub invoice_date: Option<String>,
94    pub due_date: Option<String>,
95    pub supplier: Option<String>,
96    pub invoice_number: Option<String>,
97}
98
99/// Soumettre une facture pour validation (Draft → PendingApproval).
100#[derive(Debug, Deserialize, Clone)]
101pub struct SubmitForApprovalDto {
102    // Empty body, action via PUT /invoices/:id/submit
103}
104
105/// Approuver une facture (PendingApproval → Approved).
106#[derive(Debug, Deserialize, Clone)]
107pub struct ApproveInvoiceDto {
108    pub approved_by_user_id: String, // User ID du syndic/admin
109}
110
111/// Rejeter une facture avec raison (PendingApproval → Rejected).
112#[derive(Debug, Deserialize, Validate, Clone)]
113pub struct RejectInvoiceDto {
114    pub rejected_by_user_id: String,
115
116    #[validate(length(min = 1))]
117    pub rejection_reason: String,
118}
119
120/// Créer une ligne de facture.
121/// Validations (quantity > 0, unit_price ≥ 0, vat_rate 0..=100) dans `InvoiceLineItem::new`.
122#[derive(Debug, Deserialize, Validate, Clone)]
123pub struct CreateInvoiceLineItemDto {
124    pub expense_id: String,
125
126    #[validate(length(min = 1))]
127    pub description: String,
128
129    pub quantity: Decimal,
130    pub unit_price: Decimal,
131    pub vat_rate: Decimal,
132}
133
134// ========== Response DTOs ==========
135
136/// Response enrichie avec tous les champs invoice/workflow.
137#[derive(Debug, Serialize, Clone)]
138pub struct InvoiceResponseDto {
139    pub id: String,
140    pub organization_id: String,
141    pub building_id: String,
142    pub category: ExpenseCategory,
143    pub description: String,
144
145    // Montants — exact decimal (cf. ADR-0007)
146    pub amount: Decimal, // TTC (backward compatibility)
147    pub amount_excl_vat: Option<Decimal>,
148    pub vat_rate: Option<Decimal>,
149    pub vat_amount: Option<Decimal>,
150    pub amount_incl_vat: Option<Decimal>,
151
152    // Dates
153    pub expense_date: String,
154    pub invoice_date: Option<String>,
155    pub due_date: Option<String>,
156    pub paid_date: Option<String>,
157
158    // Workflow
159    pub approval_status: ApprovalStatus,
160    pub submitted_at: Option<String>,
161    pub approved_by: Option<String>,
162    pub approved_at: Option<String>,
163    pub rejection_reason: Option<String>,
164
165    // Payment
166    pub payment_status: PaymentStatus,
167    pub supplier: Option<String>,
168    pub invoice_number: Option<String>,
169
170    /// Contractor report reference for Works category (Issue #309)
171    pub contractor_report_id: Option<String>,
172
173    pub created_at: String,
174    pub updated_at: String,
175}
176
177/// Response pour une ligne de facture.
178#[derive(Debug, Serialize)]
179pub struct InvoiceLineItemResponseDto {
180    pub id: String,
181    pub expense_id: String,
182    pub description: String,
183    pub quantity: Decimal,
184    pub unit_price: Decimal,
185    pub amount_excl_vat: Decimal,
186    pub vat_rate: Decimal,
187    pub vat_amount: Decimal,
188    pub amount_incl_vat: Decimal,
189    pub created_at: String,
190}
191
192/// Response pour une répartition de charge.
193#[derive(Debug, Serialize)]
194pub struct ChargeDistributionResponseDto {
195    pub id: String,
196    pub expense_id: String,
197    pub unit_id: String,
198    pub owner_id: String,
199    /// Quote-part (e.g., dec!(0.25) pour 25%). Decimal exact (cf. ADR-0007).
200    pub quota_percentage: Decimal,
201    pub amount_due: Decimal,
202    pub created_at: String,
203}
204
205/// Liste des factures en attente d'approbation (pour syndics).
206#[derive(Debug, Serialize)]
207pub struct PendingInvoicesListDto {
208    pub invoices: Vec<InvoiceResponseDto>,
209    pub count: usize,
210}