Skip to main content

koprogo_api/domain/entities/
unit.rs

1use chrono::{DateTime, Utc};
2use rust_decimal::Decimal;
3use rust_decimal_macros::dec;
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7const MAX_QUOTA: Decimal = dec!(1000);
8
9/// Type de lot (appartement, cave, parking, etc.)
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11pub enum UnitType {
12    Apartment,
13    Parking,
14    Cellar,
15    Commercial,
16    Other,
17}
18
19/// Représente un lot dans la copropriété
20#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
21pub struct Unit {
22    pub id: Uuid,
23    pub organization_id: Uuid,
24    pub building_id: Uuid,
25    pub unit_number: String,
26    pub unit_type: UnitType,
27    pub floor: Option<i32>,
28    pub surface_area: f64, // en m² (mesure physique, f64 OK — cf. ADR-0009)
29    pub quota: Decimal,    // Quote-part en millièmes (Decimal exact — cf. ADR-0007)
30    pub owner_id: Option<Uuid>,
31    pub created_at: DateTime<Utc>,
32    pub updated_at: DateTime<Utc>,
33}
34
35impl Unit {
36    pub fn new(
37        organization_id: Uuid,
38        building_id: Uuid,
39        unit_number: String,
40        unit_type: UnitType,
41        floor: Option<i32>,
42        surface_area: f64,
43        quota: Decimal,
44    ) -> Result<Self, String> {
45        if unit_number.is_empty() {
46            return Err("Unit number cannot be empty".to_string());
47        }
48        if surface_area <= 0.0 {
49            return Err("Surface area must be greater than 0".to_string());
50        }
51        // Validate shares (tantièmes) according to Art. 577-2 §4 Code Civil belge
52        // Shares must be positive and typically don't exceed 1000 (building tantiemes)
53        // Individual unit shares must sum to building.total_shares (usually 1000)
54        if quota <= Decimal::ZERO || quota > MAX_QUOTA {
55            return Err("Quota (shares) must be between 0 (exclusive) and 1000 (inclusive), representing building tantièmes".to_string());
56        }
57
58        let now = Utc::now();
59        Ok(Self {
60            id: Uuid::new_v4(),
61            organization_id,
62            building_id,
63            unit_number,
64            unit_type,
65            floor,
66            surface_area,
67            quota,
68            owner_id: None,
69            created_at: now,
70            updated_at: now,
71        })
72    }
73
74    pub fn validate_update(&self) -> Result<(), String> {
75        if self.unit_number.is_empty() {
76            return Err("Unit number cannot be empty".to_string());
77        }
78        if self.surface_area <= 0.0 {
79            return Err("Surface area must be greater than 0".to_string());
80        }
81        if self.quota <= Decimal::ZERO || self.quota > MAX_QUOTA {
82            return Err("Quota must be between 0 and 1000".to_string());
83        }
84        Ok(())
85    }
86
87    pub fn assign_owner(&mut self, owner_id: Uuid) {
88        self.owner_id = Some(owner_id);
89        self.updated_at = Utc::now();
90    }
91
92    pub fn remove_owner(&mut self) {
93        self.owner_id = None;
94        self.updated_at = Utc::now();
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_create_unit_success() {
104        let org_id = Uuid::new_v4();
105        let building_id = Uuid::new_v4();
106        let unit = Unit::new(
107            org_id,
108            building_id,
109            "A101".to_string(),
110            UnitType::Apartment,
111            Some(1),
112            75.5,
113            dec!(50),
114        );
115
116        assert!(unit.is_ok());
117        let unit = unit.unwrap();
118        assert_eq!(unit.organization_id, org_id);
119        assert_eq!(unit.unit_number, "A101");
120        assert_eq!(unit.surface_area, 75.5);
121    }
122
123    #[test]
124    fn test_create_unit_invalid_surface_fails() {
125        let org_id = Uuid::new_v4();
126        let building_id = Uuid::new_v4();
127        let unit = Unit::new(
128            org_id,
129            building_id,
130            "A101".to_string(),
131            UnitType::Apartment,
132            Some(1),
133            0.0,
134            dec!(50),
135        );
136
137        assert!(unit.is_err());
138    }
139
140    #[test]
141    fn test_assign_owner() {
142        let org_id = Uuid::new_v4();
143        let building_id = Uuid::new_v4();
144        let mut unit = Unit::new(
145            org_id,
146            building_id,
147            "A101".to_string(),
148            UnitType::Apartment,
149            Some(1),
150            75.5,
151            dec!(50),
152        )
153        .unwrap();
154
155        let owner_id = Uuid::new_v4();
156        unit.assign_owner(owner_id);
157
158        assert_eq!(unit.owner_id, Some(owner_id));
159    }
160}