Skip to main content

fatoora_core/invoice/
xml.rs

1//! XML serialization for invoices.
2use 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/// Wrapper for serializing invoices to XML.
16#[derive(Debug, Clone, Copy)]
17pub struct InvoiceXml<'a, T: InvoiceView + ?Sized>(pub &'a T);
18
19/// XML serialization error.
20#[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/// XML formatting options.
30#[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", &note.language)?;
422        st.serialize_field("$text", &note.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                &currency_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                &currency_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                &currency_amount("cbc:TaxableAmount", data.currency, data.taxable_amount),
673            )?;
674            st.serialize_field(
675                "cbc:TaxAmount",
676                &currency_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                &currency_amount(
718                    "cbc:LineExtensionAmount",
719                    self.currency,
720                    self.line_extension,
721                ),
722            )?;
723            st.serialize_field(
724                "cbc:TaxExclusiveAmount",
725                &currency_amount("cbc:TaxExclusiveAmount", self.currency, self.tax_exclusive),
726            )?;
727            st.serialize_field(
728                "cbc:TaxInclusiveAmount",
729                &currency_amount("cbc:TaxInclusiveAmount", self.currency, self.tax_inclusive),
730            )?;
731            st.serialize_field(
732                "cbc:AllowanceTotalAmount",
733                &currency_amount(
734                    "cbc:AllowanceTotalAmount",
735                    self.currency,
736                    self.allowance_total,
737                ),
738            )?;
739            st.serialize_field(
740                "cbc:PrepaidAmount",
741                &currency_amount("cbc:PrepaidAmount", self.currency, self.prepaid),
742            )?;
743            st.serialize_field(
744                "cbc:PayableAmount",
745                &currency_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                &currency_amount("cbc:TaxAmount", self.currency, self.tax_amount),
780            )?;
781            st.serialize_field(
782                "cbc:RoundingAmount",
783                &currency_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                &currency_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        // Identification
896        // Address
897        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        // Legal entity
904        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            &currency_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
996/// Serialize invoices to XML.
997///
998/// # Examples
999/// ```rust,no_run
1000/// use fatoora_core::invoice::xml::ToXml;
1001/// use fatoora_core::invoice::FinalizedInvoice;
1002///
1003/// let invoice: FinalizedInvoice = unimplemented!();
1004/// let xml = invoice.to_xml()?;
1005/// # let _ = xml;
1006/// # Ok::<(), fatoora_core::invoice::xml::InvoiceXmlError>(())
1007/// ```
1008pub 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        // FIXME: sort this out properly
1036        // to_xml_with_format(self, format)
1037        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        // ---- namespaces (attributes) ----
1078        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        // ---- identifiers & issue info ----
1096        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        // ---- invoice type ----
1103        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        // ---- credit/debit references ----
1111        match &data.invoice_type {
1112            InvoiceType::CreditNote(_, original, _) | InvoiceType::DebitNote(_, original, _) => {
1113                root.serialize_field("cac:BillingReference", &BillingReferenceXml(original))?;
1114            }
1115            _ => {}
1116        }
1117
1118        // ---- supporting references ----
1119        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        // ---- parties ----
1136        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        // ---- payment ----
1146        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        // ---- allowance / charges ----
1162        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        // ---- tax totals ----
1190        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        // ---- legal monetary totals ----
1207        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        // ---- lines ----
1221        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}