nfe_parser/base/mod.rs
1//! Base da NF-e - Estruturas fundamentais da Nota Fiscal Eletrônica
2//!
3//! Este módulo contém os tipos e estruturas base para tratamento da NF-e,
4//! independente do modelo fiscal (NF-e modelo 55 ou NFC-e modelo 65).
5//!
6//! ## Estrutura do XML da NF-e (Layout 4.00 - SEFAZ)
7//!
8//! A NF-e segue a estrutura definida no Manual de Orientação do Contribuinte (MOC):
9//!
10//! ```text
11//! <NFe>
12//! <infNFe versao="4.00" Id="NFe...">
13//! <ide> <!-- Identificação da NF-e -->
14//! <emit> <!-- Emitente -->
15//! <dest> <!-- Destinatário (opcional em alguns casos) -->
16//! <det> <!-- Detalhamento de produtos/serviços (1 a 990 itens) -->
17//! <total> <!-- Totais da NF-e -->
18//! <transp> <!-- Transporte -->
19//! <infAdic> <!-- Informações adicionais -->
20//! </infNFe>
21//! </NFe>
22//! ```
23//!
24//! ## Referências
25//!
26//! - [Manual de Orientação do Contribuinte v6.00](https://www.nfe.fazenda.gov.br/portal/listaConteudo.aspx?tipoConteudo=ndIjl+iEFdE%3D)
27//! - [Esquemas XML NF-e](https://www.nfe.fazenda.gov.br/portal/listaConteudo.aspx?tipoConteudo=BMPFMBoln3w%3D)
28
29use serde::{Deserialize, Deserializer, Serialize, Serializer};
30use std::convert::TryFrom;
31use std::fs::File;
32use std::io::Read;
33use std::str::FromStr;
34
35// Submódulos que compõem a estrutura da NF-e
36pub mod dest; // Destinatário (comprador/cliente)
37pub mod emit; // Emitente (vendedor/empresa)
38pub mod endereco; // Endereço (usado por emit e dest)
39mod error; // Tipos de erro da biblioteca
40pub mod ide; // Identificação da nota fiscal
41pub mod item; // Itens/produtos da nota
42pub mod totais; // Totalização de valores
43pub mod transporte; // Dados de transporte/frete
44
45use dest::Destinatario;
46use emit::Emitente;
47pub use error::Error;
48use ide::Identificacao;
49use item::Item;
50use totais::Totalizacao;
51use transporte::Transporte;
52
53/// Estrutura principal da Nota Fiscal Eletrônica (NF-e)
54///
55/// Esta estrutura representa uma NF-e parseada, contendo todos os grupos
56/// de informações definidos no layout 4.00 da SEFAZ.
57///
58/// A NF-e pode ser de dois modelos:
59/// - **Modelo 55 (NF-e)**: Nota Fiscal Eletrônica tradicional para operações B2B
60/// - **Modelo 65 (NFC-e)**: Nota Fiscal de Consumidor Eletrônica para varejo
61///
62/// ## Campos Principais
63///
64/// | Campo | Tag XML | Descrição |
65/// |-------|---------|-----------|
66/// | versao | @versao | Versão do layout (4.00) |
67/// | chave_acesso | @Id | Chave de 44 dígitos que identifica a nota |
68/// | ide | \<ide\> | Dados de identificação |
69/// | emit | \<emit\> | Dados do emitente |
70/// | dest | \<dest\> | Dados do destinatário |
71/// | itens | \<det\> | Lista de produtos (1 a 990) |
72/// | totais | \<total\> | Valores totalizados |
73/// | transporte | \<transp\> | Informações de frete |
74///
75/// ## Exemplo de Uso
76///
77/// ```rust,ignore
78/// use std::fs::File;
79/// use nfe::Nfe;
80///
81/// let file = File::open("nota.xml")?;
82/// let nfe = Nfe::try_from(file)?;
83///
84/// println!("Chave: {}", nfe.chave_acesso);
85/// println!("Total: R$ {:.2}", nfe.totais.valor_total);
86/// ```
87#[derive(Debug, PartialEq)]
88pub struct Nfe {
89 /// Versão do layout XML da NF-e (atualmente 4.00)
90 pub versao: VersaoLayout,
91
92 /// Chave de acesso de 44 dígitos que identifica unicamente a NF-e
93 /// Formato: UF(2) + AAMM(4) + CNPJ(14) + MOD(2) + SERIE(3) + NNF(9) + CODIGO(9) + DV(1)
94 pub chave_acesso: String,
95
96 /// Grupo de identificação da NF-e (tag <ide>)
97 /// Contém: UF, número, série, modelo, datas, tipo de emissão, etc.
98 pub ide: Identificacao,
99
100 /// Dados do emitente da nota fiscal (tag <emit>)
101 /// Contém: CNPJ, razão social, endereço, IE, etc.
102 pub emit: Emitente,
103
104 /// Dados do destinatário/comprador (tag <dest>)
105 /// Opcional em algumas operações (ex: NFC-e para consumidor não identificado)
106 pub dest: Option<Destinatario>,
107
108 /// Lista de itens/produtos da nota fiscal (tags <det>)
109 /// Cada NF-e pode conter de 1 a 990 itens
110 pub itens: Vec<Item>,
111
112 /// Totalização de valores da nota fiscal (tag <total>)
113 /// Contém: BC ICMS, valor ICMS, valor produtos, frete, desconto, total, etc.
114 pub totais: Totalizacao,
115
116 /// Informações de transporte/frete (tag <transp>)
117 /// Contém: modalidade do frete (CIF/FOB), transportador, volumes, etc.
118 pub transporte: Transporte,
119
120 /// Informações complementares de interesse do contribuinte (tag <infCpl>)
121 /// Campo de texto livre para observações adicionais
122 pub informacao_complementar: Option<String>,
123}
124
125/// Versão do layout XML da NF-e conforme definido pela SEFAZ
126///
127/// A versão do layout determina a estrutura do XML e as regras de validação.
128/// Desde 2019, a versão 4.00 é obrigatória em todo o Brasil.
129///
130/// ## Histórico de Versões
131///
132/// | Versão | Vigência | Observação |
133/// |--------|----------|------------|
134/// | 1.00 | 2005-2006 | Versão inicial |
135/// | 2.00 | 2006-2010 | Expansão nacional |
136/// | 3.00 | 2010-2017 | Eventos, cancelamento |
137/// | 3.10 | 2017-2019 | Ajustes menores |
138/// | 4.00 | 2019-atual | Versão atual obrigatória |
139///
140/// ## Referência SEFAZ
141///
142/// Tag XML: `@versao` no elemento `<infNFe>`
143#[derive(Debug, Eq, PartialEq, Copy, Clone, Deserialize)]
144pub enum VersaoLayout {
145 /// Layout 4.00 - Versão atual e obrigatória desde 2019
146 #[serde(rename = "4.00")]
147 V4_00 = 4,
148}
149
150impl FromStr for Nfe {
151 type Err = Error;
152
153 fn from_str(s: &str) -> Result<Self, Self::Err> {
154 quick_xml::de::from_str(s).map_err(|e| e.into())
155 }
156}
157
158impl TryFrom<File> for Nfe {
159 type Error = Error;
160
161 fn try_from(mut f: File) -> Result<Self, Self::Error> {
162 let mut xml = String::new();
163 f.read_to_string(&mut xml).map_err(|e| Error::Io(e))?;
164
165 xml.parse::<Nfe>()
166 }
167}
168
169impl ToString for Nfe {
170 fn to_string(&self) -> String {
171 quick_xml::se::to_string(self).expect("Falha ao serializar a nota")
172 }
173}
174
175impl<'de> Deserialize<'de> for Nfe {
176 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
177 where
178 D: Deserializer<'de>,
179 {
180 let nfe = NfeRootContainer::deserialize(deserializer)?;
181
182 Ok(Self {
183 versao: nfe.inf.versao,
184 chave_acesso: nfe.inf.chave_acesso.replace("NFe", ""),
185 ide: nfe.inf.ide,
186 emit: nfe.inf.emit,
187 dest: nfe.inf.dest,
188 itens: nfe.inf.itens,
189 totais: nfe.inf.totais,
190 transporte: nfe.inf.transporte,
191 informacao_complementar: match nfe.inf.add {
192 Some(add) => add.informacao_complementar,
193 None => None,
194 },
195 })
196 }
197}
198
199impl Serialize for Nfe {
200 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
201 where
202 S: Serializer,
203 {
204 let inf = NfeInfContainer {
205 versao: self.versao,
206 chave_acesso: format!("NFe{}", self.chave_acesso),
207 ide: self.ide.clone(),
208 emit: self.emit.clone(),
209 dest: self.dest.clone(),
210 itens: self.itens.clone(),
211 totais: self.totais.clone(),
212 transporte: self.transporte.clone(),
213 add: match self.informacao_complementar.clone() {
214 Some(ic) => Some(InfAddContainer {
215 informacao_complementar: Some(ic),
216 }),
217 None => None,
218 },
219 };
220
221 let root = NfeRootContainer { inf };
222
223 root.serialize(serializer)
224 }
225}
226
227impl Serialize for VersaoLayout {
228 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
229 where
230 S: Serializer,
231 {
232 serializer.serialize_str(match self {
233 VersaoLayout::V4_00 => "4.00",
234 })
235 }
236}
237
238/// Container auxiliar para deserialização do elemento raiz <NFe>
239///
240/// O quick-xml necessita de estruturas intermediárias para mapear
241/// a hierarquia do XML corretamente. Esta estrutura representa
242/// o elemento raiz `<NFe>` que contém `<infNFe>`.
243#[derive(Deserialize, Serialize)]
244#[serde(rename = "NFe")]
245struct NfeRootContainer {
246 /// Elemento <infNFe> que contém todas as informações da nota
247 #[serde(rename = "infNFe")]
248 pub inf: NfeInfContainer,
249}
250
251/// Container para informações adicionais (tag <infAdic>)
252///
253/// Grupo opcional que pode conter informações complementares
254/// de interesse do contribuinte e do Fisco.
255#[derive(Deserialize, Serialize)]
256struct InfAddContainer {
257 /// Informações complementares de interesse do contribuinte
258 /// Tag: <infCpl> - Máximo 5000 caracteres
259 #[serde(rename = "$unflatten=infCpl")]
260 pub informacao_complementar: Option<String>,
261}
262
263/// Container para o elemento <infNFe> - Informações da NF-e
264///
265/// Este container mapeia diretamente os atributos e elementos filhos
266/// do grupo `<infNFe>`, que é o container principal de dados da nota.
267///
268/// ## Mapeamento XML -> Rust
269///
270/// | Atributo/Tag | Campo | Descrição |
271/// |--------------|-------|-----------|
272/// | @versao | versao | Versão do layout |
273/// | @Id | chave_acesso | Chave de acesso (44 dígitos com prefixo "NFe") |
274/// | \<ide\> | ide | Identificação |
275/// | \<emit\> | emit | Emitente |
276/// | \<dest\> | dest | Destinatário |
277/// | \<det\> | itens | Itens/produtos (vetor) |
278/// | \<total\> | totais | Totalização |
279/// | \<transp\> | transporte | Transporte |
280/// | \<infAdic\> | add | Informações adicionais |
281#[derive(Deserialize, Serialize)]
282struct NfeInfContainer {
283 /// Versão do layout (atributo @versao)
284 #[serde(rename = "versao")]
285 pub versao: VersaoLayout,
286
287 /// Chave de acesso com prefixo "NFe" (atributo @Id)
288 /// Formato: "NFe" + 44 dígitos
289 #[serde(rename = "Id")]
290 pub chave_acesso: String,
291
292 /// Grupo de identificação da NF-e
293 #[serde(rename = "ide")]
294 pub ide: Identificacao,
295
296 /// Grupo de dados do emitente
297 #[serde(rename = "emit")]
298 pub emit: Emitente,
299
300 /// Grupo de dados do destinatário (opcional)
301 #[serde(rename = "dest")]
302 pub dest: Option<Destinatario>,
303
304 /// Lista de itens/detalhamento de produtos
305 /// Cada elemento <det> representa um item da nota
306 #[serde(rename = "det")]
307 pub itens: Vec<Item>,
308
309 /// Grupo de totais da NF-e
310 #[serde(rename = "total")]
311 pub totais: Totalizacao,
312
313 /// Grupo de informações de transporte
314 #[serde(rename = "transp")]
315 pub transporte: Transporte,
316
317 /// Grupo de informações adicionais (opcional)
318 #[serde(rename = "infAdic")]
319 pub add: Option<InfAddContainer>,
320}