konduto/types/customer/
mod.rs

1use crate::customer_email::CustomerEmail;
2use crate::customer_id::CustomerId;
3use crate::customer_name::CustomerName;
4use crate::risk::{RiskLevel, RiskScore};
5use crate::tax_id::{CnpjOrCpf, DocumentType};
6use crate::types::shared::date_iso_8601::DateIso8601;
7use crate::types::shared::phone_number::PhoneNumber;
8use crate::types::validation_errors::ValidationError;
9use serde::{Deserialize, Serialize};
10
11pub mod customer_email;
12pub mod customer_id;
13pub mod customer_name;
14pub mod risk;
15pub mod tax_id;
16
17/// Dados do cliente
18///
19/// Esta estrutura utiliza tipos seguros que garantem validação em tempo de compilação
20/// ou falha rápida na criação/parsing.
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct Customer {
23    /// ID único do cliente no sistema do lojista (obrigatório)
24    pub id: CustomerId,
25
26    /// Nome completo do cliente (obrigatório pela API)
27    pub name: CustomerName,
28
29    /// Email do cliente (obrigatório pela API)
30    pub email: CustomerEmail,
31
32    /// Telefone do cliente (recomendado)
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub phone1: Option<PhoneNumber>,
35
36    /// Telefone alternativo (recomendado)
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub phone2: Option<PhoneNumber>,
39
40    /// CPF ou CNPJ do cliente (recomendado)
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub tax_id: Option<CnpjOrCpf>,
43
44    /// Tipo do documento fiscal (recomendado)
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub document_type: Option<DocumentType>,
47
48    /// Data de nascimento no formato ISO 8601(YYYY-MM-DD) (recomendado)
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub dob: Option<DateIso8601>,
51
52    /// Data de criação da conta no formato ISO 8601(YYYY-MM-DD)  (recomendado)
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub created_at: Option<DateIso8601>,
55
56    /// Indica se o cliente é novo (recomendado)
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub new: Option<bool>,
59
60    /// Indica se o cliente é VIP (recomendado)
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub vip: Option<bool>,
63
64    /// Tipo do cliente (recomendado)
65    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
66    pub customer_type: Option<String>,
67
68    /// Nível de risco (recomendado)
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub risk_level: Option<RiskLevel>,
71
72    /// Score de risco (recomendado)
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub risk_score: Option<RiskScore>,
75
76    /// Nome completo da mãe (recomendado)
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub mother_name: Option<CustomerName>,
79}
80
81impl Customer {
82    /// Cria um builder para construir um cliente
83    pub fn builder() -> CustomerBuilder {
84        CustomerBuilder::default()
85    }
86}
87
88#[derive(Default)]
89pub struct CustomerBuilder {
90    id: Option<CustomerId>,
91    name: Option<CustomerName>,
92    email: Option<CustomerEmail>,
93    phone1: Option<PhoneNumber>,
94    phone2: Option<PhoneNumber>,
95    tax_id: Option<CnpjOrCpf>,
96    document_type: Option<DocumentType>,
97    dob: Option<DateIso8601>,
98    created_at: Option<DateIso8601>,
99    new: Option<bool>,
100    vip: Option<bool>,
101    customer_type: Option<String>,
102    risk_level: Option<RiskLevel>,
103    risk_score: Option<RiskScore>,
104    mother_name: Option<CustomerName>,
105}
106
107impl CustomerBuilder {
108    /// Define o ID do cliente (obrigatório)
109    pub fn id(mut self, id: impl Into<String>) -> Result<Self, ValidationError> {
110        self.id = Some(CustomerId::try_new(id)?);
111        Ok(self)
112    }
113
114    /// Define o nome do cliente
115    pub fn name(mut self, name: impl Into<String>) -> Result<Self, ValidationError> {
116        self.name = Some(CustomerName::try_new(name)?);
117        Ok(self)
118    }
119
120    /// Define o email do cliente
121    pub fn email(mut self, email: impl Into<String>) -> Result<Self, ValidationError> {
122        self.email = Some(CustomerEmail::new(email)?);
123        Ok(self)
124    }
125
126    /// Define o telefone principal
127    pub fn phone1(mut self, phone: impl Into<String>) -> Result<Self, ValidationError> {
128        self.phone1 = Some(PhoneNumber::new(phone)?);
129        Ok(self)
130    }
131
132    /// Define o telefone alternativo
133    pub fn phone2(mut self, phone: impl Into<String>) -> Result<Self, ValidationError> {
134        self.phone2 = Some(PhoneNumber::new(phone)?);
135        Ok(self)
136    }
137
138    /// Define o CPF/CNPJ (com limpeza automática de pontuação)
139    pub fn tax_id(mut self, tax_id: impl Into<String>) -> Result<Self, ValidationError> {
140        self.tax_id = Some(CnpjOrCpf::try_from(tax_id.into())?);
141        Ok(self)
142    }
143
144    /// Define o tipo de documento
145    pub fn document_type(mut self, doc_type: DocumentType) -> Self {
146        self.document_type = Some(doc_type);
147        self
148    }
149
150    /// Define a data de nascimento (formato: AAAA-MM-DD)
151    pub fn dob(mut self, dob: impl Into<String>) -> Result<Self, ValidationError> {
152        self.dob = Some(DateIso8601::new(dob)?);
153        Ok(self)
154    }
155
156    /// Define a data de criação da conta (formato: AAAA-MM-DD)
157    pub fn created_at(mut self, date: impl Into<String>) -> Result<Self, ValidationError> {
158        self.created_at = Some(DateIso8601::new(date)?);
159        Ok(self)
160    }
161
162    /// Define se é novo cliente
163    pub fn new(mut self, is_new: bool) -> Self {
164        self.new = Some(is_new);
165        self
166    }
167
168    /// Define se é VIP
169    pub fn vip(mut self, is_vip: bool) -> Self {
170        self.vip = Some(is_vip);
171        self
172    }
173
174    /// Define o tipo do cliente
175    pub fn customer_type(mut self, customer_type: impl Into<String>) -> Self {
176        self.customer_type = Some(customer_type.into());
177        self
178    }
179
180    /// Define o nível de risco
181    pub fn risk_level(mut self, level: RiskLevel) -> Self {
182        self.risk_level = Some(level);
183        self
184    }
185
186    /// Define o score de risco (0.0 a 1.0)
187    pub fn risk_score(mut self, score: f64) -> Result<Self, ValidationError> {
188        self.risk_score = Some(RiskScore::new(score)?);
189        Ok(self)
190    }
191
192    /// Define o nome da mãe
193    pub fn mother_name(mut self, name: impl Into<String>) -> Result<Self, ValidationError> {
194        self.mother_name = Some(CustomerName::try_new(name)?);
195        Ok(self)
196    }
197
198    /// Constrói o cliente
199    ///
200    /// # Erros
201    ///
202    /// Retorna erro se o ID obrigatório não foi fornecido
203    pub fn build(self) -> Result<Customer, ValidationError> {
204        let id = self
205            .id
206            .ok_or_else(|| ValidationError::EmptyField("customer_id (required)".to_string()))?;
207
208        let name = self
209            .name
210            .ok_or_else(|| ValidationError::EmptyField("customer_id (required)".to_string()))?;
211
212        let email = self
213            .email
214            .ok_or_else(|| ValidationError::EmptyField("customer_id (required)".to_string()))?;
215
216        Ok(Customer {
217            id,
218            name,
219            email,
220            phone1: self.phone1,
221            phone2: self.phone2,
222            tax_id: self.tax_id,
223            document_type: self.document_type,
224            dob: self.dob,
225            created_at: self.created_at,
226            new: self.new,
227            vip: self.vip,
228            customer_type: self.customer_type,
229            risk_level: self.risk_level,
230            risk_score: self.risk_score,
231            mother_name: self.mother_name,
232        })
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239    use crate::customer_id::CustomerId;
240    use crate::types::validation_errors::ValidationError;
241
242    #[test]
243    fn test_customer_id_valid() {
244        let id = CustomerId::try_new("CUSTOMER123").unwrap();
245        assert_eq!(id.as_str(), "CUSTOMER123");
246    }
247
248    #[test]
249    fn test_customer_id_empty() {
250        let result = CustomerId::try_new("");
251        assert!(result.is_err());
252    }
253
254    #[test]
255    fn test_customer_id_too_long() {
256        let long_id = "A".repeat(101);
257        let result = CustomerId::try_new(long_id);
258        assert!(result.is_err());
259    }
260
261    #[test]
262    fn test_customer_name_valid() {
263        let name = CustomerName::try_new("João Silva").unwrap();
264        assert_eq!(name.as_str(), "João Silva");
265    }
266
267    #[test]
268    fn test_customer_name_trim() {
269        let name = CustomerName::try_new("  João Silva  ").unwrap();
270        assert_eq!(name.as_str(), "João Silva");
271    }
272
273    #[test]
274    fn test_email_valid() {
275        let email = CustomerEmail::new("joao@example.com").unwrap();
276        assert_eq!(email.as_str(), "joao@example.com");
277    }
278
279    #[test]
280    fn test_email_lowercase() {
281        let email = CustomerEmail::new("JOAO@EXAMPLE.COM").unwrap();
282        assert_eq!(email.as_str(), "joao@example.com");
283    }
284
285    #[test]
286    fn test_email_invalid() {
287        assert!(CustomerEmail::new("invalid").is_err());
288        assert!(CustomerEmail::new("invalid@").is_err());
289        assert!(CustomerEmail::new("@invalid").is_err());
290    }
291
292    #[test]
293    fn test_date_iso8601_valid() {
294        let date = DateIso8601::new("2025-11-15").unwrap();
295        assert_eq!(date.as_str(), "2025-11-15");
296    }
297
298    #[test]
299    fn test_date_iso8601_invalid() {
300        assert!(DateIso8601::new("2025/11/15").is_err());
301        assert!(DateIso8601::new("15-11-2025").is_err());
302        assert!(DateIso8601::new("2025-11").is_err());
303    }
304
305    #[test]
306    fn test_risk_score_valid() {
307        let score = RiskScore::new(0.5).unwrap();
308        assert_eq!(score.value(), 0.5);
309    }
310
311    #[test]
312    fn test_risk_score_out_of_range() {
313        assert!(RiskScore::new(-0.1).is_err());
314        assert!(RiskScore::new(1.1).is_err());
315    }
316
317    #[test]
318    fn test_customer_builder_complete() -> Result<(), ValidationError> {
319        let customer = Customer::builder()
320            .id("CUSTOMER123")?
321            .name("João Silva")?
322            .email("joao@example.com")?
323            .phone1("+55 11 98765-4321")?
324            .tax_id("80.232.738/0001-13")?
325            .document_type(DocumentType::Cnpj)
326            .dob("1990-01-15")?
327            .created_at("2020-05-10")?
328            .new(false)
329            .vip(true)
330            .customer_type("premium")
331            .risk_level(RiskLevel::Low)
332            .risk_score(0.2)?
333            .mother_name("Maria Silva")?
334            .build()?;
335
336        assert_eq!(customer.id.as_str(), "CUSTOMER123");
337        assert_eq!(customer.name.as_str(), "João Silva");
338        assert_eq!(customer.email.as_str(), "joao@example.com");
339        assert!(customer.vip.unwrap());
340
341        Ok(())
342    }
343
344    #[test]
345    fn test_customer_builder_minimal() -> Result<(), ValidationError> {
346        let customer = Customer::builder()
347            .id("CUSTOMER456")?
348            .name("some name")?
349            .email("some@email.com")?
350            .build()?;
351
352        assert_eq!(customer.id.as_str(), "CUSTOMER456");
353        assert_eq!(customer.name.as_str(), "some name");
354        assert_eq!(customer.email.as_str(), "some@email.com");
355
356        Ok(())
357    }
358
359    #[test]
360    fn test_customer_builder_no_id() {
361        let result = Customer::builder().build();
362        assert!(result.is_err());
363    }
364
365    #[test]
366    fn test_serde_serialization() -> Result<(), Box<dyn std::error::Error>> {
367        let customer = Customer::builder()
368            .id("TEST123")?
369            .name("Test User")?
370            .email("test@example.com")?
371            .tax_id("47568576043")?
372            .build()?;
373
374        let json = serde_json::to_string(&customer)?;
375        assert!(json.contains("TEST123"));
376        assert!(json.contains("test@example.com"));
377
378        Ok(())
379    }
380
381    #[test]
382    fn test_serde_deserialization() -> Result<(), Box<dyn std::error::Error>> {
383        let json = r#"{
384            "id": "CUSTOMER789",
385            "name": "Maria Santos",
386            "email": "maria@example.com",
387            "tax_id": "98765432100"
388        }"#;
389
390        let customer: Customer = serde_json::from_str(json)?;
391        assert_eq!(customer.id.as_str(), "CUSTOMER789");
392        assert_eq!(customer.name.as_str(), "Maria Santos");
393
394        Ok(())
395    }
396
397    #[test]
398    fn test_document_type_serialization() -> Result<(), Box<dyn std::error::Error>> {
399        assert_eq!(serde_json::to_string(&DocumentType::Cpf)?, "\"CPF\"");
400        assert_eq!(serde_json::to_string(&DocumentType::Cnpj)?, "\"CNPJ\"");
401        assert_eq!(
402            serde_json::to_string(&DocumentType::CpfCnpj)?,
403            "\"CPF_CNPJ\""
404        );
405
406        Ok(())
407    }
408
409    #[test]
410    fn test_risk_level_serialization() -> Result<(), Box<dyn std::error::Error>> {
411        assert_eq!(serde_json::to_string(&RiskLevel::Low)?, "\"low\"");
412        assert_eq!(serde_json::to_string(&RiskLevel::Medium)?, "\"medium\"");
413        assert_eq!(serde_json::to_string(&RiskLevel::High)?, "\"high\"");
414
415        Ok(())
416    }
417}