Skip to main content

nfe_parser/base/item/
produto.rs

1//! Produto da Nota Fiscal Eletrônica
2
3use super::Error;
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use serde_repr::{Deserialize_repr, Serialize_repr};
6use std::str::FromStr;
7
8/// Produto da NFe
9///
10/// Contém os dados do produto ou serviço vendido na nota fiscal.
11#[derive(Debug, PartialEq, Clone)]
12pub struct Produto {
13    /// Código do produto no cadastro do contribuinte
14    pub codigo: String,
15    /// Código GTIN (EAN) do produto
16    pub gtin: Option<String>,
17    /// Descrição do produto ou serviço
18    pub descricao: String,
19    /// Código NCM (Nomenclatura Comum do Mercosul)
20    pub ncm: String,
21    /// CNPJ do fabricante
22    pub fabricante_cnpj: Option<String>,
23    /// Dados da tributação do produto
24    pub tributacao: ProdutoTributacao,
25    /// Unidade comercial
26    pub unidade: String,
27    /// Quantidade comercial
28    pub quantidade: f32,
29    /// Valor unitário comercial
30    pub valor_unitario: f32,
31    /// Valor bruto do produto
32    pub valor_bruto: f32,
33    /// Valor do frete
34    pub valor_frete: Option<f32>,
35    /// Valor do seguro
36    pub valor_seguro: Option<f32>,
37    /// Valor do desconto
38    pub valor_desconto: Option<f32>,
39    /// Outras despesas acessórias
40    pub valor_outros: Option<f32>,
41    /// Indica se o valor do produto compõe o total da NF-e
42    pub valor_compoe_total_nota: bool,
43}
44
45/// Dados referentes a tributação do produto
46#[derive(Debug, PartialEq, Clone, Serialize)]
47pub struct ProdutoTributacao {
48    /// CEST - Código Especificador da Substituição Tributária
49    pub cest: Option<String>,
50    /// Indicador de Escala Relevante
51    pub escala_relevante: Option<EscalaRelevante>,
52    /// Código de Benefício Fiscal
53    pub codigo_beneficio_fiscal: Option<String>,
54    /// Código de Exceção do IPI
55    pub codigo_excecao_ipi: Option<String>,
56    /// CFOP - Código Fiscal de Operações e Prestações
57    pub cfop: String,
58    /// Código GTIN (EAN) tributável
59    pub gtin: Option<String>,
60    /// Unidade tributável
61    pub unidade: String,
62    /// Quantidade tributável
63    pub quantidade: f32,
64    /// Valor unitário tributável
65    pub valor_unitario: f32,
66}
67
68/// Indicador de Escala Relevante
69#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize_repr, Serialize_repr)]
70#[repr(u8)]
71pub enum EscalaRelevante {
72    /// Produzido em escala relevante
73    Sim = 1,
74    /// Não produzido em escala relevante
75    Nao = 2,
76}
77
78impl FromStr for Produto {
79    type Err = Error;
80
81    fn from_str(s: &str) -> Result<Self, Self::Err> {
82        quick_xml::de::from_str(s).map_err(|e| e.into())
83    }
84}
85
86impl ToString for Produto {
87    fn to_string(&self) -> String {
88        quick_xml::se::to_string(self).expect("Falha ao serializar o produto")
89    }
90}
91
92impl<'de> Deserialize<'de> for Produto {
93    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
94    where
95        D: Deserializer<'de>,
96    {
97        // TODO: Implementar a deserialização com serde flatten
98        let prod = ProdContainer::deserialize(deserializer)?;
99
100        // Função auxiliar para verificar se o GTIN é válido
101        // "SEM GTIN" ou vazio são considerados ausência de GTIN
102        fn parse_gtin(gtin: String) -> Option<String> {
103            let trimmed = gtin.trim();
104            if trimmed.is_empty() || trimmed.eq_ignore_ascii_case("sem gtin") {
105                None
106            } else {
107                Some(gtin)
108            }
109        }
110
111        Ok(Self {
112            codigo: prod.codigo,
113            gtin: parse_gtin(prod.gtin),
114            descricao: prod.descricao,
115            ncm: prod.ncm,
116            fabricante_cnpj: prod.fabricante_cnpj,
117            unidade: prod.unidade,
118            quantidade: prod.quantidade,
119            valor_unitario: prod.valor_unitario,
120            valor_bruto: prod.valor_bruto,
121            valor_frete: prod.valor_frete,
122            valor_seguro: prod.valor_seguro,
123            valor_desconto: prod.valor_desconto,
124            valor_outros: prod.valor_outros,
125            valor_compoe_total_nota: prod.valor_compoe_total_nota == 1,
126            tributacao: ProdutoTributacao {
127                cest: prod.t_cest,
128                escala_relevante: prod.t_escala_relevante,
129                codigo_beneficio_fiscal: prod.t_codigo_beneficio_fiscal,
130                codigo_excecao_ipi: prod.t_codigo_excecao_ipi,
131                cfop: prod.t_cfop,
132                gtin: parse_gtin(prod.t_gtin),
133                unidade: prod.t_unidade,
134                quantidade: prod.t_quantidade,
135                valor_unitario: prod.t_valor_unitario,
136            },
137        })
138    }
139}
140
141impl Serialize for Produto {
142    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
143    where
144        S: Serializer,
145    {
146        let prod = ProdContainer {
147            codigo: self.codigo.clone(),
148            gtin: match &self.gtin {
149                Some(gt) => gt.clone(),
150                None => "SEM GTIN".to_string(),
151            },
152            descricao: self.descricao.clone(),
153            ncm: self.ncm.clone(),
154            fabricante_cnpj: self.fabricante_cnpj.clone(),
155            unidade: self.unidade.clone(),
156            quantidade: self.quantidade.clone(),
157            valor_unitario: self.valor_unitario.clone(),
158            valor_bruto: self.valor_bruto,
159            valor_frete: self.valor_frete,
160            valor_seguro: self.valor_seguro,
161            valor_desconto: self.valor_desconto,
162            valor_outros: self.valor_outros,
163            valor_compoe_total_nota: if self.valor_compoe_total_nota { 1 } else { 0 },
164            t_cest: self.tributacao.cest.clone(),
165            t_escala_relevante: self.tributacao.escala_relevante,
166            t_codigo_beneficio_fiscal: self.tributacao.codigo_beneficio_fiscal.clone(),
167            t_codigo_excecao_ipi: self.tributacao.codigo_excecao_ipi.clone(),
168            t_cfop: self.tributacao.cfop.clone(),
169            t_gtin: match &self.tributacao.gtin {
170                Some(gt) => gt.clone(),
171                None => "SEM GTIN".to_string(),
172            },
173            t_unidade: self.tributacao.unidade.clone(),
174            t_quantidade: self.tributacao.quantidade,
175            t_valor_unitario: self.tributacao.valor_unitario,
176        };
177
178        prod.serialize(serializer)
179    }
180}
181
182#[derive(Deserialize, Serialize)]
183#[serde(rename = "prod")]
184struct ProdContainer {
185    #[serde(rename = "cProd")]
186    pub codigo: String,
187    #[serde(rename = "cEAN")]
188    pub gtin: String,
189    #[serde(rename = "xProd")]
190    pub descricao: String,
191    #[serde(rename = "NCM")]
192    pub ncm: String,
193    #[serde(rename = "CNPJFab")]
194    #[serde(skip_serializing_if = "Option::is_none")]
195    #[serde(default)]
196    pub fabricante_cnpj: Option<String>,
197    #[serde(rename = "uCom")]
198    pub unidade: String,
199    #[serde(rename = "qCom")]
200    pub quantidade: f32,
201    #[serde(rename = "vUnCom")]
202    pub valor_unitario: f32,
203    #[serde(rename = "vProd")]
204    pub valor_bruto: f32,
205    #[serde(rename = "vFrete")]
206    #[serde(skip_serializing_if = "Option::is_none")]
207    #[serde(default)]
208    pub valor_frete: Option<f32>,
209    #[serde(rename = "vSeg")]
210    #[serde(skip_serializing_if = "Option::is_none")]
211    #[serde(default)]
212    pub valor_seguro: Option<f32>,
213    #[serde(rename = "vDesc")]
214    #[serde(skip_serializing_if = "Option::is_none")]
215    #[serde(default)]
216    pub valor_desconto: Option<f32>,
217    #[serde(rename = "vOutro")]
218    #[serde(skip_serializing_if = "Option::is_none")]
219    #[serde(default)]
220    pub valor_outros: Option<f32>,
221    #[serde(rename = "indTot")]
222    pub valor_compoe_total_nota: u8,
223
224    #[serde(rename = "CEST")]
225    #[serde(skip_serializing_if = "Option::is_none")]
226    #[serde(default)]
227    pub t_cest: Option<String>,
228    #[serde(rename = "indEscala")]
229    #[serde(skip_serializing_if = "Option::is_none")]
230    #[serde(default)]
231    pub t_escala_relevante: Option<EscalaRelevante>,
232    #[serde(rename = "cBenef")]
233    #[serde(skip_serializing_if = "Option::is_none")]
234    #[serde(default)]
235    pub t_codigo_beneficio_fiscal: Option<String>,
236    #[serde(rename = "EXTIPI")]
237    #[serde(skip_serializing_if = "Option::is_none")]
238    #[serde(default)]
239    pub t_codigo_excecao_ipi: Option<String>,
240    #[serde(rename = "CFOP")]
241    pub t_cfop: String,
242    #[serde(rename = "cEANTrib")]
243    pub t_gtin: String,
244    #[serde(rename = "uTrib")]
245    pub t_unidade: String,
246    #[serde(rename = "qTrib")]
247    pub t_quantidade: f32,
248    #[serde(rename = "vUnTrib")]
249    pub t_valor_unitario: f32,
250}
251
252impl Produto {
253    /// Cria uma nova instância de Produto
254    ///
255    /// # Argumentos
256    ///
257    /// * `codigo` - Código do produto
258    /// * `descricao` - Descrição do produto
259    /// * `ncm` - Código NCM
260    /// * `cfop` - Código CFOP
261    /// * `unidade` - Unidade comercial
262    /// * `quantidade` - Quantidade comercial
263    /// * `valor_unitario` - Valor unitário
264    /// * `valor_bruto` - Valor bruto total
265    pub fn new(
266        codigo: String,
267        descricao: String,
268        ncm: String,
269        cfop: String,
270        unidade: String,
271        quantidade: f32,
272        valor_unitario: f32,
273        valor_bruto: f32,
274    ) -> Self {
275        Produto {
276            codigo,
277            gtin: None,
278            descricao,
279            ncm,
280            fabricante_cnpj: None,
281            tributacao: ProdutoTributacao {
282                cest: None,
283                escala_relevante: None,
284                codigo_beneficio_fiscal: None,
285                codigo_excecao_ipi: None,
286                cfop,
287                gtin: None,
288                unidade: unidade.clone(),
289                quantidade,
290                valor_unitario,
291            },
292            unidade,
293            quantidade,
294            valor_unitario,
295            valor_bruto,
296            valor_frete: None,
297            valor_seguro: None,
298            valor_desconto: None,
299            valor_outros: None,
300            valor_compoe_total_nota: true,
301        }
302    }
303}