1use super::Error;
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use serde_repr::{Deserialize_repr, Serialize_repr};
6use std::str::FromStr;
7
8#[derive(Debug, PartialEq, Clone)]
12pub struct Produto {
13 pub codigo: String,
15 pub gtin: Option<String>,
17 pub descricao: String,
19 pub ncm: String,
21 pub fabricante_cnpj: Option<String>,
23 pub tributacao: ProdutoTributacao,
25 pub unidade: String,
27 pub quantidade: f32,
29 pub valor_unitario: f32,
31 pub valor_bruto: f32,
33 pub valor_frete: Option<f32>,
35 pub valor_seguro: Option<f32>,
37 pub valor_desconto: Option<f32>,
39 pub valor_outros: Option<f32>,
41 pub valor_compoe_total_nota: bool,
43}
44
45#[derive(Debug, PartialEq, Clone, Serialize)]
47pub struct ProdutoTributacao {
48 pub cest: Option<String>,
50 pub escala_relevante: Option<EscalaRelevante>,
52 pub codigo_beneficio_fiscal: Option<String>,
54 pub codigo_excecao_ipi: Option<String>,
56 pub cfop: String,
58 pub gtin: Option<String>,
60 pub unidade: String,
62 pub quantidade: f32,
64 pub valor_unitario: f32,
66}
67
68#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize_repr, Serialize_repr)]
70#[repr(u8)]
71pub enum EscalaRelevante {
72 Sim = 1,
74 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 let prod = ProdContainer::deserialize(deserializer)?;
99
100 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 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}