Skip to main content

fiscal_core/xml_builder/
det.rs

1//! Build `<det>` (item detail) elements of the NF-e XML.
2
3use crate::FiscalError;
4use crate::format_utils::{format_cents, format_decimal};
5use crate::newtypes::{Cents, Rate, Rate4};
6use crate::tax_icms::{self, IcmsCsosn, IcmsCst, IcmsTotals, IcmsVariant};
7use crate::tax_pis_cofins_ipi::{self, CofinsData, IiData, IpiData, PisData};
8use crate::types::{InvoiceBuildData, InvoiceItemData, TaxRegime};
9use crate::xml_utils::{TagContent, tag};
10
11/// Result from building a single `<det>` element.
12#[derive(Debug, Clone)]
13pub struct DetResult {
14    /// The serialised `<det>` XML string.
15    pub xml: String,
16    /// Accumulated ICMS totals contributed by this item.
17    pub icms_totals: IcmsTotals,
18    /// IPI value in cents contributed by this item.
19    pub v_ipi: i64,
20    /// PIS value in cents contributed by this item.
21    pub v_pis: i64,
22    /// COFINS value in cents contributed by this item.
23    pub v_cofins: i64,
24    /// II (import tax) value in cents contributed by this item.
25    pub v_ii: i64,
26}
27
28/// Map an invoice item's ICMS fields to the correct typed [`IcmsVariant`].
29fn build_icms_variant(
30    item: &InvoiceItemData,
31    is_simples: bool,
32) -> Result<IcmsVariant, FiscalError> {
33    let orig = item.orig.clone().unwrap_or_else(|| "0".to_string());
34
35    if is_simples {
36        let csosn_code = if item.icms_cst.is_empty() {
37            "102"
38        } else {
39            item.icms_cst.as_str()
40        };
41
42        let csosn = match csosn_code {
43            "101" => IcmsCsosn::Csosn101 {
44                orig,
45                csosn: csosn_code.to_string(),
46                p_cred_sn: item.icms_p_cred_sn.ok_or_else(|| {
47                    FiscalError::MissingRequiredField {
48                        field: "pCredSN".to_string(),
49                    }
50                })?,
51                v_cred_icms_sn: item.icms_v_cred_icms_sn.ok_or_else(|| {
52                    FiscalError::MissingRequiredField {
53                        field: "vCredICMSSN".to_string(),
54                    }
55                })?,
56            },
57            "102" | "103" | "300" | "400" => IcmsCsosn::Csosn102 {
58                orig,
59                csosn: csosn_code.to_string(),
60            },
61            "201" => IcmsCsosn::Csosn201 {
62                orig,
63                csosn: csosn_code.to_string(),
64                mod_bc_st: item.icms_mod_bc_st.map(|v| v.to_string()).ok_or_else(|| {
65                    FiscalError::MissingRequiredField {
66                        field: "modBCST".to_string(),
67                    }
68                })?,
69                p_mva_st: item.icms_p_mva_st,
70                p_red_bc_st: item.icms_red_bc_st,
71                v_bc_st: item
72                    .icms_v_bc_st
73                    .ok_or_else(|| FiscalError::MissingRequiredField {
74                        field: "vBCST".to_string(),
75                    })?,
76                p_icms_st: item.icms_p_icms_st.ok_or_else(|| {
77                    FiscalError::MissingRequiredField {
78                        field: "pICMSST".to_string(),
79                    }
80                })?,
81                v_icms_st: item.icms_v_icms_st.ok_or_else(|| {
82                    FiscalError::MissingRequiredField {
83                        field: "vICMSST".to_string(),
84                    }
85                })?,
86                v_bc_fcp_st: item.icms_v_bc_fcp_st,
87                p_fcp_st: item.icms_p_fcp_st,
88                v_fcp_st: item.icms_v_fcp_st,
89                p_cred_sn: item.icms_p_cred_sn,
90                v_cred_icms_sn: item.icms_v_cred_icms_sn,
91            },
92            "202" | "203" => IcmsCsosn::Csosn202 {
93                orig,
94                csosn: csosn_code.to_string(),
95                mod_bc_st: item.icms_mod_bc_st.map(|v| v.to_string()).ok_or_else(|| {
96                    FiscalError::MissingRequiredField {
97                        field: "modBCST".to_string(),
98                    }
99                })?,
100                p_mva_st: item.icms_p_mva_st,
101                p_red_bc_st: item.icms_red_bc_st,
102                v_bc_st: item
103                    .icms_v_bc_st
104                    .ok_or_else(|| FiscalError::MissingRequiredField {
105                        field: "vBCST".to_string(),
106                    })?,
107                p_icms_st: item.icms_p_icms_st.ok_or_else(|| {
108                    FiscalError::MissingRequiredField {
109                        field: "pICMSST".to_string(),
110                    }
111                })?,
112                v_icms_st: item.icms_v_icms_st.ok_or_else(|| {
113                    FiscalError::MissingRequiredField {
114                        field: "vICMSST".to_string(),
115                    }
116                })?,
117                v_bc_fcp_st: item.icms_v_bc_fcp_st,
118                p_fcp_st: item.icms_p_fcp_st,
119                v_fcp_st: item.icms_v_fcp_st,
120            },
121            "500" => IcmsCsosn::Csosn500 {
122                orig,
123                csosn: csosn_code.to_string(),
124                v_bc_st_ret: None,
125                p_st: None,
126                v_icms_substituto: item.icms_v_icms_substituto,
127                v_icms_st_ret: None,
128                v_bc_fcp_st_ret: None,
129                p_fcp_st_ret: None,
130                v_fcp_st_ret: None,
131                p_red_bc_efet: None,
132                v_bc_efet: None,
133                p_icms_efet: None,
134                v_icms_efet: None,
135            },
136            "900" => IcmsCsosn::Csosn900 {
137                orig,
138                csosn: csosn_code.to_string(),
139                mod_bc: item.icms_mod_bc.map(|v| v.to_string()),
140                v_bc: Some(item.total_price),
141                p_red_bc: item.icms_red_bc,
142                p_icms: Some(item.icms_rate),
143                v_icms: Some(item.icms_amount),
144                mod_bc_st: item.icms_mod_bc_st.map(|v| v.to_string()),
145                p_mva_st: item.icms_p_mva_st,
146                p_red_bc_st: item.icms_red_bc_st,
147                v_bc_st: item.icms_v_bc_st,
148                p_icms_st: item.icms_p_icms_st,
149                v_icms_st: item.icms_v_icms_st,
150                v_bc_fcp_st: item.icms_v_bc_fcp_st,
151                p_fcp_st: item.icms_p_fcp_st,
152                v_fcp_st: item.icms_v_fcp_st,
153                p_cred_sn: item.icms_p_cred_sn,
154                v_cred_icms_sn: item.icms_v_cred_icms_sn,
155            },
156            other => return Err(FiscalError::UnsupportedIcmsCsosn(other.to_string())),
157        };
158        Ok(csosn.into())
159    } else {
160        let cst_code = item.icms_cst.as_str();
161        let cst = match cst_code {
162            "00" => IcmsCst::Cst00 {
163                orig,
164                mod_bc: item
165                    .icms_mod_bc
166                    .map(|v| v.to_string())
167                    .unwrap_or_else(|| "3".to_string()),
168                v_bc: item.total_price,
169                p_icms: item.icms_rate,
170                v_icms: item.icms_amount,
171                p_fcp: item.icms_p_fcp,
172                v_fcp: item.icms_v_fcp,
173            },
174            "10" => IcmsCst::Cst10 {
175                orig,
176                mod_bc: item
177                    .icms_mod_bc
178                    .map(|v| v.to_string())
179                    .unwrap_or_else(|| "3".to_string()),
180                v_bc: item.total_price,
181                p_icms: item.icms_rate,
182                v_icms: item.icms_amount,
183                v_bc_fcp: item.icms_v_bc_fcp,
184                p_fcp: item.icms_p_fcp,
185                v_fcp: item.icms_v_fcp,
186                mod_bc_st: item.icms_mod_bc_st.map(|v| v.to_string()).ok_or_else(|| {
187                    FiscalError::MissingRequiredField {
188                        field: "modBCST".to_string(),
189                    }
190                })?,
191                p_mva_st: item.icms_p_mva_st,
192                p_red_bc_st: item.icms_red_bc_st,
193                v_bc_st: item
194                    .icms_v_bc_st
195                    .ok_or_else(|| FiscalError::MissingRequiredField {
196                        field: "vBCST".to_string(),
197                    })?,
198                p_icms_st: item.icms_p_icms_st.ok_or_else(|| {
199                    FiscalError::MissingRequiredField {
200                        field: "pICMSST".to_string(),
201                    }
202                })?,
203                v_icms_st: item.icms_v_icms_st.ok_or_else(|| {
204                    FiscalError::MissingRequiredField {
205                        field: "vICMSST".to_string(),
206                    }
207                })?,
208                v_bc_fcp_st: item.icms_v_bc_fcp_st,
209                p_fcp_st: item.icms_p_fcp_st,
210                v_fcp_st: item.icms_v_fcp_st,
211                v_icms_st_deson: None,
212                mot_des_icms_st: None,
213            },
214            "20" => IcmsCst::Cst20 {
215                orig,
216                mod_bc: item
217                    .icms_mod_bc
218                    .map(|v| v.to_string())
219                    .unwrap_or_else(|| "3".to_string()),
220                p_red_bc: item.icms_red_bc.unwrap_or(Rate(0)),
221                v_bc: item.total_price,
222                p_icms: item.icms_rate,
223                v_icms: item.icms_amount,
224                v_bc_fcp: item.icms_v_bc_fcp,
225                p_fcp: item.icms_p_fcp,
226                v_fcp: item.icms_v_fcp,
227                v_icms_deson: item.icms_v_icms_deson,
228                mot_des_icms: item.icms_mot_des_icms.map(|v| v.to_string()),
229                ind_deduz_deson: None,
230            },
231            "30" => IcmsCst::Cst30 {
232                orig,
233                mod_bc_st: item.icms_mod_bc_st.map(|v| v.to_string()).ok_or_else(|| {
234                    FiscalError::MissingRequiredField {
235                        field: "modBCST".to_string(),
236                    }
237                })?,
238                p_mva_st: item.icms_p_mva_st,
239                p_red_bc_st: item.icms_red_bc_st,
240                v_bc_st: item
241                    .icms_v_bc_st
242                    .ok_or_else(|| FiscalError::MissingRequiredField {
243                        field: "vBCST".to_string(),
244                    })?,
245                p_icms_st: item.icms_p_icms_st.ok_or_else(|| {
246                    FiscalError::MissingRequiredField {
247                        field: "pICMSST".to_string(),
248                    }
249                })?,
250                v_icms_st: item.icms_v_icms_st.ok_or_else(|| {
251                    FiscalError::MissingRequiredField {
252                        field: "vICMSST".to_string(),
253                    }
254                })?,
255                v_bc_fcp_st: item.icms_v_bc_fcp_st,
256                p_fcp_st: item.icms_p_fcp_st,
257                v_fcp_st: item.icms_v_fcp_st,
258                v_icms_deson: item.icms_v_icms_deson,
259                mot_des_icms: item.icms_mot_des_icms.map(|v| v.to_string()),
260                ind_deduz_deson: None,
261            },
262            "40" => IcmsCst::Cst40 {
263                orig,
264                v_icms_deson: item.icms_v_icms_deson,
265                mot_des_icms: item.icms_mot_des_icms.map(|v| v.to_string()),
266                ind_deduz_deson: None,
267            },
268            "41" => IcmsCst::Cst41 {
269                orig,
270                v_icms_deson: item.icms_v_icms_deson,
271                mot_des_icms: item.icms_mot_des_icms.map(|v| v.to_string()),
272                ind_deduz_deson: None,
273            },
274            "50" => IcmsCst::Cst50 {
275                orig,
276                v_icms_deson: item.icms_v_icms_deson,
277                mot_des_icms: item.icms_mot_des_icms.map(|v| v.to_string()),
278                ind_deduz_deson: None,
279            },
280            "51" => IcmsCst::Cst51 {
281                orig,
282                mod_bc: item.icms_mod_bc.map(|v| v.to_string()),
283                p_red_bc: item.icms_red_bc,
284                c_benef_rbc: None,
285                v_bc: Some(item.total_price),
286                p_icms: Some(item.icms_rate),
287                v_icms_op: None,
288                p_dif: None,
289                v_icms_dif: None,
290                v_icms: Some(item.icms_amount),
291                v_bc_fcp: item.icms_v_bc_fcp,
292                p_fcp: item.icms_p_fcp,
293                v_fcp: item.icms_v_fcp,
294                p_fcp_dif: None,
295                v_fcp_dif: None,
296                v_fcp_efet: None,
297            },
298            "60" => IcmsCst::Cst60 {
299                orig,
300                v_bc_st_ret: None,
301                p_st: None,
302                v_icms_substituto: item.icms_v_icms_substituto,
303                v_icms_st_ret: None,
304                v_bc_fcp_st_ret: None,
305                p_fcp_st_ret: None,
306                v_fcp_st_ret: None,
307                p_red_bc_efet: None,
308                v_bc_efet: None,
309                p_icms_efet: None,
310                v_icms_efet: None,
311            },
312            "70" => IcmsCst::Cst70 {
313                orig,
314                mod_bc: item
315                    .icms_mod_bc
316                    .map(|v| v.to_string())
317                    .unwrap_or_else(|| "3".to_string()),
318                p_red_bc: item.icms_red_bc.unwrap_or(Rate(0)),
319                v_bc: item.total_price,
320                p_icms: item.icms_rate,
321                v_icms: item.icms_amount,
322                v_bc_fcp: item.icms_v_bc_fcp,
323                p_fcp: item.icms_p_fcp,
324                v_fcp: item.icms_v_fcp,
325                mod_bc_st: item.icms_mod_bc_st.map(|v| v.to_string()).ok_or_else(|| {
326                    FiscalError::MissingRequiredField {
327                        field: "modBCST".to_string(),
328                    }
329                })?,
330                p_mva_st: item.icms_p_mva_st,
331                p_red_bc_st: item.icms_red_bc_st,
332                v_bc_st: item
333                    .icms_v_bc_st
334                    .ok_or_else(|| FiscalError::MissingRequiredField {
335                        field: "vBCST".to_string(),
336                    })?,
337                p_icms_st: item.icms_p_icms_st.ok_or_else(|| {
338                    FiscalError::MissingRequiredField {
339                        field: "pICMSST".to_string(),
340                    }
341                })?,
342                v_icms_st: item.icms_v_icms_st.ok_or_else(|| {
343                    FiscalError::MissingRequiredField {
344                        field: "vICMSST".to_string(),
345                    }
346                })?,
347                v_bc_fcp_st: item.icms_v_bc_fcp_st,
348                p_fcp_st: item.icms_p_fcp_st,
349                v_fcp_st: item.icms_v_fcp_st,
350                v_icms_deson: item.icms_v_icms_deson,
351                mot_des_icms: item.icms_mot_des_icms.map(|v| v.to_string()),
352                ind_deduz_deson: None,
353                v_icms_st_deson: None,
354                mot_des_icms_st: None,
355            },
356            "90" => IcmsCst::Cst90 {
357                orig,
358                mod_bc: item.icms_mod_bc.map(|v| v.to_string()),
359                v_bc: Some(item.total_price),
360                p_red_bc: item.icms_red_bc,
361                c_benef_rbc: None,
362                p_icms: Some(item.icms_rate),
363                v_icms_op: None,
364                p_dif: None,
365                v_icms_dif: None,
366                v_icms: Some(item.icms_amount),
367                v_bc_fcp: item.icms_v_bc_fcp,
368                p_fcp: item.icms_p_fcp,
369                v_fcp: item.icms_v_fcp,
370                p_fcp_dif: None,
371                v_fcp_dif: None,
372                v_fcp_efet: None,
373                mod_bc_st: item.icms_mod_bc_st.map(|v| v.to_string()),
374                p_mva_st: item.icms_p_mva_st,
375                p_red_bc_st: item.icms_red_bc_st,
376                v_bc_st: item.icms_v_bc_st,
377                p_icms_st: item.icms_p_icms_st,
378                v_icms_st: item.icms_v_icms_st,
379                v_bc_fcp_st: item.icms_v_bc_fcp_st,
380                p_fcp_st: item.icms_p_fcp_st,
381                v_fcp_st: item.icms_v_fcp_st,
382                v_icms_deson: item.icms_v_icms_deson,
383                mot_des_icms: item.icms_mot_des_icms.map(|v| v.to_string()),
384                ind_deduz_deson: None,
385                v_icms_st_deson: None,
386                mot_des_icms_st: None,
387            },
388            other => return Err(FiscalError::UnsupportedIcmsCst(other.to_string())),
389        };
390        Ok(cst.into())
391    }
392}
393
394/// Build a `<det nItem="N">` element for one invoice item.
395pub(crate) fn build_det(
396    item: &InvoiceItemData,
397    data: &InvoiceBuildData,
398) -> Result<DetResult, FiscalError> {
399    let is_simples = matches!(
400        data.issuer.tax_regime,
401        TaxRegime::SimplesNacional | TaxRegime::SimplesExcess
402    );
403
404    // Build ICMS
405    let icms_variant = build_icms_variant(item, is_simples)?;
406    let mut icms_totals = IcmsTotals::default();
407    let icms_xml = tax_icms::build_icms_xml(&icms_variant, &mut icms_totals)?;
408
409    // Build PIS
410    let pis_xml = tax_pis_cofins_ipi::build_pis_xml(&PisData {
411        cst: item.pis_cst.clone(),
412        v_bc: item.pis_v_bc.or(Some(Cents(0))),
413        p_pis: item.pis_p_pis.or(Some(Rate4(0))),
414        v_pis: item.pis_v_pis.or(Some(Cents(0))),
415        q_bc_prod: item.pis_q_bc_prod,
416        v_aliq_prod: item.pis_v_aliq_prod,
417    });
418
419    // Build COFINS
420    let cofins_xml = tax_pis_cofins_ipi::build_cofins_xml(&CofinsData {
421        cst: item.cofins_cst.clone(),
422        v_bc: item.cofins_v_bc.or(Some(Cents(0))),
423        p_cofins: item.cofins_p_cofins.or(Some(Rate4(0))),
424        v_cofins: item.cofins_v_cofins.or(Some(Cents(0))),
425        q_bc_prod: item.cofins_q_bc_prod,
426        v_aliq_prod: item.cofins_v_aliq_prod,
427    });
428
429    // Build IPI (optional)
430    let mut ipi_xml = String::new();
431    let mut v_ipi = 0i64;
432    if let Some(ref ipi_cst) = item.ipi_cst {
433        ipi_xml = tax_pis_cofins_ipi::build_ipi_xml(&IpiData {
434            cst: ipi_cst.clone(),
435            c_enq: item.ipi_c_enq.clone().unwrap_or_else(|| "999".to_string()),
436            v_bc: item.ipi_v_bc,
437            p_ipi: item.ipi_p_ipi,
438            v_ipi: item.ipi_v_ipi,
439            q_unid: item.ipi_q_unid,
440            v_unid: item.ipi_v_unid,
441            ..IpiData::default()
442        });
443        v_ipi = item.ipi_v_ipi.map(|c| c.0).unwrap_or(0);
444    }
445
446    // Build II (optional)
447    let mut ii_xml = String::new();
448    let mut v_ii = 0i64;
449    if let Some(ii_vbc) = item.ii_v_bc {
450        ii_xml = tax_pis_cofins_ipi::build_ii_xml(&IiData {
451            v_bc: ii_vbc,
452            v_desp_adu: item.ii_v_desp_adu.unwrap_or(Cents(0)),
453            v_ii: item.ii_v_ii.unwrap_or(Cents(0)),
454            v_iof: item.ii_v_iof.unwrap_or(Cents(0)),
455        });
456        v_ii = item.ii_v_ii.map(|c| c.0).unwrap_or(0);
457    }
458
459    // Build prod options (rastro, veicProd, med, arma, nRECOPI)
460    let prod_options = build_prod_options(item);
461
462    // Build det-level extras (infAdProd, obsItem, DFeReferenciado)
463    let det_extras = build_det_extras(item);
464
465    // Assemble imposto
466    let mut imposto_children: Vec<String> = vec![icms_xml];
467    if !ipi_xml.is_empty() {
468        imposto_children.push(ipi_xml);
469    }
470    imposto_children.push(pis_xml);
471    imposto_children.push(cofins_xml);
472    if !ii_xml.is_empty() {
473        imposto_children.push(ii_xml);
474    }
475
476    // Assemble prod
477    let fc2 = |c: i64| format_cents(c, 2);
478    let fc10 = |c: i64| format_cents(c, 10);
479    let fd4 = |v: f64| format_decimal(v, 4);
480
481    let mut prod_children = vec![
482        tag("cProd", &[], TagContent::Text(&item.product_code)),
483        tag(
484            "cEAN",
485            &[],
486            TagContent::Text(item.c_ean.as_deref().unwrap_or("SEM GTIN")),
487        ),
488        tag("xProd", &[], TagContent::Text(&item.description)),
489        tag("NCM", &[], TagContent::Text(&item.ncm)),
490    ];
491    if let Some(ref cest) = item.cest {
492        prod_children.push(tag("CEST", &[], TagContent::Text(cest)));
493    }
494    prod_children.extend([
495        tag("CFOP", &[], TagContent::Text(&item.cfop)),
496        tag("uCom", &[], TagContent::Text(&item.unit_of_measure)),
497        tag("qCom", &[], TagContent::Text(&fd4(item.quantity))),
498        tag("vUnCom", &[], TagContent::Text(&fc10(item.unit_price.0))),
499        tag("vProd", &[], TagContent::Text(&fc2(item.total_price.0))),
500        tag(
501            "cEANTrib",
502            &[],
503            TagContent::Text(item.c_ean_trib.as_deref().unwrap_or("SEM GTIN")),
504        ),
505        tag("uTrib", &[], TagContent::Text(&item.unit_of_measure)),
506        tag("qTrib", &[], TagContent::Text(&fd4(item.quantity))),
507        tag("vUnTrib", &[], TagContent::Text(&fc10(item.unit_price.0))),
508    ]);
509    if let Some(v) = item.v_frete {
510        prod_children.push(tag("vFrete", &[], TagContent::Text(&fc2(v.0))));
511    }
512    if let Some(v) = item.v_seg {
513        prod_children.push(tag("vSeg", &[], TagContent::Text(&fc2(v.0))));
514    }
515    if let Some(v) = item.v_desc {
516        prod_children.push(tag("vDesc", &[], TagContent::Text(&fc2(v.0))));
517    }
518    if let Some(v) = item.v_outro {
519        prod_children.push(tag("vOutro", &[], TagContent::Text(&fc2(v.0))));
520    }
521    prod_children.push(tag("indTot", &[], TagContent::Text("1")));
522    prod_children.extend(prod_options);
523
524    // Assemble det
525    let nitem = item.item_number.to_string();
526    let mut det_children = vec![
527        tag("prod", &[], TagContent::Children(prod_children)),
528        tag("imposto", &[], TagContent::Children(imposto_children)),
529    ];
530    det_children.extend(det_extras);
531
532    let xml = tag(
533        "det",
534        &[("nItem", &nitem)],
535        TagContent::Children(det_children),
536    );
537
538    Ok(DetResult {
539        xml,
540        icms_totals,
541        v_ipi,
542        v_pis: item.pis_v_pis.map(|c| c.0).unwrap_or(0),
543        v_cofins: item.cofins_v_cofins.map(|c| c.0).unwrap_or(0),
544        v_ii,
545    })
546}
547
548fn build_prod_options(item: &InvoiceItemData) -> Vec<String> {
549    let mut opts = Vec::new();
550
551    // rastro (batch tracking)
552    if let Some(ref rastros) = item.rastro {
553        for r in rastros.iter().take(500) {
554            let mut rastro_children = vec![
555                tag("nLote", &[], TagContent::Text(&r.n_lote)),
556                tag("qLote", &[], TagContent::Text(&format_decimal(r.q_lote, 3))),
557                tag("dFab", &[], TagContent::Text(&r.d_fab)),
558                tag("dVal", &[], TagContent::Text(&r.d_val)),
559            ];
560            if let Some(ref agreg) = r.c_agreg {
561                rastro_children.push(tag("cAgreg", &[], TagContent::Text(agreg)));
562            }
563            opts.push(tag("rastro", &[], TagContent::Children(rastro_children)));
564        }
565    }
566
567    // CHOICE group: veicProd, med, arma, nRECOPI (mutually exclusive)
568    if let Some(ref v) = item.veic_prod {
569        opts.push(tag(
570            "veicProd",
571            &[],
572            TagContent::Children(vec![
573                tag("tpOp", &[], TagContent::Text(&v.tp_op)),
574                tag("chassi", &[], TagContent::Text(&v.chassi)),
575                tag("cCor", &[], TagContent::Text(&v.c_cor)),
576                tag("xCor", &[], TagContent::Text(&v.x_cor)),
577                tag("pot", &[], TagContent::Text(&v.pot)),
578                tag("cilin", &[], TagContent::Text(&v.cilin)),
579                tag("pesoL", &[], TagContent::Text(&v.peso_l)),
580                tag("pesoB", &[], TagContent::Text(&v.peso_b)),
581                tag("nSerie", &[], TagContent::Text(&v.n_serie)),
582                tag("tpComb", &[], TagContent::Text(&v.tp_comb)),
583                tag("nMotor", &[], TagContent::Text(&v.n_motor)),
584                tag("CMT", &[], TagContent::Text(&v.cmt)),
585                tag("dist", &[], TagContent::Text(&v.dist)),
586                tag("anoMod", &[], TagContent::Text(&v.ano_mod)),
587                tag("anoFab", &[], TagContent::Text(&v.ano_fab)),
588                tag("tpPint", &[], TagContent::Text(&v.tp_pint)),
589                tag("tpVeic", &[], TagContent::Text(&v.tp_veic)),
590                tag("espVeic", &[], TagContent::Text(&v.esp_veic)),
591                tag("VIN", &[], TagContent::Text(&v.vin)),
592                tag("condVeic", &[], TagContent::Text(&v.cond_veic)),
593                tag("cMod", &[], TagContent::Text(&v.c_mod)),
594                tag("cCorDENATRAN", &[], TagContent::Text(&v.c_cor_denatran)),
595                tag("lota", &[], TagContent::Text(&v.lota)),
596                tag("tpRest", &[], TagContent::Text(&v.tp_rest)),
597            ]),
598        ));
599    } else if let Some(ref m) = item.med {
600        let mut med_children = Vec::new();
601        if let Some(ref code) = m.c_prod_anvisa {
602            med_children.push(tag("cProdANVISA", &[], TagContent::Text(code)));
603        }
604        if let Some(ref reason) = m.x_motivo_isencao {
605            med_children.push(tag("xMotivoIsencao", &[], TagContent::Text(reason)));
606        }
607        med_children.push(tag(
608            "vPMC",
609            &[],
610            TagContent::Text(&format_cents(m.v_pmc.0, 2)),
611        ));
612        opts.push(tag("med", &[], TagContent::Children(med_children)));
613    } else if let Some(ref arms) = item.arma {
614        for a in arms.iter().take(500) {
615            opts.push(tag(
616                "arma",
617                &[],
618                TagContent::Children(vec![
619                    tag("tpArma", &[], TagContent::Text(&a.tp_arma)),
620                    tag("nSerie", &[], TagContent::Text(&a.n_serie)),
621                    tag("nCano", &[], TagContent::Text(&a.n_cano)),
622                    tag("descr", &[], TagContent::Text(&a.descr)),
623                ]),
624            ));
625        }
626    } else if let Some(ref recopi) = item.n_recopi {
627        if !recopi.is_empty() {
628            opts.push(tag("nRECOPI", &[], TagContent::Text(recopi)));
629        }
630    }
631
632    opts
633}
634
635fn build_det_extras(item: &InvoiceItemData) -> Vec<String> {
636    let mut extras = Vec::new();
637
638    if let Some(ref info) = item.inf_ad_prod {
639        extras.push(tag("infAdProd", &[], TagContent::Text(info)));
640    }
641
642    if let Some(ref obs) = item.obs_item {
643        let mut obs_children = Vec::new();
644        if let Some(ref cont) = obs.obs_cont {
645            obs_children.push(tag(
646                "obsCont",
647                &[("xCampo", &cont.x_campo)],
648                TagContent::Children(vec![tag("xTexto", &[], TagContent::Text(&cont.x_texto))]),
649            ));
650        }
651        if let Some(ref fisco) = obs.obs_fisco {
652            obs_children.push(tag(
653                "obsFisco",
654                &[("xCampo", &fisco.x_campo)],
655                TagContent::Children(vec![tag("xTexto", &[], TagContent::Text(&fisco.x_texto))]),
656            ));
657        }
658        extras.push(tag("obsItem", &[], TagContent::Children(obs_children)));
659    }
660
661    if let Some(ref dfe) = item.dfe_referenciado {
662        let mut dfe_children = vec![tag("chaveAcesso", &[], TagContent::Text(&dfe.chave_acesso))];
663        if let Some(ref n) = dfe.n_item {
664            dfe_children.push(tag("nItem", &[], TagContent::Text(n)));
665        }
666        extras.push(tag(
667            "DFeReferenciado",
668            &[],
669            TagContent::Children(dfe_children),
670        ));
671    }
672
673    extras
674}