nfe-core 0.1.2

Core types and error handling for NF-e
Documentation
//! Utilitários para NF-e

use crate::error::Error;

pub fn formatar_cnpj(cnpj: &str) -> String {
    if cnpj.len() != 14 {
        return cnpj.to_string();
    }
    format!(
        "{}.{}.{}/{}-{}",
        &cnpj[0..2],
        &cnpj[2..5],
        &cnpj[5..8],
        &cnpj[8..12],
        &cnpj[12..14]
    )
}

pub fn formatar_cpf(cpf: &str) -> String {
    if cpf.len() != 11 {
        return cpf.to_string();
    }
    format!(
        "{}.{}.{}-{}",
        &cpf[0..3],
        &cpf[3..6],
        &cpf[6..9],
        &cpf[9..11]
    )
}

pub fn formatar_cep(cep: &str) -> String {
    if cep.len() != 8 {
        return cep.to_string();
    }
    format!("{}-{}", &cep[0..5], &cep[5..8])
}

pub fn validar_cnpj(cnpj: &str) -> bool {
    if cnpj.len() != 14 || !cnpj.chars().all(|c| c.is_ascii_digit()) {
        return false;
    }
    if cnpj.chars().all(|c| c == cnpj.chars().next().unwrap()) {
        return false;
    }

    let numeros: Vec<u32> = cnpj.chars().map(|c| c.to_digit(10).unwrap()).collect();

    let pesos1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
    let soma1: u32 = numeros[..12]
        .iter()
        .zip(pesos1.iter())
        .map(|(a, b)| a * b)
        .sum();
    let resto1 = soma1 % 11;
    let digito1 = if resto1 < 2 { 0 } else { 11 - resto1 };
    if numeros[12] != digito1 {
        return false;
    }

    let pesos2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
    let soma2: u32 = numeros[..13]
        .iter()
        .zip(pesos2.iter())
        .map(|(a, b)| a * b)
        .sum();
    let resto2 = soma2 % 11;
    let digito2 = if resto2 < 2 { 0 } else { 11 - resto2 };
    numeros[13] == digito2
}

pub fn validar_cpf(cpf: &str) -> bool {
    if cpf.len() != 11 || !cpf.chars().all(|c| c.is_ascii_digit()) {
        return false;
    }
    if cpf.chars().all(|c| c == cpf.chars().next().unwrap()) {
        return false;
    }

    let numeros: Vec<u32> = cpf.chars().map(|c| c.to_digit(10).unwrap()).collect();

    let pesos1 = [10, 9, 8, 7, 6, 5, 4, 3, 2];
    let soma1: u32 = numeros[..9]
        .iter()
        .zip(pesos1.iter())
        .map(|(a, b)| a * b)
        .sum();
    let resto1 = soma1 % 11;
    let digito1 = if resto1 < 2 { 0 } else { 11 - resto1 };
    if numeros[9] != digito1 {
        return false;
    }

    let pesos2 = [11, 10, 9, 8, 7, 6, 5, 4, 3, 2];
    let soma2: u32 = numeros[..10]
        .iter()
        .zip(pesos2.iter())
        .map(|(a, b)| a * b)
        .sum();
    let resto2 = soma2 % 11;
    let digito2 = if resto2 < 2 { 0 } else { 11 - resto2 };
    numeros[10] == digito2
}

pub fn calcular_digito_verificador(chave: &str) -> u8 {
    let pesos = [2, 3, 4, 5, 6, 7, 8, 9];
    let mut soma = 0;
    let mut peso_idx = 0;

    for digito in chave.chars().rev() {
        let valor = digito.to_digit(10).unwrap() as u32;
        soma += valor * pesos[peso_idx] as u32;
        peso_idx = (peso_idx + 1) % 8;
    }

    let resto = soma % 11;
    if resto == 0 || resto == 1 {
        0
    } else {
        (11 - resto) as u8
    }
}

pub fn gerar_chave_acesso(
    cnpj: &str,
    estado: u8,
    serie: u16,
    numero_nfe: u32,
    tipo_emi: u8,
    codigo_numerico: u32,
) -> Result<String, Error> {
    if cnpj.len() != 14 {
        return Err(Error::InvalidCnpj);
    }

    let estado_str = format!("{:02}", estado);
    let ano_mes = chrono::Utc::now().format("%y%m").to_string();
    let modelo = "55";
    let serie_str = format!("{:03}", serie);
    let numero_str = format!("{:09}", numero_nfe);
    let tipo_emi_str = format!("{}", tipo_emi);
    let codigo_num_str = format!("{:08}", codigo_numerico);

    let base: String = format!(
        "{}{}{}{}{}{}{}{}",
        estado_str, ano_mes, cnpj, modelo, serie_str, numero_str, tipo_emi_str, codigo_num_str
    );

    let dv = calcular_digito_verificador(&base);
    Ok(format!("{}{}", base, dv))
}

pub fn formatar_chave_acesso(chave: &str) -> String {
    if chave.len() != 44 {
        return chave.to_string();
    }
    format!(
        "{}.{}.{}.{}.{}.{}.{}",
        &chave[0..2],
        &chave[2..6],
        &chave[6..20],
        &chave[20..22],
        &chave[22..25],
        &chave[25..34],
        &chave[34..44]
    )
}

pub fn validar_chave_acesso(chave: &str) -> bool {
    if chave.len() != 44 || !chave.chars().all(|c| c.is_ascii_digit()) {
        return false;
    }
    let base = &chave[..43];
    let dv_recebido: u8 = chave[43..44].parse().unwrap_or(0);
    dv_recebido == calcular_digito_verificador(base)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_formatar_cnpj() {
        assert_eq!(formatar_cnpj("12345678000199"), "12.345.678/0001-99");
    }

    #[test]
    fn test_validar_cnpj_valido() {
        assert!(validar_cnpj("12345678000195"));
        assert!(validar_cnpj("99999999999962"));
    }

    #[test]
    fn test_validar_cnpj_invalido() {
        assert!(!validar_cnpj("12345678000198"));
        assert!(!validar_cnpj("11111111111111"));
    }

    #[test]
    fn test_validar_cpf_valido() {
        assert!(validar_cpf("12345678909"));
    }

    #[test]
    fn test_calcular_digito_verificador() {
        let chave = "4215089999999999999955001000000001123456789";
        let dv = calcular_digito_verificador(chave);
        assert!(dv <= 9);
    }

    #[test]
    fn test_validar_chave_acesso() {
        let base = "4215089999999999999955001000000001123456789";
        let dv = calcular_digito_verificador(base);
        let chave = format!("{}{}", base, dv);
        assert!(validar_chave_acesso(&chave));
    }
}