koprogo_api/domain/entities/
service_provider.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
8pub enum TradeCategory {
9 Syndic,
10 BureauEtude,
11 Architecte,
12 AssistantMaitreOeuvre,
13 IngenieurStabilite,
14 Plombier,
15 Electricien,
16 Chauffagiste,
17 Menuisier,
18 Peintre,
19 Maconnerie,
20 Etancheite,
21 Ascensoriste,
22 Jardinier,
23 Nettoyage,
24 Securite,
25 Deboucheur,
26 Couvreur,
27 Carreleur,
28 TechniquesSpeciales,
29}
30
31impl TradeCategory {
32 pub fn to_sql(&self) -> &'static str {
33 match self {
34 TradeCategory::Syndic => "Syndic",
35 TradeCategory::BureauEtude => "BureauEtude",
36 TradeCategory::Architecte => "Architecte",
37 TradeCategory::AssistantMaitreOeuvre => "AssistantMaitreOeuvre",
38 TradeCategory::IngenieurStabilite => "IngenieurStabilite",
39 TradeCategory::Plombier => "Plombier",
40 TradeCategory::Electricien => "Electricien",
41 TradeCategory::Chauffagiste => "Chauffagiste",
42 TradeCategory::Menuisier => "Menuisier",
43 TradeCategory::Peintre => "Peintre",
44 TradeCategory::Maconnerie => "Maconnerie",
45 TradeCategory::Etancheite => "Etancheite",
46 TradeCategory::Ascensoriste => "Ascensoriste",
47 TradeCategory::Jardinier => "Jardinier",
48 TradeCategory::Nettoyage => "Nettoyage",
49 TradeCategory::Securite => "Securite",
50 TradeCategory::Deboucheur => "Deboucheur",
51 TradeCategory::Couvreur => "Couvreur",
52 TradeCategory::Carreleur => "Carreleur",
53 TradeCategory::TechniquesSpeciales => "TechniquesSpeciales",
54 }
55 }
56
57 pub fn from_sql(s: &str) -> Result<Self, String> {
58 match s {
59 "Syndic" => Ok(TradeCategory::Syndic),
60 "BureauEtude" => Ok(TradeCategory::BureauEtude),
61 "Architecte" => Ok(TradeCategory::Architecte),
62 "AssistantMaitreOeuvre" => Ok(TradeCategory::AssistantMaitreOeuvre),
63 "IngenieurStabilite" => Ok(TradeCategory::IngenieurStabilite),
64 "Plombier" => Ok(TradeCategory::Plombier),
65 "Electricien" => Ok(TradeCategory::Electricien),
66 "Chauffagiste" => Ok(TradeCategory::Chauffagiste),
67 "Menuisier" => Ok(TradeCategory::Menuisier),
68 "Peintre" => Ok(TradeCategory::Peintre),
69 "Maconnerie" => Ok(TradeCategory::Maconnerie),
70 "Etancheite" => Ok(TradeCategory::Etancheite),
71 "Ascensoriste" => Ok(TradeCategory::Ascensoriste),
72 "Jardinier" => Ok(TradeCategory::Jardinier),
73 "Nettoyage" => Ok(TradeCategory::Nettoyage),
74 "Securite" => Ok(TradeCategory::Securite),
75 "Deboucheur" => Ok(TradeCategory::Deboucheur),
76 "Couvreur" => Ok(TradeCategory::Couvreur),
77 "Carreleur" => Ok(TradeCategory::Carreleur),
78 "TechniquesSpeciales" => Ok(TradeCategory::TechniquesSpeciales),
79 _ => Err(format!("Invalid trade category: {}", s)),
80 }
81 }
82}
83
84#[derive(Clone)]
85pub struct ServiceProvider {
86 pub id: Uuid,
87 pub organization_id: Uuid,
88 pub company_name: String,
89 pub trade_category: TradeCategory,
90 pub specializations: Vec<String>,
91 pub service_zone_postal_codes: Vec<String>,
92 pub certifications: Vec<String>, pub ipi_registration: Option<String>,
94 pub bce_number: Option<String>, pub rating_avg: Option<f64>, pub reviews_count: i32,
97 pub is_verified: bool,
98 pub public_profile_slug: String,
99 pub created_at: DateTime<Utc>,
100 pub updated_at: DateTime<Utc>,
101}
102
103impl ServiceProvider {
104 pub fn new(
105 organization_id: Uuid,
106 company_name: String,
107 trade_category: TradeCategory,
108 bce_number: Option<String>,
109 ) -> Result<Self, String> {
110 if company_name.is_empty() {
111 return Err("company_name cannot be empty".to_string());
112 }
113 let slug = generate_slug(&company_name);
114 Ok(Self {
115 id: Uuid::new_v4(),
116 organization_id,
117 company_name,
118 trade_category,
119 specializations: vec![],
120 service_zone_postal_codes: vec![],
121 certifications: vec![],
122 ipi_registration: None,
123 bce_number,
124 rating_avg: None,
125 reviews_count: 0,
126 is_verified: false,
127 public_profile_slug: slug,
128 created_at: Utc::now(),
129 updated_at: Utc::now(),
130 })
131 }
132
133 pub fn update_rating(&mut self, new_score: f64) -> Result<(), String> {
135 if new_score < 0.0 || new_score > 5.0 {
136 return Err("Rating must be between 0.0 and 5.0".to_string());
137 }
138 let current_avg = self.rating_avg.unwrap_or(0.0);
139 let new_reviews_count = self.reviews_count + 1;
140 self.rating_avg =
141 Some((current_avg * self.reviews_count as f64 + new_score) / new_reviews_count as f64);
142 self.reviews_count = new_reviews_count;
143 self.updated_at = Utc::now();
144 Ok(())
145 }
146}
147
148fn generate_slug(name: &str) -> String {
149 name.to_lowercase()
150 .chars()
151 .map(|c| if c.is_alphanumeric() { c } else { '-' })
152 .collect::<String>()
153 .split('-')
154 .filter(|s| !s.is_empty())
155 .collect::<Vec<_>>()
156 .join("-")
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn test_service_provider_new_success() {
165 let org_id = Uuid::new_v4();
166 let provider = ServiceProvider::new(
167 org_id,
168 "ABC Plomberie".to_string(),
169 TradeCategory::Plombier,
170 Some("BE123456789".to_string()),
171 );
172 assert!(provider.is_ok());
173 let p = provider.unwrap();
174 assert_eq!(p.company_name, "ABC Plomberie");
175 assert_eq!(p.public_profile_slug, "abc-plomberie");
176 assert_eq!(p.reviews_count, 0);
177 assert_eq!(p.rating_avg, None);
178 }
179
180 #[test]
181 fn test_service_provider_empty_name() {
182 let org_id = Uuid::new_v4();
183 let result = ServiceProvider::new(org_id, "".to_string(), TradeCategory::Plombier, None);
184 assert!(result.is_err());
185 }
186
187 #[test]
188 fn test_update_rating() {
189 let org_id = Uuid::new_v4();
190 let mut provider = ServiceProvider::new(
191 org_id,
192 "Test Provider".to_string(),
193 TradeCategory::Electricien,
194 None,
195 )
196 .unwrap();
197
198 let _ = provider.update_rating(4.0);
199 assert_eq!(provider.reviews_count, 1);
200 assert_eq!(provider.rating_avg, Some(4.0));
201
202 let _ = provider.update_rating(5.0);
203 assert_eq!(provider.reviews_count, 2);
204 assert_eq!(provider.rating_avg, Some(4.5));
205 }
206
207 #[test]
208 fn test_update_rating_invalid() {
209 let org_id = Uuid::new_v4();
210 let mut provider = ServiceProvider::new(
211 org_id,
212 "Test Provider".to_string(),
213 TradeCategory::Electricien,
214 None,
215 )
216 .unwrap();
217
218 let result = provider.update_rating(6.0);
219 assert!(result.is_err());
220 }
221}