fiscal_core/xml_builder/
mod.rs1pub mod access_key;
19mod builder;
20pub mod dest;
21pub mod det;
22pub mod emit;
23pub mod ide;
24pub mod optional;
25pub mod pag;
26pub mod tax_id;
27pub mod total;
28pub mod transp;
29
30pub use access_key::build_access_key;
31pub use builder::{Built, Draft, InvoiceBuilder, Signed};
32
33pub fn build_from_data(
46 data: &crate::types::InvoiceBuildData,
47) -> Result<crate::types::InvoiceXmlResult, FiscalError> {
48 generate_xml(data)
49}
50
51use crate::FiscalError;
52use crate::constants::{NFE_NAMESPACE, NFE_VERSION};
53use crate::newtypes::IbgeCode;
54use crate::state_codes::STATE_IBGE_CODES;
55use crate::tax_icms::{create_icms_totals, merge_icms_totals};
56use crate::types::{AccessKeyParams, InvoiceBuildData, InvoiceXmlResult};
57use crate::xml_utils::{TagContent, tag};
58
59fn generate_xml(data: &InvoiceBuildData) -> Result<InvoiceXmlResult, FiscalError> {
63 let state_ibge = STATE_IBGE_CODES
64 .get(data.issuer.state_code.as_str())
65 .copied()
66 .ok_or_else(|| FiscalError::InvalidStateCode(data.issuer.state_code.clone()))?;
67
68 let numeric_code = access_key::generate_numeric_code();
69 let year_month = access_key::format_year_month(&data.issued_at);
70
71 let ak_params = AccessKeyParams {
72 state_code: IbgeCode(state_ibge.to_string()),
73 year_month,
74 tax_id: data.issuer.tax_id.clone(),
75 model: data.model,
76 series: data.series,
77 number: data.number,
78 emission_type: data.emission_type,
79 numeric_code: numeric_code.clone(),
80 };
81
82 let access_key = build_access_key(&ak_params)?;
83 let inf_nfe_id = format!("NFe{access_key}");
84
85 let mut icms_totals = create_icms_totals();
87 let mut total_products: i64 = 0;
88 let mut total_ipi: i64 = 0;
89 let mut total_pis: i64 = 0;
90 let mut total_cofins: i64 = 0;
91 let mut total_ii: i64 = 0;
92 let mut total_frete: i64 = 0;
93 let mut total_seg: i64 = 0;
94 let mut total_desc: i64 = 0;
95 let mut total_outro: i64 = 0;
96 let mut total_tot_trib: i64 = 0;
97 let mut total_ipi_devol: i64 = 0;
98 let mut total_pis_st: i64 = 0;
99 let mut total_cofins_st: i64 = 0;
100 let mut any_ind_deduz_deson = false;
101
102 let mut det_elements = Vec::with_capacity(data.items.len());
103 for item in &data.items {
104 let det_result = det::build_det(item, data)?;
105 if det_result.ind_deduz_deson {
107 any_ind_deduz_deson = true;
108 }
109 if det_result.ind_tot == 1 {
111 total_products += item.total_price.0;
112 total_ipi += det_result.v_ipi;
113 total_pis += det_result.v_pis;
114 total_cofins += det_result.v_cofins;
115 total_ii += det_result.v_ii;
116 total_frete += det_result.v_frete;
117 total_seg += det_result.v_seg;
118 total_desc += det_result.v_desc;
119 total_outro += det_result.v_outro;
120 total_tot_trib += det_result.v_tot_trib;
121 total_ipi_devol += det_result.v_ipi_devol;
122 total_pis_st += det_result.v_pis_st;
123 total_cofins_st += det_result.v_cofins_st;
124 merge_icms_totals(&mut icms_totals, &det_result.icms_totals);
125 }
126 det_elements.push(det_result.xml);
127 }
128
129 let mut inf_children = vec![
131 ide::build_ide(data, state_ibge, &numeric_code, &access_key),
132 emit::build_emit(data),
133 ];
134
135 if let Some(dest_xml) = dest::build_dest(data) {
136 inf_children.push(dest_xml);
137 }
138
139 if let Some(ref w) = data.withdrawal {
140 inf_children.push(optional::build_withdrawal(w));
141 }
142 if let Some(ref d) = data.delivery {
143 inf_children.push(optional::build_delivery(d));
144 }
145 if let Some(ref auths) = data.authorized_xml {
146 for a in auths {
147 inf_children.push(optional::build_aut_xml(a));
148 }
149 }
150
151 inf_children.extend(det_elements);
152
153 if any_ind_deduz_deson {
155 icms_totals.ind_deduz_deson = true;
156 }
157
158 inf_children.push(total::build_total(
159 total_products,
160 &icms_totals,
161 &total::OtherTotals {
162 v_ipi: total_ipi,
163 v_pis: total_pis,
164 v_cofins: total_cofins,
165 v_ii: total_ii,
166 v_frete: total_frete,
167 v_seg: total_seg,
168 v_desc: total_desc,
169 v_outro: total_outro,
170 v_tot_trib: total_tot_trib,
171 v_ipi_devol: total_ipi_devol,
172 v_pis_st: total_pis_st,
173 v_cofins_st: total_cofins_st,
174 },
175 data.ret_trib.as_ref(),
176 data.issqn_tot.as_ref(),
177 data.is_tot.as_ref(),
178 data.ibs_cbs_tot.as_ref(),
179 data.schema_version,
180 data.calculation_method,
181 data.v_nf_tot_override,
182 ));
183
184 inf_children.push(transp::build_transp(data));
185
186 if let Some(ref billing) = data.billing {
187 inf_children.push(optional::build_cobr(billing));
188 }
189
190 inf_children.push(pag::build_pag(
191 &data.payments,
192 data.change_amount,
193 data.payment_card_details.as_deref(),
194 ));
195
196 if let Some(ref intermed) = data.intermediary {
197 inf_children.push(optional::build_intermediary(intermed));
198 }
199
200 let inf_adic = optional::build_inf_adic(data);
201 if !inf_adic.is_empty() {
202 inf_children.push(inf_adic);
203 }
204
205 if let Some(ref exp) = data.export {
206 inf_children.push(optional::build_export(exp));
207 }
208 if let Some(ref purchase) = data.purchase {
209 inf_children.push(optional::build_purchase(purchase));
210 }
211 if let Some(ref cana) = data.cana {
212 inf_children.push(optional::build_cana(cana));
213 }
214 if let Some(ref tech) = data.tech_responsible {
215 inf_children.push(optional::build_tech_responsible_with_key(tech, &access_key));
216 }
217 if data.schema_version.is_pl010() {
220 if let Some(ref agro) = data.agropecuario {
221 inf_children.push(optional::build_agropecuario(agro));
222 }
223 }
224
225 let inf_nfe = tag(
228 "infNFe",
229 &[("Id", &inf_nfe_id), ("versao", NFE_VERSION)],
230 TagContent::Children(inf_children),
231 );
232
233 let mut xml = format!(
234 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>{}",
235 tag(
236 "NFe",
237 &[("xmlns", NFE_NAMESPACE)],
238 TagContent::Children(vec![inf_nfe])
239 ),
240 );
241
242 if data.only_ascii {
243 xml = crate::sanitize::sanitize_xml_text(&xml);
244 }
245
246 Ok(InvoiceXmlResult { xml, access_key })
247}