koprogo_api/domain/copropriete/
unit.rs1use chrono::{DateTime, Utc};
2use rust_decimal::Decimal;
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)]
12pub enum UnitType {
13 Apartment,
14 Parking,
15 Cellar,
16 Commercial,
17 Other,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
22pub struct Unit {
23 pub id: Uuid,
24 pub acp_id: Uuid,
29 pub building_id: Uuid,
30 pub unit_number: String,
31 pub unit_type: UnitType,
32 pub floor: Option<i32>,
33 pub surface_area: f64, pub quota: Decimal, pub owner_id: Option<Uuid>,
36 pub created_at: DateTime<Utc>,
37 pub updated_at: DateTime<Utc>,
38}
39
40impl Unit {
41 pub fn new(
42 acp_id: Uuid,
43 building_id: Uuid,
44 unit_number: String,
45 unit_type: UnitType,
46 floor: Option<i32>,
47 surface_area: f64,
48 quota: Decimal,
49 ) -> Result<Self, String> {
50 if unit_number.is_empty() {
51 return Err("Unit number cannot be empty".to_string());
52 }
53 if surface_area <= 0.0 {
54 return Err("Surface area must be greater than 0".to_string());
55 }
56 if quota <= Decimal::ZERO {
65 return Err("Quota (shares) must be strictly positive (Art. 3.84 CC)".to_string());
66 }
67
68 let now = Utc::now();
69 Ok(Self {
70 id: Uuid::new_v4(),
71 acp_id,
72 building_id,
73 unit_number,
74 unit_type,
75 floor,
76 surface_area,
77 quota,
78 owner_id: None,
79 created_at: now,
80 updated_at: now,
81 })
82 }
83
84 pub fn validate_update(&self) -> Result<(), String> {
85 if self.unit_number.is_empty() {
86 return Err("Unit number cannot be empty".to_string());
87 }
88 if self.surface_area <= 0.0 {
89 return Err("Surface area must be greater than 0".to_string());
90 }
91 if self.quota <= Decimal::ZERO {
92 return Err("Quota must be strictly positive (Art. 3.84 CC)".to_string());
93 }
94 Ok(())
95 }
96
97 pub fn assign_owner(&mut self, owner_id: Uuid) {
98 self.owner_id = Some(owner_id);
99 self.updated_at = Utc::now();
100 }
101
102 pub fn remove_owner(&mut self) {
103 self.owner_id = None;
104 self.updated_at = Utc::now();
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use rust_decimal_macros::dec;
112
113 #[test]
116 fn happy_unit_quota_5000_on_acte_10000_accepted() {
117 let u = Unit::new(
120 Uuid::new_v4(),
121 Uuid::new_v4(),
122 "B12".to_string(),
123 UnitType::Apartment,
124 Some(2),
125 120.0,
126 dec!(5000),
127 );
128 assert!(
129 u.is_ok(),
130 "quota 5000 doit être accepté (acte de base 10000)"
131 );
132 }
133
134 #[test]
135 fn edge_unit_quota_just_above_1000_accepted() {
136 let u = Unit::new(
137 Uuid::new_v4(),
138 Uuid::new_v4(),
139 "B13".to_string(),
140 UnitType::Apartment,
141 Some(2),
142 60.0,
143 dec!(1001),
144 );
145 assert!(
146 u.is_ok(),
147 "1001 ne doit plus être rejeté (plus de cap 1000)"
148 );
149 }
150
151 #[test]
152 fn security_unit_quota_zero_rejected() {
153 let u = Unit::new(
154 Uuid::new_v4(),
155 Uuid::new_v4(),
156 "B14".to_string(),
157 UnitType::Apartment,
158 None,
159 50.0,
160 Decimal::ZERO,
161 );
162 assert!(u.is_err(), "quota nul rejeté (invariant unitaire minimal)");
163 }
164
165 #[test]
166 fn negative_unit_quota_negative_rejected() {
167 let u = Unit::new(
168 Uuid::new_v4(),
169 Uuid::new_v4(),
170 "B15".to_string(),
171 UnitType::Apartment,
172 None,
173 50.0,
174 dec!(-1),
175 );
176 assert!(u.is_err());
177 }
178
179 #[test]
180 fn test_create_unit_success() {
181 let acp_id = Uuid::new_v4();
182 let building_id = Uuid::new_v4();
183 let unit = Unit::new(
184 acp_id,
185 building_id,
186 "A101".to_string(),
187 UnitType::Apartment,
188 Some(1),
189 75.5,
190 dec!(50),
191 );
192
193 assert!(unit.is_ok());
194 let unit = unit.unwrap();
195 assert_eq!(unit.acp_id, acp_id);
196 assert_eq!(unit.unit_number, "A101");
197 assert_eq!(unit.surface_area, 75.5);
198 }
199
200 #[test]
201 fn test_create_unit_invalid_surface_fails() {
202 let org_id = Uuid::new_v4();
203 let building_id = Uuid::new_v4();
204 let unit = Unit::new(
205 org_id,
206 building_id,
207 "A101".to_string(),
208 UnitType::Apartment,
209 Some(1),
210 0.0,
211 dec!(50),
212 );
213
214 assert!(unit.is_err());
215 }
216
217 #[test]
218 fn test_assign_owner() {
219 let org_id = Uuid::new_v4();
220 let building_id = Uuid::new_v4();
221 let mut unit = Unit::new(
222 org_id,
223 building_id,
224 "A101".to_string(),
225 UnitType::Apartment,
226 Some(1),
227 75.5,
228 dec!(50),
229 )
230 .unwrap();
231
232 let owner_id = Uuid::new_v4();
233 unit.assign_owner(owner_id);
234
235 assert_eq!(unit.owner_id, Some(owner_id));
236 }
237}