use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DadosInutilizacao {
pub ano: u16,
pub cnpj: String,
pub modelo: u8,
pub serie: u16,
pub numero_inicial: u32,
pub numero_final: u32,
pub justificativa: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResultadoInutilizacao {
pub sucesso: bool,
pub id_inutilizacao: String,
pub codigo_status: u16,
pub descricao_status: String,
pub protocolo: Option<String>,
pub data_processamento: Option<String>,
pub xml_assinado: Option<String>,
}
impl DadosInutilizacao {
pub fn validar(&self) -> Result<(), Vec<String>> {
let mut erros = Vec::new();
if self.ano < 2006 || self.ano > 2099 {
erros.push(format!("Ano inválido: {}", self.ano));
}
if self.cnpj.len() != 14 || !self.cnpj.chars().all(|c| c.is_ascii_digit()) {
erros.push(format!("CNPJ inválido: {}", self.cnpj));
}
if self.modelo != 55 && self.modelo != 65 {
erros.push(format!("Modelo inválido: {} (use 55 para NF-e ou 65 para NFC-e)", self.modelo));
}
if self.serie > 999 {
erros.push(format!("Série inválida: {} (máximo 999)", self.serie));
}
if self.numero_inicial > self.numero_final {
erros.push(format!(
"Número inicial ({}) maior que o final ({})",
self.numero_inicial, self.numero_final
));
}
if self.numero_inicial == 0 {
erros.push("Número inicial não pode ser zero".to_string());
}
if self.justificativa.len() < 15 {
erros.push(format!(
"Justificativa muito curta ({} caracteres, mínimo 15)",
self.justificativa.len()
));
}
if self.justificativa.len() > 255 {
erros.push(format!(
"Justificativa muito longa ({} caracteres, máximo 255)",
self.justificativa.len()
));
}
if erros.is_empty() {
Ok(())
} else {
Err(erros)
}
}
pub fn gerar_id(&self, codigo_uf: u8) -> String {
format!(
"ID{:02}{:04}{}{:02}{:03}{:09}{:09}",
codigo_uf,
self.ano,
self.cnpj,
self.modelo,
self.serie,
self.numero_inicial,
self.numero_final
)
}
}
pub fn gerar_xml_inutilizacao(dados: &DadosInutilizacao, codigo_uf: u8, ambiente: u8) -> String {
let id = dados.gerar_id(codigo_uf);
format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<inutNFe xmlns="http://www.portalfiscal.inf.br/nfe" versao="4.00">
<infInut Id="{id}">
<tpAmb>{ambiente}</tpAmb>
<xServ>INUTILIZAR</xServ>
<cUF>{codigo_uf:02}</cUF>
<ano>{ano}</ano>
<CNPJ>{cnpj}</CNPJ>
<mod>{modelo:02}</mod>
<serie>{serie}</serie>
<nNFIni>{numero_inicial}</nNFIni>
<nNFFin>{numero_final}</nNFFin>
<xJust>{justificativa}</xJust>
</infInut>
</inutNFe>"#,
id = id,
ambiente = ambiente,
codigo_uf = codigo_uf,
ano = dados.ano,
cnpj = dados.cnpj,
modelo = dados.modelo,
serie = dados.serie,
numero_inicial = dados.numero_inicial,
numero_final = dados.numero_final,
justificativa = dados.justificativa,
)
}
pub fn parsear_resposta_inutilizacao(xml: &str) -> ResultadoInutilizacao {
let codigo_status = extrair_tag(xml, "cStat")
.and_then(|s| s.parse::<u16>().ok())
.unwrap_or(0);
let descricao = extrair_tag(xml, "xMotivo")
.unwrap_or_else(|| "Erro desconhecido".to_string());
let protocolo = extrair_tag(xml, "nProt");
let data = extrair_tag(xml, "dhRecbto");
let id = extrair_tag(xml, "Id")
.or_else(|| extrair_atributo(xml, "infInut", "Id"))
.unwrap_or_default();
let sucesso = codigo_status == 102;
ResultadoInutilizacao {
sucesso,
id_inutilizacao: id,
codigo_status,
descricao_status: descricao,
protocolo,
data_processamento: data,
xml_assinado: None,
}
}
fn extrair_tag(xml: &str, tag: &str) -> Option<String> {
let inicio = format!("<{}>", tag);
let fim = format!("</{}>", tag);
if let Some(start) = xml.find(&inicio) {
let start = start + inicio.len();
if let Some(end) = xml[start..].find(&fim) {
return Some(xml[start..start + end].to_string());
}
}
None
}
fn extrair_atributo(xml: &str, tag: &str, attr: &str) -> Option<String> {
let pattern = format!("<{}", tag);
if let Some(start) = xml.find(&pattern) {
let tag_content = &xml[start..];
if let Some(end) = tag_content.find('>') {
let attr_section = &tag_content[..end];
let attr_pattern = format!("{}=\"", attr);
if let Some(attr_start) = attr_section.find(&attr_pattern) {
let value_start = attr_start + attr_pattern.len();
if let Some(value_end) = attr_section[value_start..].find('"') {
return Some(attr_section[value_start..value_start + value_end].to_string());
}
}
}
}
None
}