use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[repr(u32)]
pub enum TipoManifestacao {
CienciaOperacao = 210210,
ConfirmacaoOperacao = 210200,
DesconhecimentoOperacao = 210220,
OperacaoNaoRealizada = 210240,
}
impl TipoManifestacao {
pub fn descricao(&self) -> &'static str {
match self {
Self::CienciaOperacao => "Ciência da Operação",
Self::ConfirmacaoOperacao => "Confirmação da Operação",
Self::DesconhecimentoOperacao => "Desconhecimento da Operação",
Self::OperacaoNaoRealizada => "Operação não Realizada",
}
}
pub fn codigo(&self) -> u32 {
*self as u32
}
pub fn justificativa_obrigatoria(&self) -> bool {
matches!(self, Self::OperacaoNaoRealizada)
}
pub fn from_codigo(codigo: u32) -> Option<Self> {
match codigo {
210210 => Some(Self::CienciaOperacao),
210200 => Some(Self::ConfirmacaoOperacao),
210220 => Some(Self::DesconhecimentoOperacao),
210240 => Some(Self::OperacaoNaoRealizada),
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DadosManifestacao {
pub chave_acesso: String,
pub cnpj_destinatario: String,
pub tipo_manifestacao: TipoManifestacao,
pub justificativa: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResultadoManifestacao {
pub sucesso: bool,
pub codigo_status: u16,
pub descricao_status: String,
pub protocolo: Option<String>,
pub data_registro: Option<String>,
pub tipo_evento: u32,
}
impl DadosManifestacao {
pub fn validar(&self) -> Result<(), Vec<String>> {
let mut erros = Vec::new();
if self.chave_acesso.len() != 44 {
erros.push(format!(
"Chave de acesso inválida: {} dígitos (esperado 44)",
self.chave_acesso.len()
));
}
if !self.chave_acesso.chars().all(|c| c.is_ascii_digit()) {
erros.push("Chave de acesso deve conter apenas números".to_string());
}
if self.cnpj_destinatario.len() != 14 {
erros.push(format!(
"CNPJ inválido: {} dígitos (esperado 14)",
self.cnpj_destinatario.len()
));
}
if self.tipo_manifestacao.justificativa_obrigatoria() {
match &self.justificativa {
None => {
erros.push("Justificativa é obrigatória para Operação não Realizada".to_string());
}
Some(j) if j.len() < 15 => {
erros.push(format!(
"Justificativa muito curta ({} caracteres, mínimo 15)",
j.len()
));
}
Some(j) if j.len() > 255 => {
erros.push(format!(
"Justificativa muito longa ({} caracteres, máximo 255)",
j.len()
));
}
_ => {}
}
}
if erros.is_empty() {
Ok(())
} else {
Err(erros)
}
}
pub fn gerar_id(&self, sequencia: u8) -> String {
format!(
"ID{}{}{}",
self.tipo_manifestacao.codigo(),
self.chave_acesso,
format!("{:02}", sequencia)
)
}
}
pub fn gerar_xml_manifestacao(dados: &DadosManifestacao, ambiente: u8, sequencia: u8) -> String {
let id = dados.gerar_id(sequencia);
let tipo_evento = dados.tipo_manifestacao.codigo();
let desc_evento = dados.tipo_manifestacao.descricao();
let codigo_uf = &dados.chave_acesso[0..2];
let det_evento = if let Some(just) = &dados.justificativa {
format!(
r#"<detEvento versao="1.00">
<descEvento>{}</descEvento>
<xJust>{}</xJust>
</detEvento>"#,
desc_evento, just
)
} else {
format!(
r#"<detEvento versao="1.00">
<descEvento>{}</descEvento>
</detEvento>"#,
desc_evento
)
};
format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<envEvento xmlns="http://www.portalfiscal.inf.br/nfe" versao="1.00">
<idLote>1</idLote>
<evento versao="1.00">
<infEvento Id="{id}">
<cOrgao>{codigo_uf}</cOrgao>
<tpAmb>{ambiente}</tpAmb>
<CNPJ>{cnpj}</CNPJ>
<chNFe>{chave}</chNFe>
<dhEvento>{data}</dhEvento>
<tpEvento>{tipo_evento}</tpEvento>
<nSeqEvento>{sequencia}</nSeqEvento>
<verEvento>1.00</verEvento>
{det_evento}
</infEvento>
</evento>
</envEvento>"#,
id = id,
codigo_uf = codigo_uf,
ambiente = ambiente,
cnpj = dados.cnpj_destinatario,
chave = dados.chave_acesso,
data = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S-03:00"),
tipo_evento = tipo_evento,
sequencia = sequencia,
det_evento = det_evento,
)
}
pub fn parsear_resposta_manifestacao(xml: &str) -> ResultadoManifestacao {
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_registro = extrair_tag(xml, "dhRegEvento");
let tipo_evento = extrair_tag(xml, "tpEvento")
.and_then(|s| s.parse::<u32>().ok())
.unwrap_or(0);
let sucesso = codigo_status == 135 || codigo_status == 136;
ResultadoManifestacao {
sucesso,
codigo_status,
descricao_status: descricao,
protocolo,
data_registro,
tipo_evento,
}
}
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
}