Skip to main content

fiscal_core/xml_builder/
optional.rs

1//! Build optional XML groups: cobr, infAdic, infIntermed, exporta, compra,
2//! infRespTec, retirada, entrega, autXML.
3
4use super::emit::build_address_fields;
5use super::tax_id::TaxId;
6use crate::format_utils::format_cents;
7use crate::types::*;
8use crate::xml_utils::{TagContent, tag};
9
10/// Build `<cobr>` (billing) element.
11pub fn build_cobr(billing: &BillingData) -> String {
12    let fc2 = |c: i64| format_cents(c, 2);
13    let mut children = Vec::new();
14
15    if let Some(ref inv) = billing.invoice {
16        let mut fat_children = vec![
17            tag("nFat", &[], TagContent::Text(&inv.number)),
18            tag("vOrig", &[], TagContent::Text(&fc2(inv.original_value.0))),
19        ];
20        if let Some(disc) = inv.discount_value {
21            fat_children.push(tag("vDesc", &[], TagContent::Text(&fc2(disc.0))));
22        }
23        fat_children.push(tag("vLiq", &[], TagContent::Text(&fc2(inv.net_value.0))));
24        children.push(tag("fat", &[], TagContent::Children(fat_children)));
25    }
26
27    if let Some(ref installments) = billing.installments {
28        for inst in installments {
29            children.push(tag(
30                "dup",
31                &[],
32                TagContent::Children(vec![
33                    tag("nDup", &[], TagContent::Text(&inst.number)),
34                    tag("dVenc", &[], TagContent::Text(&inst.due_date)),
35                    tag("vDup", &[], TagContent::Text(&fc2(inst.value.0))),
36                ]),
37            ));
38        }
39    }
40
41    tag("cobr", &[], TagContent::Children(children))
42}
43
44/// Build `<infAdic>` (additional info) element.
45pub(crate) fn build_inf_adic(data: &InvoiceBuildData) -> String {
46    let mut notes: Vec<String> = Vec::new();
47
48    if let Some(ref cont) = data.contingency {
49        notes.push(format!(
50            "Emitida em contingencia ({}). Motivo: {}",
51            cont.contingency_type.as_str(),
52            cont.reason
53        ));
54    }
55
56    if data.environment == SefazEnvironment::Homologation {
57        notes.push("EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL".to_string());
58    }
59
60    let add_info = data.additional_info.as_ref();
61    let has_additional = add_info.is_some_and(|a| {
62        a.taxpayer_note.is_some()
63            || a.tax_authority_note.is_some()
64            || a.contributor_obs.as_ref().is_some_and(|v| !v.is_empty())
65            || a.fiscal_obs.as_ref().is_some_and(|v| !v.is_empty())
66            || a.process_refs.as_ref().is_some_and(|v| !v.is_empty())
67    });
68
69    if notes.is_empty() && !has_additional {
70        return String::new();
71    }
72
73    let mut children = Vec::new();
74
75    // infAdFisco before infCpl per schema
76    if let Some(note) = add_info.and_then(|a| a.tax_authority_note.as_ref()) {
77        children.push(tag("infAdFisco", &[], TagContent::Text(note)));
78    }
79
80    // Merge contingency/env notes with taxpayer note
81    if let Some(tn) = add_info.and_then(|a| a.taxpayer_note.as_ref()) {
82        notes.push(tn.to_string());
83    }
84    if !notes.is_empty() {
85        children.push(tag("infCpl", &[], TagContent::Text(&notes.join("; "))));
86    }
87
88    // obsCont (max 10)
89    if let Some(obs_list) = add_info.and_then(|a| a.contributor_obs.as_ref()) {
90        for obs in obs_list.iter().take(10) {
91            children.push(tag(
92                "obsCont",
93                &[("xCampo", &obs.field)],
94                TagContent::Children(vec![tag("xTexto", &[], TagContent::Text(&obs.text))]),
95            ));
96        }
97    }
98
99    // obsFisco (max 10)
100    if let Some(obs_list) = add_info.and_then(|a| a.fiscal_obs.as_ref()) {
101        for obs in obs_list.iter().take(10) {
102            children.push(tag(
103                "obsFisco",
104                &[("xCampo", &obs.field)],
105                TagContent::Children(vec![tag("xTexto", &[], TagContent::Text(&obs.text))]),
106            ));
107        }
108    }
109
110    // procRef (max 100)
111    if let Some(procs) = add_info.and_then(|a| a.process_refs.as_ref()) {
112        for p in procs.iter().take(100) {
113            children.push(tag(
114                "procRef",
115                &[],
116                TagContent::Children(vec![
117                    tag("nProc", &[], TagContent::Text(&p.number)),
118                    tag("indProc", &[], TagContent::Text(&p.origin)),
119                ]),
120            ));
121        }
122    }
123
124    tag("infAdic", &[], TagContent::Children(children))
125}
126
127/// Build `<infIntermed>` element.
128pub fn build_intermediary(intermed: &IntermediaryData) -> String {
129    let mut children = vec![tag("CNPJ", &[], TagContent::Text(&intermed.tax_id))];
130    if let Some(ref id) = intermed.id_cad_int_tran {
131        children.push(tag("idCadIntTran", &[], TagContent::Text(id)));
132    }
133    tag("infIntermed", &[], TagContent::Children(children))
134}
135
136/// Build `<infRespTec>` element.
137pub fn build_tech_responsible(tech: &TechResponsibleData) -> String {
138    let mut children = vec![
139        tag("CNPJ", &[], TagContent::Text(&tech.tax_id)),
140        tag("xContato", &[], TagContent::Text(&tech.contact)),
141        tag("email", &[], TagContent::Text(&tech.email)),
142    ];
143    if let Some(ref phone) = tech.phone {
144        children.push(tag("fone", &[], TagContent::Text(phone)));
145    }
146    tag("infRespTec", &[], TagContent::Children(children))
147}
148
149/// Build `<compra>` (purchase) element.
150pub fn build_purchase(purchase: &PurchaseData) -> String {
151    let mut children = Vec::new();
152    if let Some(ref note) = purchase.purchase_note {
153        children.push(tag("xNEmp", &[], TagContent::Text(note)));
154    }
155    if let Some(ref order) = purchase.order_number {
156        children.push(tag("xPed", &[], TagContent::Text(order)));
157    }
158    if let Some(ref contract) = purchase.contract_number {
159        children.push(tag("xCont", &[], TagContent::Text(contract)));
160    }
161    tag("compra", &[], TagContent::Children(children))
162}
163
164/// Build `<exporta>` element.
165pub fn build_export(exp: &ExportData) -> String {
166    let mut children = vec![
167        tag("UFSaidaPais", &[], TagContent::Text(&exp.exit_state)),
168        tag("xLocExporta", &[], TagContent::Text(&exp.export_location)),
169    ];
170    if let Some(ref dispatch) = exp.dispatch_location {
171        children.push(tag("xLocDespacho", &[], TagContent::Text(dispatch)));
172    }
173    tag("exporta", &[], TagContent::Children(children))
174}
175
176/// Build `<retirada>` (withdrawal) element.
177pub fn build_withdrawal(w: &LocationData) -> String {
178    let tid = TaxId::new(&w.tax_id);
179    let padded = tid.padded();
180    let mut children = vec![tag(tid.tag_name(), &[], TagContent::Text(&padded))];
181    if let Some(ref name) = w.name {
182        children.push(tag("xNome", &[], TagContent::Text(name)));
183    }
184    children.extend(build_address_fields(
185        &w.street,
186        &w.number,
187        w.complement.as_deref(),
188        &w.district,
189        &w.city_code.0,
190        &w.city_name,
191        &w.state_code,
192        w.zip_code.as_deref(),
193        false,
194    ));
195    tag("retirada", &[], TagContent::Children(children))
196}
197
198/// Build `<entrega>` (delivery) element.
199pub fn build_delivery(d: &LocationData) -> String {
200    let tid = TaxId::new(&d.tax_id);
201    let padded = tid.padded();
202    let mut children = vec![tag(tid.tag_name(), &[], TagContent::Text(&padded))];
203    if let Some(ref name) = d.name {
204        children.push(tag("xNome", &[], TagContent::Text(name)));
205    }
206    children.extend(build_address_fields(
207        &d.street,
208        &d.number,
209        d.complement.as_deref(),
210        &d.district,
211        &d.city_code.0,
212        &d.city_name,
213        &d.state_code,
214        d.zip_code.as_deref(),
215        false,
216    ));
217    tag("entrega", &[], TagContent::Children(children))
218}
219
220/// Build `<autXML>` element.
221pub fn build_aut_xml(entry: &AuthorizedXml) -> String {
222    let tid = TaxId::new(&entry.tax_id);
223    let padded = tid.padded();
224    tag(
225        "autXML",
226        &[],
227        TagContent::Children(vec![tag(tid.tag_name(), &[], TagContent::Text(&padded))]),
228    )
229}