koprogo_api/domain/entities/
organization.rs

1use 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}