1use 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};
9use base64::Engine as _;
10
11pub fn build_cobr(billing: &BillingData) -> String {
13 let fc2 = |c: i64| format_cents(c, 2);
14 let mut children = Vec::new();
15
16 if let Some(ref inv) = billing.invoice {
17 let mut fat_children = vec![
18 tag("nFat", &[], TagContent::Text(&inv.number)),
19 tag("vOrig", &[], TagContent::Text(&fc2(inv.original_value.0))),
20 ];
21 if let Some(disc) = inv.discount_value {
22 fat_children.push(tag("vDesc", &[], TagContent::Text(&fc2(disc.0))));
23 }
24 fat_children.push(tag("vLiq", &[], TagContent::Text(&fc2(inv.net_value.0))));
25 children.push(tag("fat", &[], TagContent::Children(fat_children)));
26 }
27
28 if let Some(ref installments) = billing.installments {
29 for inst in installments {
30 children.push(tag(
31 "dup",
32 &[],
33 TagContent::Children(vec![
34 tag("nDup", &[], TagContent::Text(&inst.number)),
35 tag("dVenc", &[], TagContent::Text(&inst.due_date)),
36 tag("vDup", &[], TagContent::Text(&fc2(inst.value.0))),
37 ]),
38 ));
39 }
40 }
41
42 tag("cobr", &[], TagContent::Children(children))
43}
44
45pub(crate) fn build_inf_adic(data: &InvoiceBuildData) -> String {
47 let mut notes: Vec<String> = Vec::new();
48
49 if let Some(ref cont) = data.contingency {
50 notes.push(format!(
51 "Emitida em contingencia ({}). Motivo: {}",
52 cont.contingency_type.as_str(),
53 cont.reason
54 ));
55 }
56
57 if data.environment == SefazEnvironment::Homologation {
58 notes.push("EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL".to_string());
59 }
60
61 let add_info = data.additional_info.as_ref();
62 let has_additional = add_info.is_some_and(|a| {
63 a.taxpayer_note.is_some()
64 || a.tax_authority_note.is_some()
65 || a.contributor_obs.as_ref().is_some_and(|v| !v.is_empty())
66 || a.fiscal_obs.as_ref().is_some_and(|v| !v.is_empty())
67 || a.process_refs.as_ref().is_some_and(|v| !v.is_empty())
68 });
69
70 if notes.is_empty() && !has_additional {
71 return String::new();
72 }
73
74 let mut children = Vec::new();
75
76 if let Some(note) = add_info.and_then(|a| a.tax_authority_note.as_ref()) {
78 children.push(tag("infAdFisco", &[], TagContent::Text(note)));
79 }
80
81 if let Some(tn) = add_info.and_then(|a| a.taxpayer_note.as_ref()) {
83 notes.push(tn.to_string());
84 }
85 if !notes.is_empty() {
86 children.push(tag("infCpl", &[], TagContent::Text(¬es.join("; "))));
87 }
88
89 if let Some(obs_list) = add_info.and_then(|a| a.contributor_obs.as_ref()) {
91 for obs in obs_list.iter().take(10) {
92 children.push(tag(
93 "obsCont",
94 &[("xCampo", &obs.field)],
95 TagContent::Children(vec![tag("xTexto", &[], TagContent::Text(&obs.text))]),
96 ));
97 }
98 }
99
100 if let Some(obs_list) = add_info.and_then(|a| a.fiscal_obs.as_ref()) {
102 for obs in obs_list.iter().take(10) {
103 children.push(tag(
104 "obsFisco",
105 &[("xCampo", &obs.field)],
106 TagContent::Children(vec![tag("xTexto", &[], TagContent::Text(&obs.text))]),
107 ));
108 }
109 }
110
111 if let Some(procs) = add_info.and_then(|a| a.process_refs.as_ref()) {
113 for p in procs.iter().take(100) {
114 children.push(tag(
115 "procRef",
116 &[],
117 TagContent::Children(vec![
118 tag("nProc", &[], TagContent::Text(&p.number)),
119 tag("indProc", &[], TagContent::Text(&p.origin)),
120 ]),
121 ));
122 }
123 }
124
125 tag("infAdic", &[], TagContent::Children(children))
126}
127
128pub fn build_intermediary(intermed: &IntermediaryData) -> String {
130 let mut children = vec![tag("CNPJ", &[], TagContent::Text(&intermed.tax_id))];
131 if let Some(ref id) = intermed.id_cad_int_tran {
132 children.push(tag("idCadIntTran", &[], TagContent::Text(id)));
133 }
134 tag("infIntermed", &[], TagContent::Children(children))
135}
136
137pub fn build_tech_responsible(tech: &TechResponsibleData) -> String {
143 build_tech_responsible_with_key(tech, "")
144}
145
146pub fn build_tech_responsible_with_key(tech: &TechResponsibleData, access_key: &str) -> String {
152 let mut children = vec![
153 tag("CNPJ", &[], TagContent::Text(&tech.tax_id)),
154 tag("xContato", &[], TagContent::Text(&tech.contact)),
155 tag("email", &[], TagContent::Text(&tech.email)),
156 ];
157 if let Some(ref phone) = tech.phone {
158 children.push(tag("fone", &[], TagContent::Text(phone)));
159 }
160 if let (Some(csrt), Some(csrt_id)) = (&tech.csrt, &tech.csrt_id) {
161 if !access_key.is_empty() {
162 children.push(tag("idCSRT", &[], TagContent::Text(csrt_id)));
163 let hash = compute_hash_csrt(csrt, access_key);
164 children.push(tag("hashCSRT", &[], TagContent::Text(&hash)));
165 }
166 }
167 tag("infRespTec", &[], TagContent::Children(children))
168}
169
170fn compute_hash_csrt(csrt: &str, access_key: &str) -> String {
175 use sha1::{Digest, Sha1};
176 let combined = format!("{csrt}{access_key}");
177 let mut hasher = Sha1::new();
178 hasher.update(combined.as_bytes());
179 let raw_hash = hasher.finalize();
180 base64::engine::general_purpose::STANDARD.encode(raw_hash)
181}
182
183pub fn build_purchase(purchase: &PurchaseData) -> String {
185 let mut children = Vec::new();
186 if let Some(ref note) = purchase.purchase_note {
187 children.push(tag("xNEmp", &[], TagContent::Text(note)));
188 }
189 if let Some(ref order) = purchase.order_number {
190 children.push(tag("xPed", &[], TagContent::Text(order)));
191 }
192 if let Some(ref contract) = purchase.contract_number {
193 children.push(tag("xCont", &[], TagContent::Text(contract)));
194 }
195 tag("compra", &[], TagContent::Children(children))
196}
197
198pub fn build_export(exp: &ExportData) -> String {
200 let mut children = vec![
201 tag("UFSaidaPais", &[], TagContent::Text(&exp.exit_state)),
202 tag("xLocExporta", &[], TagContent::Text(&exp.export_location)),
203 ];
204 if let Some(ref dispatch) = exp.dispatch_location {
205 children.push(tag("xLocDespacho", &[], TagContent::Text(dispatch)));
206 }
207 tag("exporta", &[], TagContent::Children(children))
208}
209
210pub fn build_withdrawal(w: &LocationData) -> String {
212 let tid = TaxId::new(&w.tax_id);
213 let padded = tid.padded();
214 let mut children = vec![tag(tid.tag_name(), &[], TagContent::Text(&padded))];
215 if let Some(ref name) = w.name {
216 children.push(tag("xNome", &[], TagContent::Text(name)));
217 }
218 children.extend(build_address_fields(
219 &w.street,
220 &w.number,
221 w.complement.as_deref(),
222 &w.district,
223 &w.city_code.0,
224 &w.city_name,
225 &w.state_code,
226 w.zip_code.as_deref(),
227 false,
228 ));
229 tag("retirada", &[], TagContent::Children(children))
230}
231
232pub fn build_delivery(d: &LocationData) -> String {
234 let tid = TaxId::new(&d.tax_id);
235 let padded = tid.padded();
236 let mut children = vec![tag(tid.tag_name(), &[], TagContent::Text(&padded))];
237 if let Some(ref name) = d.name {
238 children.push(tag("xNome", &[], TagContent::Text(name)));
239 }
240 children.extend(build_address_fields(
241 &d.street,
242 &d.number,
243 d.complement.as_deref(),
244 &d.district,
245 &d.city_code.0,
246 &d.city_name,
247 &d.state_code,
248 d.zip_code.as_deref(),
249 false,
250 ));
251 tag("entrega", &[], TagContent::Children(children))
252}
253
254pub fn build_aut_xml(entry: &AuthorizedXml) -> String {
256 let tid = TaxId::new(&entry.tax_id);
257 let padded = tid.padded();
258 tag(
259 "autXML",
260 &[],
261 TagContent::Children(vec![tag(tid.tag_name(), &[], TagContent::Text(&padded))]),
262 )
263}