nfe/base/item/
produto.rs

1//! Produtos
2
3use super::Error;
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use serde_repr::{Deserialize_repr, Serialize_repr};
6use std::str::FromStr;
7
8/// Detalhamento do produto do item
9#[derive(Debug, PartialEq, Clone)]
10pub struct Produto {
11    /// Código do produto
12    pub codigo: String,
13    /// GTIN (Global Trade Item Number) do produto, antigo código EAN ou código de barras
14    pub gtin: Option<String>,
15    /// Descrição do produto
16    pub descricao: String,
17    /// NCM - Nomenclatura Comum do Mercosul
18    pub ncm: String,
19    /// CNPJ do Fabricante da Mercadoria
20    pub fabricante_cnpj: Option<String>,
21    /// Dados sobre a tributação do produto
22    pub tributacao: ProdutoTributacao,
23    /// Unidade de medida da comercialização
24    pub unidade: String,
25    /// Quantidade da comercialização do produto
26    pub quantidade: f32,
27    /// Valor unitário do produto
28    pub valor_unitario: f32,
29    /// Valor total bruto do produto. ICMS incluso
30    pub valor_bruto: f32,
31    /// Valor total do frete do produto
32    pub valor_frete: Option<f32>,
33    /// Valor total do seguro do produto
34    pub valor_seguro: Option<f32>,
35    /// Valor total desconto
36    pub valor_desconto: Option<f32>,
37    /// Outras despesas acessórias
38    pub valor_outros: Option<f32>,
39    /// Indica se valor bruto entra no valor total da NF-e
40    pub valor_compoe_total_nota: bool,
41}
42
43/// Dados sobre a tributação do produto
44#[derive(Debug, PartialEq, Clone)]
45pub struct ProdutoTributacao {
46    /// CEST - Código Especificador da Substituição Tributária
47    pub cest: Option<String>,
48    /// Indicador de Produção em escala relevante
49    pub escala_relevante: Option<EscalaRelevante>,
50    /// Código de Benefício Fiscal na UF aplicado ao item
51    pub codigo_beneficio_fiscal: Option<String>,
52    /// Código Exceção da Tabela de IPI
53    pub codigo_excecao_ipi: Option<String>,
54    /// Código Fiscal de Operações e Prestações
55    pub cfop: String,
56    /// GTIN (Global Trade Item Number) da unidade tributável do produto
57    pub gtin: Option<String>,
58    /// Unidade tributável
59    pub unidade: String,
60    /// Quantidade tributável
61    pub quantidade: f32,
62    /// Valor unitário de tributação
63    pub valor_unitario: f32,
64}
65
66/// Indicador de Produção em escala relevante
67#[derive(Debug, Eq, PartialEq, Copy, Clone, Deserialize_repr, Serialize_repr)]
68#[repr(u8)]
69pub enum EscalaRelevante {
70    Sim = 1,
71    Nao = 2,
72}
73
74impl FromStr for Produto {
75    type Err = Error;
76
77    fn from_str(s: &str) -> Result<Self, Self::Err> {
78        quick_xml::de::from_str(s).map_err(|e| e.into())
79    }
80}
81
82impl ToString for Produto {
83    fn to_string(&self) -> String {
84        quick_xml::se::to_string(self).expect("Falha ao serializar o produto")
85    }
86}
87
88impl<'de> Deserialize<'de> for Produto {
89    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
90    where
91        D: Deserializer<'de>,
92    {
93        // TODO: voltar a tentar usar o serde flatten
94        let prod = ProdContainer::deserialize(deserializer)?;
95
96        Ok(Self {
97            codigo: prod.codigo,
98            gtin: match prod.gtin.to_lowercase().trim() {
99                "sem gtin" => None,
100                "" => None,
101                _ => Some(prod.gtin),
102            },
103            descricao: prod.descricao,
104            ncm: prod.ncm,
105            fabricante_cnpj: prod.fabricante_cnpj,
106            unidade: prod.unidade,
107            quantidade: prod.quantidade,
108            valor_unitario: prod.valor_unitario,
109            valor_bruto: prod.valor_bruto,
110            valor_frete: prod.valor_frete,
111            valor_seguro: prod.valor_seguro,
112            valor_desconto: prod.valor_desconto,
113            valor_outros: prod.valor_outros,
114            valor_compoe_total_nota: prod.valor_compoe_total_nota == 1,
115            tributacao: ProdutoTributacao {
116                cest: prod.t_cest,
117                escala_relevante: prod.t_escala_relevante,
118                codigo_beneficio_fiscal: prod.t_codigo_beneficio_fiscal,
119                codigo_excecao_ipi: prod.t_codigo_excecao_ipi,
120                cfop: prod.t_cfop,
121                gtin: match prod.t_gtin.to_lowercase().trim() {
122                    "sem gtin" => None,
123                    "" => None,
124                    _ => Some(prod.t_gtin),
125                },
126                unidade: prod.t_unidade,
127                quantidade: prod.t_quantidade,
128                valor_unitario: prod.t_valor_unitario,
129            },
130        })
131    }
132}
133
134impl Serialize for Produto {
135    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
136    where
137        S: Serializer,
138    {
139        let prod = ProdContainer {
140            codigo: self.codigo.clone(),
141            gtin: match &self.gtin {
142                Some(gt) => gt.clone(),
143                None => "SEM GTIN".to_string(),
144            },
145            descricao: self.descricao.clone(),
146            ncm: self.ncm.clone(),
147            fabricante_cnpj: self.fabricante_cnpj.clone(),
148            unidade: self.unidade.clone(),
149            quantidade: self.quantidade.clone(),
150            valor_unitario: self.valor_unitario.clone(),
151            valor_bruto: self.valor_bruto,
152            valor_frete: self.valor_frete,
153            valor_seguro: self.valor_seguro,
154            valor_desconto: self.valor_desconto,
155            valor_outros: self.valor_outros,
156            valor_compoe_total_nota: if self.valor_compoe_total_nota { 1 } else { 0 },
157            t_cest: self.tributacao.cest.clone(),
158            t_escala_relevante: self.tributacao.escala_relevante,
159            t_codigo_beneficio_fiscal: self.tributacao.codigo_beneficio_fiscal.clone(),
160            t_codigo_excecao_ipi: self.tributacao.codigo_excecao_ipi.clone(),
161            t_cfop: self.tributacao.cfop.clone(),
162            t_gtin: match &self.tributacao.gtin {
163                Some(gt) => gt.clone(),
164                None => "SEM GTIN".to_string(),
165            },
166            t_unidade: self.tributacao.unidade.clone(),
167            t_quantidade: self.tributacao.quantidade,
168            t_valor_unitario: self.tributacao.valor_unitario,
169        };
170
171        prod.serialize(serializer)
172    }
173}
174
175#[derive(Deserialize, Serialize)]
176#[serde(rename = "prod")]
177struct ProdContainer {
178    #[serde(rename = "$unflatten=cProd")]
179    pub codigo: String,
180    #[serde(rename = "$unflatten=cEAN")]
181    pub gtin: String,
182    #[serde(rename = "$unflatten=xProd")]
183    pub descricao: String,
184    #[serde(rename = "$unflatten=NCM")]
185    pub ncm: String,
186    #[serde(rename = "$unflatten=CNPJFab")]
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub fabricante_cnpj: Option<String>,
189    #[serde(rename = "$unflatten=uCom")]
190    pub unidade: String,
191    #[serde(rename = "$unflatten=qCom")]
192    pub quantidade: f32,
193    #[serde(rename = "$unflatten=vUnCom")]
194    pub valor_unitario: f32,
195    #[serde(rename = "$unflatten=vProd")]
196    pub valor_bruto: f32,
197    #[serde(rename = "$unflatten=vFrete")]
198    #[serde(skip_serializing_if = "Option::is_none")]
199    pub valor_frete: Option<f32>,
200    #[serde(rename = "$unflatten=vDesc")]
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub valor_seguro: Option<f32>,
203    #[serde(rename = "$unflatten=vSeg")]
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub valor_desconto: Option<f32>,
206    #[serde(rename = "$unflatten=vOutro")]
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub valor_outros: Option<f32>,
209    #[serde(rename = "$unflatten=indTot")]
210    pub valor_compoe_total_nota: u8,
211
212    #[serde(rename = "$unflatten=CEST")]
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub t_cest: Option<String>,
215    #[serde(rename = "$unflatten=indEscala")]
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub t_escala_relevante: Option<EscalaRelevante>,
218    #[serde(rename = "$unflatten=cBenef")]
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub t_codigo_beneficio_fiscal: Option<String>,
221    #[serde(rename = "$unflatten=EXTIPI")]
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub t_codigo_excecao_ipi: Option<String>,
224    #[serde(rename = "$unflatten=CFOP")]
225    pub t_cfop: String,
226    #[serde(rename = "$unflatten=cEANTrib")]
227    pub t_gtin: String,
228    #[serde(rename = "$unflatten=uTrib")]
229    pub t_unidade: String,
230    #[serde(rename = "$unflatten=qTrib")]
231    pub t_quantidade: f32,
232    #[serde(rename = "$unflatten=vUnTrib")]
233    pub t_valor_unitario: f32,
234}