koprogo_api/domain/entities/
organization.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4use validator::Validate;
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
7pub enum SubscriptionPlan {
8 Free,
9 Starter,
10 Professional,
11 Enterprise,
12}
13
14impl std::fmt::Display for SubscriptionPlan {
15 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16 match self {
17 SubscriptionPlan::Free => write!(f, "free"),
18 SubscriptionPlan::Starter => write!(f, "starter"),
19 SubscriptionPlan::Professional => write!(f, "professional"),
20 SubscriptionPlan::Enterprise => write!(f, "enterprise"),
21 }
22 }
23}
24
25impl std::str::FromStr for SubscriptionPlan {
26 type Err = String;
27
28 fn from_str(s: &str) -> Result<Self, Self::Err> {
29 match s.to_lowercase().as_str() {
30 "free" => Ok(SubscriptionPlan::Free),
31 "starter" => Ok(SubscriptionPlan::Starter),
32 "professional" => Ok(SubscriptionPlan::Professional),
33 "enterprise" => Ok(SubscriptionPlan::Enterprise),
34 _ => Err(format!("Invalid subscription plan: {}", s)),
35 }
36 }
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
40pub struct Organization {
41 pub id: Uuid,
42
43 #[validate(length(min = 2, message = "Name must be at least 2 characters"))]
44 pub name: String,
45
46 pub slug: String,
47
48 #[validate(email(message = "Contact email must be valid"))]
49 pub contact_email: String,
50
51 pub contact_phone: Option<String>,
52
53 pub subscription_plan: SubscriptionPlan,
54
55 pub max_buildings: i32,
56
57 pub max_users: i32,
58
59 pub is_active: bool,
60
61 pub created_at: DateTime<Utc>,
62 pub updated_at: DateTime<Utc>,
63}
64
65impl Organization {
66 pub fn new(
67 name: String,
68 contact_email: String,
69 contact_phone: Option<String>,
70 subscription_plan: SubscriptionPlan,
71 ) -> Result<Self, String> {
72 let slug = Self::generate_slug(&name);
73
74 let (max_buildings, max_users) = Self::get_limits_for_plan(&subscription_plan);
75
76 let org = Self {
77 id: Uuid::new_v4(),
78 name: name.trim().to_string(),
79 slug,
80 contact_email: contact_email.to_lowercase().trim().to_string(),
81 contact_phone,
82 subscription_plan,
83 max_buildings,
84 max_users,
85 is_active: true,
86 created_at: Utc::now(),
87 updated_at: Utc::now(),
88 };
89
90 org.validate()
91 .map_err(|e| format!("Validation error: {}", e))?;
92
93 Ok(org)
94 }
95
96 fn generate_slug(name: &str) -> String {
97 name.to_lowercase()
98 .chars()
99 .map(|c| if c.is_alphanumeric() { c } else { '-' })
100 .collect::<String>()
101 .split('-')
102 .filter(|s| !s.is_empty())
103 .collect::<Vec<&str>>()
104 .join("-")
105 }
106
107 fn get_limits_for_plan(plan: &SubscriptionPlan) -> (i32, i32) {
108 match plan {
109 SubscriptionPlan::Free => (1, 3),
110 SubscriptionPlan::Starter => (5, 10),
111 SubscriptionPlan::Professional => (20, 50),
112 SubscriptionPlan::Enterprise => (i32::MAX, i32::MAX),
113 }
114 }
115
116 pub fn upgrade_plan(&mut self, new_plan: SubscriptionPlan) {
117 self.subscription_plan = new_plan;
118 let (max_buildings, max_users) = Self::get_limits_for_plan(&self.subscription_plan);
119 self.max_buildings = max_buildings;
120 self.max_users = max_users;
121 self.updated_at = Utc::now();
122 }
123
124 pub fn update_contact(&mut self, email: String, phone: Option<String>) -> Result<(), String> {
125 self.contact_email = email.to_lowercase().trim().to_string();
126 self.contact_phone = phone;
127 self.updated_at = Utc::now();
128
129 self.validate()
130 .map_err(|e| format!("Validation error: {}", e))?;
131
132 Ok(())
133 }
134
135 pub fn deactivate(&mut self) {
136 self.is_active = false;
137 self.updated_at = Utc::now();
138 }
139
140 pub fn activate(&mut self) {
141 self.is_active = true;
142 self.updated_at = Utc::now();
143 }
144
145 pub fn can_add_building(&self, current_count: i32) -> bool {
146 self.is_active && current_count < self.max_buildings
147 }
148
149 pub fn can_add_user(&self, current_count: i32) -> bool {
150 self.is_active && current_count < self.max_users
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn test_create_organization_success() {
160 let org = Organization::new(
161 "Test Company".to_string(),
162 "contact@test.com".to_string(),
163 Some("+33123456789".to_string()),
164 SubscriptionPlan::Professional,
165 );
166
167 assert!(org.is_ok());
168 let org = org.unwrap();
169 assert_eq!(org.name, "Test Company");
170 assert_eq!(org.slug, "test-company");
171 assert_eq!(org.max_buildings, 20);
172 assert_eq!(org.max_users, 50);
173 assert!(org.is_active);
174 }
175
176 #[test]
177 fn test_generate_slug() {
178 let org = Organization::new(
179 "My Super Company!!!".to_string(),
180 "contact@test.com".to_string(),
181 None,
182 SubscriptionPlan::Free,
183 )
184 .unwrap();
185
186 assert_eq!(org.slug, "my-super-company");
187 }
188
189 #[test]
190 fn test_subscription_limits() {
191 let free_org = Organization::new(
192 "Free Org".to_string(),
193 "free@test.com".to_string(),
194 None,
195 SubscriptionPlan::Free,
196 )
197 .unwrap();
198 assert_eq!(free_org.max_buildings, 1);
199 assert_eq!(free_org.max_users, 3);
200
201 let starter_org = Organization::new(
202 "Starter Org".to_string(),
203 "starter@test.com".to_string(),
204 None,
205 SubscriptionPlan::Starter,
206 )
207 .unwrap();
208 assert_eq!(starter_org.max_buildings, 5);
209 assert_eq!(starter_org.max_users, 10);
210 }
211
212 #[test]
213 fn test_upgrade_plan() {
214 let mut org = Organization::new(
215 "Test Org".to_string(),
216 "test@test.com".to_string(),
217 None,
218 SubscriptionPlan::Free,
219 )
220 .unwrap();
221
222 assert_eq!(org.max_buildings, 1);
223
224 org.upgrade_plan(SubscriptionPlan::Professional);
225 assert_eq!(org.subscription_plan, SubscriptionPlan::Professional);
226 assert_eq!(org.max_buildings, 20);
227 assert_eq!(org.max_users, 50);
228 }
229
230 #[test]
231 fn test_can_add_building() {
232 let org = Organization::new(
233 "Test Org".to_string(),
234 "test@test.com".to_string(),
235 None,
236 SubscriptionPlan::Starter,
237 )
238 .unwrap();
239
240 assert!(org.can_add_building(0));
241 assert!(org.can_add_building(4));
242 assert!(!org.can_add_building(5));
243 }
244
245 #[test]
246 fn test_deactivate_prevents_adding() {
247 let mut org = Organization::new(
248 "Test Org".to_string(),
249 "test@test.com".to_string(),
250 None,
251 SubscriptionPlan::Professional,
252 )
253 .unwrap();
254
255 org.deactivate();
256 assert!(!org.can_add_building(0));
257 assert!(!org.can_add_user(0));
258 }
259}