Skip to main content

nfe_core/
utils.rs

1//! Utilitários para NF-e
2
3use crate::error::Error;
4
5pub fn formatar_cnpj(cnpj: &str) -> String {
6    if cnpj.len() != 14 {
7        return cnpj.to_string();
8    }
9    format!(
10        "{}.{}.{}/{}-{}",
11        &cnpj[0..2],
12        &cnpj[2..5],
13        &cnpj[5..8],
14        &cnpj[8..12],
15        &cnpj[12..14]
16    )
17}
18
19pub fn formatar_cpf(cpf: &str) -> String {
20    if cpf.len() != 11 {
21        return cpf.to_string();
22    }
23    format!(
24        "{}.{}.{}-{}",
25        &cpf[0..3],
26        &cpf[3..6],
27        &cpf[6..9],
28        &cpf[9..11]
29    )
30}
31
32pub fn formatar_cep(cep: &str) -> String {
33    if cep.len() != 8 {
34        return cep.to_string();
35    }
36    format!("{}-{}", &cep[0..5], &cep[5..8])
37}
38
39pub fn validar_cnpj(cnpj: &str) -> bool {
40    if cnpj.len() != 14 || !cnpj.chars().all(|c| c.is_ascii_digit()) {
41        return false;
42    }
43    if cnpj.chars().all(|c| c == cnpj.chars().next().unwrap()) {
44        return false;
45    }
46
47    let numeros: Vec<u32> = cnpj.chars().map(|c| c.to_digit(10).unwrap()).collect();
48
49    let pesos1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
50    let soma1: u32 = numeros[..12]
51        .iter()
52        .zip(pesos1.iter())
53        .map(|(a, b)| a * b)
54        .sum();
55    let resto1 = soma1 % 11;
56    let digito1 = if resto1 < 2 { 0 } else { 11 - resto1 };
57    if numeros[12] != digito1 {
58        return false;
59    }
60
61    let pesos2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
62    let soma2: u32 = numeros[..13]
63        .iter()
64        .zip(pesos2.iter())
65        .map(|(a, b)| a * b)
66        .sum();
67    let resto2 = soma2 % 11;
68    let digito2 = if resto2 < 2 { 0 } else { 11 - resto2 };
69    numeros[13] == digito2
70}
71
72pub fn validar_cpf(cpf: &str) -> bool {
73    if cpf.len() != 11 || !cpf.chars().all(|c| c.is_ascii_digit()) {
74        return false;
75    }
76    if cpf.chars().all(|c| c == cpf.chars().next().unwrap()) {
77        return false;
78    }
79
80    let numeros: Vec<u32> = cpf.chars().map(|c| c.to_digit(10).unwrap()).collect();
81
82    let pesos1 = [10, 9, 8, 7, 6, 5, 4, 3, 2];
83    let soma1: u32 = numeros[..9]
84        .iter()
85        .zip(pesos1.iter())
86        .map(|(a, b)| a * b)
87        .sum();
88    let resto1 = soma1 % 11;
89    let digito1 = if resto1 < 2 { 0 } else { 11 - resto1 };
90    if numeros[9] != digito1 {
91        return false;
92    }
93
94    let pesos2 = [11, 10, 9, 8, 7, 6, 5, 4, 3, 2];
95    let soma2: u32 = numeros[..10]
96        .iter()
97        .zip(pesos2.iter())
98        .map(|(a, b)| a * b)
99        .sum();
100    let resto2 = soma2 % 11;
101    let digito2 = if resto2 < 2 { 0 } else { 11 - resto2 };
102    numeros[10] == digito2
103}
104
105pub fn calcular_digito_verificador(chave: &str) -> u8 {
106    let pesos = [2, 3, 4, 5, 6, 7, 8, 9];
107    let mut soma = 0;
108    let mut peso_idx = 0;
109
110    for digito in chave.chars().rev() {
111        let valor = digito.to_digit(10).unwrap() as u32;
112        soma += valor * pesos[peso_idx] as u32;
113        peso_idx = (peso_idx + 1) % 8;
114    }
115
116    let resto = soma % 11;
117    if resto == 0 || resto == 1 {
118        0
119    } else {
120        (11 - resto) as u8
121    }
122}
123
124pub fn gerar_chave_acesso(
125    cnpj: &str,
126    estado: u8,
127    serie: u16,
128    numero_nfe: u32,
129    tipo_emi: u8,
130    codigo_numerico: u32,
131) -> Result<String, Error> {
132    if cnpj.len() != 14 {
133        return Err(Error::InvalidCnpj);
134    }
135
136    let estado_str = format!("{:02}", estado);
137    let ano_mes = chrono::Utc::now().format("%y%m").to_string();
138    let modelo = "55";
139    let serie_str = format!("{:03}", serie);
140    let numero_str = format!("{:09}", numero_nfe);
141    let tipo_emi_str = format!("{}", tipo_emi);
142    let codigo_num_str = format!("{:08}", codigo_numerico);
143
144    let base: String = format!(
145        "{}{}{}{}{}{}{}{}",
146        estado_str, ano_mes, cnpj, modelo, serie_str, numero_str, tipo_emi_str, codigo_num_str
147    );
148
149    let dv = calcular_digito_verificador(&base);
150    Ok(format!("{}{}", base, dv))
151}
152
153pub fn formatar_chave_acesso(chave: &str) -> String {
154    if chave.len() != 44 {
155        return chave.to_string();
156    }
157    format!(
158        "{}.{}.{}.{}.{}.{}.{}",
159        &chave[0..2],
160        &chave[2..6],
161        &chave[6..20],
162        &chave[20..22],
163        &chave[22..25],
164        &chave[25..34],
165        &chave[34..44]
166    )
167}
168
169pub fn validar_chave_acesso(chave: &str) -> bool {
170    if chave.len() != 44 || !chave.chars().all(|c| c.is_ascii_digit()) {
171        return false;
172    }
173    let base = &chave[..43];
174    let dv_recebido: u8 = chave[43..44].parse().unwrap_or(0);
175    dv_recebido == calcular_digito_verificador(base)
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn test_formatar_cnpj() {
184        assert_eq!(formatar_cnpj("12345678000199"), "12.345.678/0001-99");
185    }
186
187    #[test]
188    fn test_validar_cnpj_valido() {
189        assert!(validar_cnpj("12345678000195"));
190        assert!(validar_cnpj("99999999999962"));
191    }
192
193    #[test]
194    fn test_validar_cnpj_invalido() {
195        assert!(!validar_cnpj("12345678000198"));
196        assert!(!validar_cnpj("11111111111111"));
197    }
198
199    #[test]
200    fn test_validar_cpf_valido() {
201        assert!(validar_cpf("12345678909"));
202    }
203
204    #[test]
205    fn test_calcular_digito_verificador() {
206        let chave = "4215089999999999999955001000000001123456789";
207        let dv = calcular_digito_verificador(chave);
208        assert!(dv <= 9);
209    }
210
211    #[test]
212    fn test_validar_chave_acesso() {
213        let base = "4215089999999999999955001000000001123456789";
214        let dv = calcular_digito_verificador(base);
215        let chave = format!("{}{}", base, dv);
216        assert!(validar_chave_acesso(&chave));
217    }
218}