1use super::{
3 Address, Buyer, FinalizedInvoice, InvoiceData, InvoiceNote, InvoiceType, InvoiceView, LineItem,
4 OtherId, Party, PartyRole, Seller, SignedInvoice, VatCategory, VatId,
5};
6
7use helpers::{
8 FixedPrecision, currency_amount, currency_amount_with_precision, id_with_scheme,
9 id_with_scheme_with_agency, quantity_with_unit, vat_category_code,
10};
11use quick_xml::se::{SeError, Serializer as QuickXmlSerializer};
12use serde::ser::{Serialize, SerializeStruct, Serializer};
13use thiserror::Error;
14
15#[derive(Debug, Clone, Copy)]
17pub struct InvoiceXml<'a, T: InvoiceView + ?Sized>(pub &'a T);
18
19#[derive(Debug, Error)]
21pub enum InvoiceXmlError {
22 #[error("failed to serialize invoice to XML: {source}")]
23 Serialize {
24 #[from]
25 source: SeError,
26 },
27}
28
29#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
31pub enum XmlFormat {
32 #[default]
33 Compact,
34 Pretty {
35 indent_char: char,
36 indent_size: usize,
37 },
38}
39
40mod helpers {
41 use super::VatCategory;
42 use serde::ser::{Serialize, SerializeStruct, Serializer};
43 use std::fmt::{self, Display, Formatter};
44
45 pub(super) fn vat_category_code(category: &VatCategory) -> &'static str {
46 match category {
47 VatCategory::Exempt => "E",
48 VatCategory::Standard => "S",
49 VatCategory::Zero => "Z",
50 VatCategory::OutOfScope => "O",
51 }
52 }
53
54 pub(super) struct FixedPrecision {
55 value: f64,
56 precision: usize,
57 }
58
59 impl FixedPrecision {
60 pub(super) fn new(value: f64, precision: usize) -> Self {
61 Self { value, precision }
62 }
63 }
64
65 impl Display for FixedPrecision {
66 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
67 write!(f, "{:.*}", self.precision, self.value)
68 }
69 }
70
71 impl Serialize for FixedPrecision {
72 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
73 where
74 S: Serializer,
75 {
76 serializer.collect_str(self)
77 }
78 }
79
80 struct CurrencyAmountSer<'a> {
81 tag: &'static str,
82 currency: &'a str,
83 value: f64,
84 precision: usize,
85 }
86
87 pub(super) fn currency_amount<'a>(
88 tag: &'static str,
89 currency: &'a str,
90 value: f64,
91 ) -> impl Serialize + 'a {
92 CurrencyAmountSer {
93 tag,
94 currency,
95 value,
96 precision: 2,
97 }
98 }
99
100 pub(super) fn currency_amount_with_precision<'a>(
101 tag: &'static str,
102 currency: &'a str,
103 value: f64,
104 precision: usize,
105 ) -> impl Serialize + 'a {
106 CurrencyAmountSer {
107 tag,
108 currency,
109 value,
110 precision,
111 }
112 }
113
114 impl<'a> Serialize for CurrencyAmountSer<'a> {
115 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
116 where
117 S: Serializer,
118 {
119 let mut st = s.serialize_struct(self.tag, 2)?;
120 st.serialize_field("@currencyID", self.currency)?;
121 st.serialize_field("$text", &FixedPrecision::new(self.value, self.precision))?;
122 st.end()
123 }
124 }
125
126 struct IdWithSchemeSer<'a> {
127 tag: &'static str,
128 scheme_id: &'a str,
129 scheme_agency_id: Option<&'a str>,
130 value: &'a str,
131 }
132
133 pub(super) fn id_with_scheme<'a>(
134 tag: &'static str,
135 scheme_id: &'a str,
136 value: &'a str,
137 ) -> impl Serialize + 'a {
138 IdWithSchemeSer {
139 tag,
140 scheme_id,
141 scheme_agency_id: None,
142 value,
143 }
144 }
145
146 pub(super) fn id_with_scheme_with_agency<'a>(
147 tag: &'static str,
148 scheme_id: &'a str,
149 scheme_agency_id: &'a str,
150 value: &'a str,
151 ) -> impl Serialize + 'a {
152 IdWithSchemeSer {
153 tag,
154 scheme_id,
155 scheme_agency_id: Some(scheme_agency_id),
156 value,
157 }
158 }
159
160 impl<'a> Serialize for IdWithSchemeSer<'a> {
161 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
162 where
163 S: Serializer,
164 {
165 let mut st = s.serialize_struct(self.tag, 3)?;
166 st.serialize_field("@schemeID", self.scheme_id)?;
167 if let Some(agency) = self.scheme_agency_id {
168 st.serialize_field("@schemeAgencyID", agency)?;
169 }
170 st.serialize_field("$text", self.value)?;
171 st.end()
172 }
173 }
174
175 struct QuantityWithUnitSer<'a> {
176 tag: &'static str,
177 value: f64,
178 unit_code: &'a str,
179 }
180
181 pub(super) fn quantity_with_unit<'a>(
182 tag: &'static str,
183 value: f64,
184 unit_code: &'a str,
185 ) -> impl Serialize + 'a {
186 QuantityWithUnitSer {
187 tag,
188 value,
189 unit_code,
190 }
191 }
192
193 impl<'a> Serialize for QuantityWithUnitSer<'a> {
194 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
195 where
196 S: Serializer,
197 {
198 let mut st = s.serialize_struct(self.tag, 2)?;
199 st.serialize_field("@unitCode", self.unit_code)?;
200 st.serialize_field("$text", &FixedPrecision::new(self.value, 6))?;
201 st.end()
202 }
203 }
204}
205
206pub(crate) mod constants;
207pub mod parse;
208
209struct InvoiceTotals<'a> {
210 currency: &'a str,
211 vat_percent: f64,
212 vat_category: &'a VatCategory,
213 totals: &'a super::InvoiceTotalsData,
214}
215
216impl<'a> InvoiceTotals<'a> {
217 fn new<T: InvoiceView + ?Sized>(inv: &'a T) -> Self {
218 let data = inv.data();
219 let vat_percent = data
220 .line_items
221 .first()
222 .map(|li| li.vat_rate)
223 .unwrap_or_default();
224
225 Self {
226 currency: data.currency.as_str(),
227 vat_percent,
228 vat_category: &data.vat_category,
229 totals: inv.totals(),
230 }
231 }
232
233 fn currency(&self) -> &'a str {
234 self.currency
235 }
236
237 fn taxable_amount(&self) -> f64 {
238 self.totals.taxable_amount()
239 }
240
241 fn tax_inclusive_amount(&self) -> f64 {
242 self.totals.tax_inclusive_amount()
243 }
244
245 fn line_extension(&self) -> f64 {
246 self.totals.line_extension()
247 }
248
249 fn tax_amount(&self) -> f64 {
250 self.totals.tax_amount()
251 }
252
253 fn allowance_total(&self) -> f64 {
254 self.totals.allowance_total()
255 }
256
257 fn charge_total(&self) -> f64 {
258 self.totals.charge_total()
259 }
260
261 fn vat_percent(&self) -> f64 {
262 self.vat_percent
263 }
264
265 fn vat_category(&self) -> &'a VatCategory {
266 self.vat_category
267 }
268}
269
270struct InvoiceTypeView<'a>(&'a InvoiceType);
271
272impl<'a> Serialize for InvoiceTypeView<'a> {
273 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
274 where
275 S: Serializer,
276 {
277 let (attribute_code, body_code) = match self.0 {
278 InvoiceType::Tax(st) => match st {
279 super::InvoiceSubType::Simplified => ("0200000", "388"),
280 super::InvoiceSubType::Standard => ("0100000", "388"),
281 },
282 InvoiceType::Prepayment(st) => match st {
283 super::InvoiceSubType::Simplified => ("0200000", "386"),
284 super::InvoiceSubType::Standard => ("0100000", "386"),
285 },
286 InvoiceType::CreditNote(st, _, _) => match st {
287 super::InvoiceSubType::Simplified => ("0200000", "381"),
288 super::InvoiceSubType::Standard => ("0100000", "381"),
289 },
290 InvoiceType::DebitNote(st, _, _) => match st {
291 super::InvoiceSubType::Simplified => ("0200000", "383"),
292 super::InvoiceSubType::Standard => ("0100000", "383"),
293 },
294 };
295
296 let mut st = s.serialize_struct("cbc:InvoiceTypeCode", 2)?;
297 st.serialize_field("@name", &attribute_code)?;
298 st.serialize_field("$text", &body_code)?;
299 st.end()
300 }
301}
302
303struct TaxSchemeXml;
304
305impl Serialize for TaxSchemeXml {
306 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
307 where
308 S: Serializer,
309 {
310 let mut st = s.serialize_struct("cac:TaxScheme", 0)?;
311 st.serialize_field(
312 "cbc:ID",
313 &id_with_scheme_with_agency("cbc:ID", "UN/ECE 5153", "6", "VAT"),
314 )?;
315 st.end()
316 }
317}
318
319struct VatSchemeXml<'a>(&'a VatId);
320
321impl<'a> Serialize for VatSchemeXml<'a> {
322 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
323 where
324 S: Serializer,
325 {
326 let vat = self.0;
327 let mut st = s.serialize_struct("cac:PartyTaxScheme", 0)?;
328 st.serialize_field("cbc:CompanyID", vat.as_str())?;
329 st.serialize_field("cac:TaxScheme", &TaxSchemeXml)?;
330 st.end()
331 }
332}
333
334struct PartyXml<'a, R: PartyRole>(&'a Party<R>);
335
336struct PartyIdentificationXml<'a>(&'a OtherId);
337
338impl<'a> Serialize for PartyIdentificationXml<'a> {
339 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
340 where
341 S: Serializer,
342 {
343 let other_id = self.0;
344 let mut st = s.serialize_struct("cac:PartyIdentification", 0)?;
345 if let Some(scheme_id) = other_id.scheme_id() {
346 st.serialize_field(
347 "cbc:ID",
348 &id_with_scheme("cbc:ID", scheme_id, other_id.as_str()),
349 )?;
350 } else {
351 st.serialize_field("cbc:ID", other_id.as_str())?;
352 }
353 st.end()
354 }
355}
356
357struct PartyLegalEntityXml<'a>(&'a str);
358
359impl<'a> Serialize for PartyLegalEntityXml<'a> {
360 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
361 where
362 S: Serializer,
363 {
364 let mut st = s.serialize_struct("cac:PartyLegalEntity", 0)?;
365 st.serialize_field("cbc:RegistrationName", self.0)?;
366 st.end()
367 }
368}
369
370struct AccountingSupplierPartyXml<'a>(&'a Seller);
371
372impl<'a> Serialize for AccountingSupplierPartyXml<'a> {
373 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
374 where
375 S: Serializer,
376 {
377 let mut st = s.serialize_struct("cac:AccountingSupplierParty", 0)?;
378 st.serialize_field("cac:Party", &PartyXml(self.0))?;
379 st.end()
380 }
381}
382
383struct AccountingCustomerPartyXml<'a>(Option<&'a Buyer>);
384
385impl<'a> Serialize for AccountingCustomerPartyXml<'a> {
386 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
387 where
388 S: Serializer,
389 {
390 let mut st = s.serialize_struct("cac:AccountingCustomerParty", 0)?;
391 if let Some(party) = self.0 {
392 st.serialize_field("cac:Party", &PartyXml(party))?;
393 } else {
394 st.serialize_field("cac:Party", &EmptyParty)?;
395 }
396 st.end()
397 }
398}
399
400struct EmptyParty;
401
402impl Serialize for EmptyParty {
403 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
404 where
405 S: Serializer,
406 {
407 let st = s.serialize_struct("cac:Party", 0)?;
408 st.end()
409 }
410}
411
412struct NoteXml<'a>(&'a InvoiceNote);
413
414impl<'a> Serialize for NoteXml<'a> {
415 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
416 where
417 S: Serializer,
418 {
419 let note = self.0;
420 let mut st = s.serialize_struct("cbc:Note", 2)?;
421 st.serialize_field("@languageID", ¬e.language)?;
422 st.serialize_field("$text", ¬e.text)?;
423 st.end()
424 }
425}
426
427struct EmbeddedDocumentXml<'a> {
428 mime_code: &'a str,
429 data: &'a str,
430}
431
432impl<'a> Serialize for EmbeddedDocumentXml<'a> {
433 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
434 where
435 S: Serializer,
436 {
437 let mut st = s.serialize_struct("cbc:EmbeddedDocumentBinaryObject", 2)?;
438 st.serialize_field("@mimeCode", self.mime_code)?;
439 st.serialize_field("$text", self.data)?;
440 st.end()
441 }
442}
443
444enum AdditionalDocumentReferenceXml<'a> {
445 InvoiceCounter(&'a str),
446 PreviousInvoiceHash(&'a str),
447 QrCode(&'a str),
448}
449
450struct BillingReferenceXml<'a>(&'a super::OriginalInvoiceRef);
451
452struct InvoiceDocumentReferenceXml<'a>(&'a super::OriginalInvoiceRef);
453
454impl<'a> Serialize for InvoiceDocumentReferenceXml<'a> {
455 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
456 where
457 S: Serializer,
458 {
459 let mut st = s.serialize_struct("cac:InvoiceDocumentReference", 0)?;
460 st.serialize_field("cbc:ID", self.0.id())?;
461 if let Some(uuid) = self.0.uuid() {
462 st.serialize_field("cbc:UUID", uuid)?;
463 }
464 if let Some(issue_date) = self.0.issue_date() {
465 st.serialize_field("cbc:IssueDate", issue_date.as_str())?;
466 }
467 st.end()
468 }
469}
470
471impl<'a> Serialize for BillingReferenceXml<'a> {
472 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
473 where
474 S: Serializer,
475 {
476 let mut st = s.serialize_struct("cac:BillingReference", 0)?;
477 st.serialize_field(
478 "cac:InvoiceDocumentReference",
479 &InvoiceDocumentReferenceXml(self.0),
480 )?;
481 st.end()
482 }
483}
484
485impl<'a> Serialize for AdditionalDocumentReferenceXml<'a> {
486 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
487 where
488 S: Serializer,
489 {
490 let mut st = s.serialize_struct("cac:AdditionalDocumentReference", 0)?;
491 match self {
492 AdditionalDocumentReferenceXml::InvoiceCounter(value) => {
493 st.serialize_field("cbc:ID", "ICV")?;
494 st.serialize_field("cbc:UUID", value)?;
495 }
496 AdditionalDocumentReferenceXml::PreviousInvoiceHash(value) => {
497 st.serialize_field("cbc:ID", "PIH")?;
498 st.serialize_field(
499 "cac:Attachment",
500 &AttachmentXml {
501 mime_code: "text/plain",
502 data: value,
503 },
504 )?;
505 }
506 AdditionalDocumentReferenceXml::QrCode(value) => {
507 st.serialize_field("cbc:ID", "QR")?;
508 st.serialize_field(
509 "cac:Attachment",
510 &AttachmentXml {
511 mime_code: "text/plain",
512 data: value,
513 },
514 )?;
515 }
516 }
517 st.end()
518 }
519}
520
521struct AttachmentXml<'a> {
522 mime_code: &'a str,
523 data: &'a str,
524}
525
526impl<'a> Serialize for AttachmentXml<'a> {
527 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
528 where
529 S: Serializer,
530 {
531 let mut st = s.serialize_struct("cac:Attachment", 0)?;
532 st.serialize_field(
533 "cbc:EmbeddedDocumentBinaryObject",
534 &EmbeddedDocumentXml {
535 mime_code: self.mime_code,
536 data: self.data,
537 },
538 )?;
539 st.end()
540 }
541}
542
543struct TaxCategoryXml<'a> {
544 category: &'a VatCategory,
545 percent: f64,
546}
547
548impl<'a> Serialize for TaxCategoryXml<'a> {
549 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
550 where
551 S: Serializer,
552 {
553 let mut st = s.serialize_struct("cac:TaxCategory", 0)?;
554 st.serialize_field(
555 "cbc:ID",
556 &id_with_scheme_with_agency(
557 "cbc:ID",
558 "UN/ECE 5305",
559 "6",
560 vat_category_code(self.category),
561 ),
562 )?;
563 st.serialize_field("cbc:Percent", &FixedPrecision::new(self.percent, 2))?;
564 st.serialize_field("cac:TaxScheme", &TaxSchemeXml)?;
565 st.end()
566 }
567}
568
569#[derive(Clone)]
570struct TaxSubtotalData<'a> {
571 taxable_amount: f64,
572 tax_amount: f64,
573 currency: &'a str,
574 category: &'a VatCategory,
575 percent: f64,
576}
577
578fn allowance_charge<'a>(
579 charge_indicator: bool,
580 amount: f64,
581 currency: &'a str,
582 reason: &'a str,
583 vat_category: &'a VatCategory,
584 percent: f64,
585) -> impl Serialize + 'a {
586 struct AllowanceChargeSer<'a> {
587 charge_indicator: bool,
588 amount: f64,
589 currency: &'a str,
590 reason: &'a str,
591 vat_category: &'a VatCategory,
592 percent: f64,
593 }
594 impl<'a> Serialize for AllowanceChargeSer<'a> {
595 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
596 where
597 S: Serializer,
598 {
599 let mut st = s.serialize_struct("cac:AllowanceCharge", 0)?;
600 st.serialize_field("cbc:ChargeIndicator", &self.charge_indicator)?;
601 st.serialize_field("cbc:AllowanceChargeReason", self.reason)?;
602 st.serialize_field(
603 "cbc:Amount",
604 ¤cy_amount("cbc:Amount", self.currency, self.amount),
605 )?;
606 st.serialize_field(
607 "cac:TaxCategory",
608 &TaxCategoryXml {
609 category: self.vat_category,
610 percent: self.percent,
611 },
612 )?;
613 st.end()
614 }
615 }
616 AllowanceChargeSer {
617 charge_indicator,
618 amount,
619 currency,
620 reason,
621 vat_category,
622 percent,
623 }
624}
625
626fn tax_total<'a>(
627 amount: f64,
628 currency: &'a str,
629 subtotal: Option<TaxSubtotalData<'a>>,
630) -> impl Serialize + 'a {
631 struct TaxTotalSer<'a> {
632 amount: f64,
633 currency: &'a str,
634 subtotal: Option<TaxSubtotalData<'a>>,
635 }
636 impl<'a> Serialize for TaxTotalSer<'a> {
637 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
638 where
639 S: Serializer,
640 {
641 let mut st = s.serialize_struct("cac:TaxTotal", 0)?;
642 st.serialize_field(
643 "cbc:TaxAmount",
644 ¤cy_amount("cbc:TaxAmount", self.currency, self.amount),
645 )?;
646 if let Some(subtotal) = &self.subtotal {
647 st.serialize_field("cac:TaxSubtotal", &tax_subtotal(subtotal.clone()))?;
648 }
649 st.end()
650 }
651 }
652 TaxTotalSer {
653 amount,
654 currency,
655 subtotal,
656 }
657}
658
659fn tax_subtotal<'a>(data: TaxSubtotalData<'a>) -> impl Serialize + 'a {
660 struct TaxSubtotalSer<'a> {
661 data: TaxSubtotalData<'a>,
662 }
663 impl<'a> Serialize for TaxSubtotalSer<'a> {
664 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
665 where
666 S: Serializer,
667 {
668 let data = &self.data;
669 let mut st = s.serialize_struct("cac:TaxSubtotal", 0)?;
670 st.serialize_field(
671 "cbc:TaxableAmount",
672 ¤cy_amount("cbc:TaxableAmount", data.currency, data.taxable_amount),
673 )?;
674 st.serialize_field(
675 "cbc:TaxAmount",
676 ¤cy_amount("cbc:TaxAmount", data.currency, data.tax_amount),
677 )?;
678 st.serialize_field(
679 "cac:TaxCategory",
680 &TaxCategoryXml {
681 category: data.category,
682 percent: data.percent,
683 },
684 )?;
685 st.end()
686 }
687 }
688 TaxSubtotalSer { data }
689}
690
691fn legal_monetary_total<'a>(
692 currency: &'a str,
693 line_extension: f64,
694 tax_exclusive: f64,
695 tax_inclusive: f64,
696 allowance_total: f64,
697 prepaid: f64,
698 payable: f64,
699) -> impl Serialize + 'a {
700 struct LegalMonetaryTotalSer<'a> {
701 currency: &'a str,
702 line_extension: f64,
703 tax_exclusive: f64,
704 tax_inclusive: f64,
705 allowance_total: f64,
706 prepaid: f64,
707 payable: f64,
708 }
709 impl<'a> Serialize for LegalMonetaryTotalSer<'a> {
710 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
711 where
712 S: Serializer,
713 {
714 let mut st = s.serialize_struct("cac:LegalMonetaryTotal", 0)?;
715 st.serialize_field(
716 "cbc:LineExtensionAmount",
717 ¤cy_amount(
718 "cbc:LineExtensionAmount",
719 self.currency,
720 self.line_extension,
721 ),
722 )?;
723 st.serialize_field(
724 "cbc:TaxExclusiveAmount",
725 ¤cy_amount("cbc:TaxExclusiveAmount", self.currency, self.tax_exclusive),
726 )?;
727 st.serialize_field(
728 "cbc:TaxInclusiveAmount",
729 ¤cy_amount("cbc:TaxInclusiveAmount", self.currency, self.tax_inclusive),
730 )?;
731 st.serialize_field(
732 "cbc:AllowanceTotalAmount",
733 ¤cy_amount(
734 "cbc:AllowanceTotalAmount",
735 self.currency,
736 self.allowance_total,
737 ),
738 )?;
739 st.serialize_field(
740 "cbc:PrepaidAmount",
741 ¤cy_amount("cbc:PrepaidAmount", self.currency, self.prepaid),
742 )?;
743 st.serialize_field(
744 "cbc:PayableAmount",
745 ¤cy_amount("cbc:PayableAmount", self.currency, self.payable),
746 )?;
747 st.end()
748 }
749 }
750 LegalMonetaryTotalSer {
751 currency,
752 line_extension,
753 tax_exclusive,
754 tax_inclusive,
755 allowance_total,
756 prepaid,
757 payable,
758 }
759}
760
761fn invoice_line_tax_total<'a>(
762 currency: &'a str,
763 tax_amount: f64,
764 rounding_amount: f64,
765) -> impl Serialize + 'a {
766 struct InvoiceLineTaxTotalSer<'a> {
767 currency: &'a str,
768 tax_amount: f64,
769 rounding_amount: f64,
770 }
771 impl<'a> Serialize for InvoiceLineTaxTotalSer<'a> {
772 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
773 where
774 S: Serializer,
775 {
776 let mut st = s.serialize_struct("cac:TaxTotal", 0)?;
777 st.serialize_field(
778 "cbc:TaxAmount",
779 ¤cy_amount("cbc:TaxAmount", self.currency, self.tax_amount),
780 )?;
781 st.serialize_field(
782 "cbc:RoundingAmount",
783 ¤cy_amount("cbc:RoundingAmount", self.currency, self.rounding_amount),
784 )?;
785 st.end()
786 }
787 }
788 InvoiceLineTaxTotalSer {
789 currency,
790 tax_amount,
791 rounding_amount,
792 }
793}
794
795fn invoice_item<'a>(
796 description: &'a str,
797 vat_category: &'a VatCategory,
798 vat_rate: f64,
799) -> impl Serialize + 'a {
800 struct InvoiceItemSer<'a> {
801 description: &'a str,
802 vat_category: &'a VatCategory,
803 vat_rate: f64,
804 }
805 impl<'a> Serialize for InvoiceItemSer<'a> {
806 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
807 where
808 S: Serializer,
809 {
810 let mut st = s.serialize_struct("cac:Item", 0)?;
811 st.serialize_field("cbc:Name", self.description)?;
812 st.serialize_field(
813 "cac:ClassifiedTaxCategory",
814 &TaxCategoryXml {
815 category: self.vat_category,
816 percent: self.vat_rate,
817 },
818 )?;
819 st.end()
820 }
821 }
822 InvoiceItemSer {
823 description,
824 vat_category,
825 vat_rate,
826 }
827}
828
829fn invoice_line_price<'a>(currency: &'a str, unit_price: f64) -> impl Serialize + 'a {
830 struct InvoiceLinePriceSer<'a> {
831 currency: &'a str,
832 unit_price: f64,
833 }
834 impl<'a> Serialize for InvoiceLinePriceSer<'a> {
835 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
836 where
837 S: Serializer,
838 {
839 let mut st = s.serialize_struct("cac:Price", 0)?;
840 st.serialize_field(
841 "cbc:PriceAmount",
842 ¤cy_amount_with_precision(
843 "cbc:PriceAmount",
844 self.currency,
845 self.unit_price,
846 2,
847 ),
848 )?;
849 st.end()
850 }
851 }
852 InvoiceLinePriceSer {
853 currency,
854 unit_price,
855 }
856}
857
858fn payment_means<'a>(code: &'a str, instruction_note: Option<&'a str>) -> impl Serialize + 'a {
859 struct PaymentMeansSer<'a> {
860 code: &'a str,
861 instruction_note: Option<&'a str>,
862 }
863 impl<'a> Serialize for PaymentMeansSer<'a> {
864 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
865 where
866 S: Serializer,
867 {
868 let mut st = s.serialize_struct("cac:PaymentMeans", 0)?;
869 st.serialize_field("cbc:PaymentMeansCode", self.code)?;
870 if let Some(note) = self.instruction_note {
871 st.serialize_field("cbc:InstructionNote", note)?;
872 }
873 st.end()
874 }
875 }
876 PaymentMeansSer {
877 code,
878 instruction_note,
879 }
880}
881
882impl<'a, R: PartyRole> Serialize for PartyXml<'a, R> {
883 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
884 where
885 S: Serializer,
886 {
887 let p = self.0;
888
889 let mut st = s.serialize_struct("cac:Party", 0)?;
890
891 if let Some(other_id) = &p.other_id {
892 st.serialize_field("cac:PartyIdentification", &PartyIdentificationXml(other_id))?;
893 }
894
895 st.serialize_field("cac:PostalAddress", &AddressXml(&p.address))?;
898
899 if let Some(vat) = &p.vat_id {
900 st.serialize_field("cac:PartyTaxScheme", &VatSchemeXml(vat))?;
901 }
902
903 st.serialize_field("cac:PartyLegalEntity", &PartyLegalEntityXml(&p.name))?;
905
906 st.end()
907 }
908}
909
910struct AddressXml<'a>(&'a Address);
911
912impl<'a> Serialize for AddressXml<'a> {
913 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
914 where
915 S: Serializer,
916 {
917 let a = self.0;
918 let mut st = s.serialize_struct("cac:PostalAddress", 0)?;
919
920 st.serialize_field("cbc:StreetName", &a.street)?;
921 if let Some(additional) = &a.additional_street {
922 st.serialize_field("cbc:AdditionalStreetName", additional)?;
923 }
924 st.serialize_field("cbc:BuildingNumber", &a.building_number)?;
925 if let Some(subdivision) = &a.subdivision {
926 st.serialize_field("cbc:CitySubdivisionName", subdivision)?;
927 }
928 st.serialize_field("cbc:CityName", &a.city)?;
929 st.serialize_field("cbc:PostalZone", &a.postal_code)?;
930 let alpha2 = a.country_code.alpha2();
931 st.serialize_field("cac:Country", &CountryXml(&alpha2))?;
932
933 st.end()
934 }
935}
936
937struct CountryXml<'a>(&'a str);
938
939impl<'a> Serialize for CountryXml<'a> {
940 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
941 where
942 S: Serializer,
943 {
944 let mut st = s.serialize_struct("cac:Country", 0)?;
945 st.serialize_field("cbc:IdentificationCode", self.0)?;
946 st.end()
947 }
948}
949
950struct InvoiceLineXml<'a>(usize, &'a LineItem, &'a InvoiceData);
951
952impl<'a> Serialize for InvoiceLineXml<'a> {
953 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
954 where
955 S: Serializer,
956 {
957 let (idx, li, invoice) = (self.0, self.1, self.2);
958
959 let mut st = s.serialize_struct("cac:InvoiceLine", 0)?;
960
961 st.serialize_field("cbc:ID", &idx.to_string())?;
962
963 st.serialize_field(
964 "cbc:InvoicedQuantity",
965 &quantity_with_unit("cbc:InvoicedQuantity", li.quantity, &li.unit_code),
966 )?;
967 st.serialize_field(
968 "cbc:LineExtensionAmount",
969 ¤cy_amount(
970 "cbc:LineExtensionAmount",
971 invoice.currency.as_str(),
972 li.total_amount,
973 ),
974 )?;
975 st.serialize_field(
976 "cac:TaxTotal",
977 &invoice_line_tax_total(
978 invoice.currency.as_str(),
979 li.vat_amount,
980 li.total_amount + li.vat_amount,
981 ),
982 )?;
983 st.serialize_field(
984 "cac:Item",
985 &invoice_item(&li.description, &li.vat_category, li.vat_rate),
986 )?;
987 st.serialize_field(
988 "cac:Price",
989 &invoice_line_price(invoice.currency.as_str(), li.unit_price),
990 )?;
991
992 st.end()
993 }
994}
995
996pub trait ToXml {
1009 fn to_xml_with_format(&self, format: XmlFormat) -> Result<String, InvoiceXmlError>;
1010
1011 fn to_xml(&self) -> Result<String, InvoiceXmlError> {
1012 self.to_xml_with_format(XmlFormat::Pretty {
1013 indent_char: ' ',
1014 indent_size: 2,
1015 })
1016 }
1017
1018 fn to_xml_pretty(&self) -> Result<String, InvoiceXmlError> {
1019 self.to_xml_with_format(XmlFormat::Pretty {
1020 indent_char: ' ',
1021 indent_size: 2,
1022 })
1023 }
1024}
1025
1026
1027impl ToXml for FinalizedInvoice {
1028 fn to_xml_with_format(&self, format: XmlFormat) -> Result<String, InvoiceXmlError> {
1029 to_xml_with_format(self, format)
1030 }
1031}
1032
1033impl ToXml for SignedInvoice {
1034 fn to_xml_with_format(&self, format: XmlFormat) -> Result<String, InvoiceXmlError> {
1035 let _ = format;
1038 Ok(self.xml().to_string())
1039 }
1040}
1041
1042fn to_xml_with_format<T: InvoiceView + ?Sized>(
1043 invoice: &T,
1044 format: XmlFormat,
1045) -> Result<String, InvoiceXmlError> {
1046 let mut buffer = String::with_capacity(4096);
1047 buffer.push_str(r#"<?xml version="1.0" encoding="UTF-8"?>"#);
1048 buffer.push('\n');
1049
1050 {
1051 let mut serializer = QuickXmlSerializer::new(&mut buffer);
1052 if let XmlFormat::Pretty {
1053 indent_char,
1054 indent_size,
1055 } = format
1056 {
1057 serializer.indent(indent_char, indent_size);
1058 }
1059 InvoiceXml(invoice).serialize(serializer)?;
1060 }
1061
1062 Ok(buffer)
1063}
1064
1065impl<'a, T: InvoiceView + ?Sized> Serialize for InvoiceXml<'a, T> {
1066 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1067 where
1068 S: Serializer,
1069 {
1070 let view = self.0;
1071 let data = view.data();
1072 let totals = InvoiceTotals::new(view);
1073 let currency_code = totals.currency();
1074
1075 let mut root = serializer.serialize_struct("Invoice", 0)?;
1076
1077 root.serialize_field(
1079 "@xmlns",
1080 "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2",
1081 )?;
1082 root.serialize_field(
1083 "@xmlns:cac",
1084 "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2",
1085 )?;
1086 root.serialize_field(
1087 "@xmlns:cbc",
1088 "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2",
1089 )?;
1090 root.serialize_field(
1091 "@xmlns:ext",
1092 "urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2",
1093 )?;
1094
1095 root.serialize_field("cbc:ProfileID", "reporting:1.0")?;
1097 root.serialize_field("cbc:ID", &data.id)?;
1098 root.serialize_field("cbc:UUID", &data.uuid)?;
1099 root.serialize_field("cbc:IssueDate", data.issue_datetime.date_str())?;
1100 root.serialize_field("cbc:IssueTime", data.issue_datetime.time_str())?;
1101
1102 root.serialize_field("cbc:InvoiceTypeCode", &InvoiceTypeView(&data.invoice_type))?;
1104 if let Some(note) = data.note.as_ref() {
1105 root.serialize_field("cbc:Note", &NoteXml(note))?;
1106 }
1107 root.serialize_field("cbc:DocumentCurrencyCode", currency_code)?;
1108 root.serialize_field("cbc:TaxCurrencyCode", currency_code)?;
1109
1110 match &data.invoice_type {
1112 InvoiceType::CreditNote(_, original, _) | InvoiceType::DebitNote(_, original, _) => {
1113 root.serialize_field("cac:BillingReference", &BillingReferenceXml(original))?;
1114 }
1115 _ => {}
1116 }
1117
1118 let counter = data.invoice_counter.to_string();
1120 root.serialize_field(
1121 "cac:AdditionalDocumentReference",
1122 &AdditionalDocumentReferenceXml::InvoiceCounter(&counter),
1123 )?;
1124 root.serialize_field(
1125 "cac:AdditionalDocumentReference",
1126 &AdditionalDocumentReferenceXml::PreviousInvoiceHash(&data.previous_invoice_hash),
1127 )?;
1128 if let Some(qr) = view.qr_code() {
1129 root.serialize_field(
1130 "cac:AdditionalDocumentReference",
1131 &AdditionalDocumentReferenceXml::QrCode(qr),
1132 )?;
1133 }
1134
1135 root.serialize_field(
1137 "cac:AccountingSupplierParty",
1138 &AccountingSupplierPartyXml(&data.seller),
1139 )?;
1140 root.serialize_field(
1141 "cac:AccountingCustomerParty",
1142 &AccountingCustomerPartyXml(data.buyer.as_ref()),
1143 )?;
1144
1145 let instruction_note = match &data.invoice_type {
1147 InvoiceType::CreditNote(_, _, reason) | InvoiceType::DebitNote(_, _, reason) => {
1148 if reason.trim().is_empty() {
1149 None
1150 } else {
1151 Some(reason.as_str())
1152 }
1153 }
1154 _ => None,
1155 };
1156 root.serialize_field(
1157 "cac:PaymentMeans",
1158 &payment_means(&data.payment_means_code, instruction_note),
1159 )?;
1160
1161 if totals.allowance_total() > 0.0 || data.allowance_reason.is_some() {
1163 root.serialize_field(
1164 "cac:AllowanceCharge",
1165 &allowance_charge(
1166 false,
1167 totals.allowance_total(),
1168 currency_code,
1169 data.allowance_reason.as_deref().unwrap_or("discount"),
1170 totals.vat_category(),
1171 totals.vat_percent(),
1172 ),
1173 )?;
1174 }
1175 if totals.charge_total() > 0.0 {
1176 root.serialize_field(
1177 "cac:AllowanceCharge",
1178 &allowance_charge(
1179 true,
1180 totals.charge_total(),
1181 currency_code,
1182 data.allowance_reason.as_deref().unwrap_or("charge"),
1183 totals.vat_category(),
1184 totals.vat_percent(),
1185 ),
1186 )?;
1187 }
1188
1189 let tax_subtotal = TaxSubtotalData {
1191 taxable_amount: totals.taxable_amount(),
1192 tax_amount: totals.tax_amount(),
1193 currency: currency_code,
1194 category: totals.vat_category(),
1195 percent: totals.vat_percent(),
1196 };
1197 root.serialize_field(
1198 "cac:TaxTotal",
1199 &tax_total(totals.tax_amount(), currency_code, None),
1200 )?;
1201 root.serialize_field(
1202 "cac:TaxTotal",
1203 &tax_total(totals.tax_amount(), currency_code, Some(tax_subtotal)),
1204 )?;
1205
1206 root.serialize_field(
1208 "cac:LegalMonetaryTotal",
1209 &legal_monetary_total(
1210 currency_code,
1211 totals.line_extension(),
1212 totals.taxable_amount(),
1213 totals.tax_inclusive_amount(),
1214 totals.allowance_total(),
1215 0.0,
1216 totals.tax_inclusive_amount(),
1217 ),
1218 )?;
1219
1220 for (i, line) in data.line_items.iter().enumerate() {
1222 root.serialize_field("cac:InvoiceLine", &InvoiceLineXml(i + 1, line, data))?;
1223 }
1224
1225 root.end()
1226 }
1227}
1228
1229#[cfg(test)]
1230mod tests {
1231 #[allow(unused_imports)]
1232 use super::*;
1233 use crate::invoice::{
1234 Address, CountryCode, InvoiceBuilder, InvoiceSubType, InvoiceType, LineItem, Party,
1235 SellerRole, VatCategory,
1236 };
1237 #[test]
1238 fn test_invoice_xml_serialization() {
1239 let seller = Party::<SellerRole>::new(
1240 "Acme Inc".into(),
1241 Address {
1242 country_code: CountryCode::parse("SAU").expect("country code"),
1243 city: "Riyadh".into(),
1244 street: "King Fahd".into(),
1245 additional_street: None,
1246 building_number: "1234".into(),
1247 additional_number: Some("5678".into()),
1248 postal_code: "12222".into(),
1249 subdivision: None,
1250 district: None,
1251 },
1252 "301121971500003",
1253 None,
1254 )
1255 .expect("valid seller");
1256
1257 let line_item = LineItem::new("Item", 1.0, "PCE", 100.0, 15.0, VatCategory::Standard);
1258
1259 let mut builder = InvoiceBuilder::new(InvoiceType::Tax(InvoiceSubType::Simplified));
1260 builder
1261 .set_id("INV-1")
1262 .set_uuid("uuid-123")
1263 .set_issue_datetime("2024-01-01T12:30:00Z")
1264 .set_currency("SAR")
1265 .set_previous_invoice_hash("hash")
1266 .set_invoice_counter(0)
1267 .set_seller(seller)
1268 .set_payment_means_code("10")
1269 .set_vat_category(VatCategory::Standard)
1270 .add_line_item(line_item);
1271 let invoice = builder.build().expect("build invoice");
1272 let xml_result = invoice.to_xml().unwrap();
1273 println!("{xml_result:?}");
1274
1275 let pretty = invoice.to_xml_pretty().unwrap();
1276 println!("{pretty}");
1277 }
1278}