Skip to main content

koprogo_api/application/dto/
budget_dto.rs

1use crate::domain::entities::{Budget, BudgetStatus};
2use chrono::{DateTime, Utc};
3use rust_decimal::Decimal;
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7/// Request pour créer un nouveau budget
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct CreateBudgetRequest {
10    #[serde(default)]
11    pub organization_id: Uuid, // Will be overridden by JWT token
12    pub building_id: Uuid,
13    pub fiscal_year: i32,
14    pub ordinary_budget: Decimal,
15    pub extraordinary_budget: Decimal,
16    pub notes: Option<String>,
17}
18
19/// Request pour mettre à jour un budget (Draft uniquement)
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct UpdateBudgetRequest {
22    pub ordinary_budget: Option<Decimal>,
23    pub extraordinary_budget: Option<Decimal>,
24    pub notes: Option<String>,
25}
26
27/// Response DTO pour un budget
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct BudgetResponse {
30    pub id: Uuid,
31    /// L'ACP dont ce budget est le budget (ADR-0045).
32    pub acp_id: Uuid,
33    /// Le syndic qui l'a préparé. Trace d'auteur, pas un droit d'accès.
34    pub organization_id: Uuid,
35    pub building_id: Uuid,
36    pub fiscal_year: i32,
37    pub ordinary_budget: Decimal,
38    pub extraordinary_budget: Decimal,
39    pub total_budget: Decimal,
40    pub status: BudgetStatus,
41    pub submitted_date: Option<DateTime<Utc>>,
42    pub approved_date: Option<DateTime<Utc>>,
43    pub approved_by_meeting_id: Option<Uuid>,
44    pub monthly_provision_amount: Decimal,
45    pub notes: Option<String>,
46    pub created_at: DateTime<Utc>,
47    pub updated_at: DateTime<Utc>,
48    // Computed fields
49    pub is_active: bool,
50    pub is_editable: bool,
51}
52
53impl From<Budget> for BudgetResponse {
54    fn from(budget: Budget) -> Self {
55        let is_active = budget.is_active();
56        let is_editable = budget.is_editable();
57
58        Self {
59            id: budget.id,
60            acp_id: budget.acp_id,
61            organization_id: budget.organization_id,
62            building_id: budget.building_id,
63            fiscal_year: budget.fiscal_year,
64            ordinary_budget: budget.ordinary_budget,
65            extraordinary_budget: budget.extraordinary_budget,
66            total_budget: budget.total_budget,
67            status: budget.status,
68            submitted_date: budget.submitted_date,
69            approved_date: budget.approved_date,
70            approved_by_meeting_id: budget.approved_by_meeting_id,
71            monthly_provision_amount: budget.monthly_provision_amount,
72            notes: budget.notes,
73            created_at: budget.created_at,
74            updated_at: budget.updated_at,
75            is_active,
76            is_editable,
77        }
78    }
79}
80
81/// Response DTO pour variance analysis (budget vs actual)
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct BudgetVarianceResponse {
84    pub budget_id: Uuid,
85    pub fiscal_year: i32,
86    pub building_id: Uuid,
87
88    // Budget prévu
89    #[serde(with = "rust_decimal::serde::float")]
90    pub budgeted_ordinary: Decimal,
91    #[serde(with = "rust_decimal::serde::float")]
92    pub budgeted_extraordinary: Decimal,
93    #[serde(with = "rust_decimal::serde::float")]
94    pub budgeted_total: Decimal,
95
96    // Réalisé (YTD - Year To Date)
97    #[serde(with = "rust_decimal::serde::float")]
98    pub actual_ordinary: Decimal,
99    #[serde(with = "rust_decimal::serde::float")]
100    pub actual_extraordinary: Decimal,
101    #[serde(with = "rust_decimal::serde::float")]
102    pub actual_total: Decimal,
103
104    // Variance (budget - actual)
105    #[serde(with = "rust_decimal::serde::float")]
106    pub variance_ordinary: Decimal,
107    #[serde(with = "rust_decimal::serde::float")]
108    pub variance_extraordinary: Decimal,
109    #[serde(with = "rust_decimal::serde::float")]
110    pub variance_total: Decimal,
111
112    // Variance en % ((budget - actual) / budget * 100)
113    #[serde(with = "rust_decimal::serde::float")]
114    pub variance_ordinary_pct: Decimal,
115    #[serde(with = "rust_decimal::serde::float")]
116    pub variance_extraordinary_pct: Decimal,
117    #[serde(with = "rust_decimal::serde::float")]
118    pub variance_total_pct: Decimal,
119
120    // Alertes
121    pub has_overruns: bool,              // Dépassements > 10%
122    pub overrun_categories: Vec<String>, // Catégories en dépassement
123    pub months_elapsed: i32,             // Mois écoulés dans l'exercice
124    #[serde(with = "rust_decimal::serde::float")]
125    pub projected_year_end_total: Decimal, // Projection fin d'année
126}
127
128/// Response avec statistiques budgétaires
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct BudgetStatsResponse {
131    pub total_budgets: i64,
132    pub draft_count: i64,
133    pub submitted_count: i64,
134    pub approved_count: i64,
135    pub rejected_count: i64,
136    pub archived_count: i64,
137    // #661 — moyennes de montants : Decimal comme le reste du PCMN.
138    #[serde(with = "rust_decimal::serde::float")]
139    pub average_total_budget: Decimal,
140    #[serde(with = "rust_decimal::serde::float")]
141    pub average_monthly_provision: Decimal,
142}