Skip to main content

core_invoice/
invoice.rs

1use crate::amount::{Amount, InvoiceAmount, UnitPriceAmount};
2use crate::attachment::Attachment;
3use crate::code::Code;
4use crate::date::Date;
5use crate::identifier::{DocumentReference, Identifier};
6use crate::kind::DocumentKind;
7use crate::numeric::{Percentage, Quantity};
8use crate::payment::PaymentMeans;
9use crate::profile::Profile;
10use crate::tax::{TaxCategory, TaxSystem};
11
12/// Postal address (BG-5/8/12/15). Country is BT-40 / BT-55 / BT-69 / BT-80.
13#[derive(Debug, Clone, PartialEq, Eq, Default)]
14pub struct PostalAddress {
15    /// BT-35 / BT-50 / BT-75 street.
16    pub line1: Option<String>,
17    /// BT-36 / BT-51 / BT-76 additional street.
18    pub line2: Option<String>,
19    /// BT-162 / BT-163 / BT-164 additional address line.
20    pub line3: Option<String>,
21    /// BT-37 / BT-52 / BT-77 city.
22    pub city: Option<String>,
23    /// BT-38 / BT-53 / BT-78 post code.
24    pub post_code: Option<String>,
25    /// BT-39 / BT-54 / BT-79 subdivision.
26    pub subdivision: Option<String>,
27    /// BT-40 / BT-55 / BT-69 / BT-80 country code.
28    pub country: Option<Code>,
29}
30
31/// BG-6 / BG-9 contact.
32#[derive(Debug, Clone, PartialEq, Eq, Default)]
33pub struct Contact {
34    /// BT-41 / BT-56 contact point.
35    pub point: Option<String>,
36    /// BT-42 / BT-57 telephone.
37    pub phone: Option<String>,
38    /// BT-43 / BT-58 email.
39    pub email: Option<String>,
40}
41
42/// Extra PartyTaxScheme row (PINT-MY SST + TIN + TTx).
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct PartyTax {
45    pub id: Identifier,
46    pub scheme: String,
47}
48
49/// Party identifiers are four slots, not a leftover `tax_id`:
50///
51/// - `legal_registration` — BT-30 / BT-47 (PINT-MY BRN, unschemed)
52/// - `vat_identifier` — BT-31 / BT-48
53/// - `tax_registration` — BT-32 (PINT-MY TIN, scheme `GST`)
54/// - `electronic_address` — BT-34 / BT-49 (PINT-MY endpoint scheme `0230`)
55///
56/// Country (BT-40 / BT-55) lives on [`PostalAddress::country`]. [`Party::country`]
57/// reads that field.
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct Party {
60    pub name: String,
61    pub trading_name: Option<String>,
62    pub identifiers: Vec<Identifier>,
63    pub legal_registration: Option<Identifier>,
64    pub vat_identifier: Option<Identifier>,
65    pub tax_registration: Option<Identifier>,
66    pub electronic_address: Option<Identifier>,
67    pub party_taxes: Vec<PartyTax>,
68    pub additional_legal: Option<String>,
69    pub address: Option<PostalAddress>,
70    pub contact: Option<Contact>,
71}
72
73impl Party {
74    pub fn new(name: impl Into<String>, country: impl Into<String>) -> Self {
75        let country = country.into();
76        let address = if country.trim().is_empty() {
77            None
78        } else {
79            Some(PostalAddress {
80                country: Some(Code::new(country)),
81                ..PostalAddress::default()
82            })
83        };
84        Self {
85            name: name.into(),
86            trading_name: None,
87            identifiers: Vec::new(),
88            legal_registration: None,
89            vat_identifier: None,
90            tax_registration: None,
91            electronic_address: None,
92            party_taxes: Vec::new(),
93            additional_legal: None,
94            address,
95            contact: None,
96        }
97    }
98
99    /// BT-40 / BT-55 from [`PostalAddress::country`]. Empty when address or code is absent.
100    pub fn country(&self) -> &str {
101        self.address
102            .as_ref()
103            .and_then(|a| a.country.as_ref())
104            .map(Code::as_str)
105            .unwrap_or("")
106    }
107}
108
109/// BG-29 item price. BT-146 net; BT-147 discount; BT-148 gross (unit price, not Amount.Type).
110#[derive(Debug, Clone, PartialEq, Eq)]
111pub struct Price {
112    /// BT-146 item net price.
113    pub net: UnitPriceAmount,
114    /// BT-147 item price discount (`Price/AllowanceCharge/Amount`).
115    pub discount: Option<UnitPriceAmount>,
116    /// BT-148 item gross price (`Price/AllowanceCharge/BaseAmount`).
117    pub gross: Option<UnitPriceAmount>,
118    /// BT-149 item price base quantity.
119    pub base_qty: Option<Quantity>,
120    /// BT-150 item price base quantity unit.
121    pub base_unit: Option<Code>,
122}
123
124/// Line allowance/charge (BG-27/28). No tax child: Peppol/PINT inherit the line category.
125/// Line A/C already sits in BT-131. Do not add them again in taxable_for.
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub struct LineAllowanceCharge {
128    /// BT-136 / BT-141 amount.
129    pub amount: InvoiceAmount,
130    /// BT-137 / BT-142 base amount.
131    pub base: Option<InvoiceAmount>,
132    /// BT-138 / BT-143 percentage.
133    pub percent: Option<Percentage>,
134    /// BT-139 / BT-144 reason.
135    pub reason: Option<String>,
136    /// BT-140 / BT-145 reason code.
137    pub reason_code: Option<Code>,
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct Line {
142    /// BT-126 line identifier, not a GTIN (BT-157).
143    pub id: String,
144    /// BT-153 item name.
145    pub name: String,
146    /// BT-131 line net amount.
147    pub net: Amount,
148    /// BT-151 / BT-152 classified tax category.
149    pub tax: TaxCategory,
150    /// BT-129 invoiced quantity.
151    pub quantity: Option<Quantity>,
152    /// BT-130 unit of measure.
153    pub unit: Option<Code>,
154    /// BG-29 item price.
155    pub price: Option<Price>,
156    /// BT-127 line note.
157    pub note: Option<String>,
158    /// BT-154 item description.
159    pub description: Option<String>,
160    /// BG-26 invoicing period (BT-134/BT-135). Not [`Invoice::period`] (BG-14).
161    pub period: Option<Period>,
162    pub allowances: Vec<LineAllowanceCharge>,
163    pub charges: Vec<LineAllowanceCharge>,
164    /// BT-157 Item standard identifier (often GTIN). UBL `StandardItemIdentification`; scheme ICD (BR-64 / BR-CL-21). Not BT-155.
165    pub standard_id: Option<Identifier>,
166    /// BT-155 Seller's item identifier. UBL `SellersItemIdentification`. Not BT-156, not BT-157.
167    pub item_id: Option<Identifier>,
168    /// BT-156 Buyer's item identifier. UBL `BuyersItemIdentification`. Not BT-155, not BT-157.
169    pub buyer_id: Option<Identifier>,
170    /// BT-132 referenced purchase order line (`OrderLineReference/LineID`). Not BT-13, not BT-126.
171    pub order_line: Option<String>,
172    /// BT-133 invoice line buyer accounting reference (`cbc:AccountingCost`). Not header BT-19.
173    pub accounting_reference: Option<String>,
174    /// BG-32 item attributes (BT-160 name, BT-161 value). Not BT-158 classifications.
175    pub attributes: Vec<ItemAttribute>,
176    /// BT-159 item origin country (BR-CL-15), not BT-80.
177    pub origin_country: Option<Code>,
178    /// BG-32 classification identifiers (PINT-MY CLASS is listID `CG`, not LHDN).
179    pub classifications: Vec<Identifier>,
180    /// Line invoiced object (BT-128). Peppol R101: DocumentTypeCode 130 only.
181    pub invoiced_object: Option<Identifier>,
182    /// UBL DocumentTypeCode on the line DocumentReference. Absent means 130.
183    pub invoiced_object_code: Option<Code>,
184    /// Extra ClassifiedTaxCategory after BT-151 (PINT-MY TTX beside HVG/SA). Not BT-151.
185    pub extra_tax: Vec<TaxCategory>,
186    /// Line `TaxTotal/cbc:TaxAmount`. ALIGNED-IBRP-TTX-09-MY sums this on TTX lines. Not BT-117.
187    pub tax_total: Option<InvoiceAmount>,
188}
189
190impl Line {
191    pub fn new(
192        id: impl Into<String>,
193        name: impl Into<String>,
194        net: Amount,
195        tax: TaxCategory,
196    ) -> Self {
197        Self {
198            id: id.into(),
199            name: name.into(),
200            net,
201            tax,
202            quantity: None,
203            unit: None,
204            price: None,
205            note: None,
206            description: None,
207            period: None,
208            allowances: vec![],
209            charges: vec![],
210            standard_id: None,
211            item_id: None,
212            buyer_id: None,
213            order_line: None,
214            accounting_reference: None,
215            attributes: vec![],
216            origin_country: None,
217            classifications: vec![],
218            invoiced_object: None,
219            invoiced_object_code: None,
220            extra_tax: vec![],
221            tax_total: None,
222        }
223    }
224}
225
226/// BG-32 item attribute: BT-160 name + BT-161 value (`AdditionalItemProperty`).
227#[derive(Debug, Clone, PartialEq, Eq)]
228pub struct ItemAttribute {
229    pub name: String,
230    pub value: String,
231}
232
233/// BT-21 / BT-22 invoice note.
234#[derive(Debug, Clone, PartialEq, Eq)]
235pub struct InvoiceNote {
236    /// BT-21 note subject code (UNCL 4451).
237    pub subject: Option<Code>,
238    /// BT-22 note text.
239    pub text: String,
240}
241
242/// BG-3 preceding invoice reference.
243#[derive(Debug, Clone, PartialEq, Eq)]
244pub struct PrecedingInvoice {
245    /// BT-25 preceding invoice reference.
246    pub reference: DocumentReference,
247    /// BT-26 preceding invoice issue date.
248    pub issue_date: Option<Date>,
249}
250
251/// Header invoicing period BG-14 (BT-73/74) or line period BG-26 (BT-134/135).
252#[derive(Debug, Clone, PartialEq, Eq)]
253pub struct Period {
254    /// BT-73 / BT-134 start.
255    pub start: Option<Date>,
256    /// BT-74 / BT-135 end.
257    pub end: Option<Date>,
258}
259
260/// BG-10 payee (if different from seller).
261#[derive(Debug, Clone, PartialEq, Eq)]
262pub struct Payee {
263    /// BT-59 payee name.
264    pub name: String,
265    /// BT-60 payee identifier.
266    pub identifier: Option<Identifier>,
267    /// BT-61 payee legal registration identifier.
268    pub legal_registration: Option<Identifier>,
269}
270
271/// BG-11 seller tax representative.
272#[derive(Debug, Clone, PartialEq, Eq)]
273pub struct TaxRepresentative {
274    /// BT-62 name.
275    pub name: String,
276    /// BT-63 VAT identifier.
277    pub vat_identifier: Option<Identifier>,
278    /// BG-12 postal address (BT-69 country).
279    pub address: Option<PostalAddress>,
280}
281
282/// BG-13 delivery information.
283#[derive(Debug, Clone, PartialEq, Eq)]
284pub struct Delivery {
285    /// BT-70 deliver-to party name.
286    pub name: Option<String>,
287    /// BT-71 deliver-to location identifier.
288    pub location_id: Option<Identifier>,
289    /// BT-72 actual delivery date.
290    pub date: Option<Date>,
291    /// BG-15 deliver-to address.
292    pub address: Option<PostalAddress>,
293}
294
295/// Payment instructions. BT-81 is `means_code`. Account/IBAN/BIC, card PAN, and
296/// mandate live only on [`PaymentMeans`] (exclusive BG-17/18/19).
297#[derive(Debug, Clone, PartialEq, Eq)]
298pub struct PaymentInstructions {
299    /// BT-81 Payment means type code.
300    pub means_code: Option<Code>,
301    /// BT-82 Payment means text (`@name` on UBL PaymentMeansCode, not InstructionNote).
302    pub means_text: Option<String>,
303    /// BT-83 Remittance information (UBL PaymentID).
304    pub remittance: Option<String>,
305    pub means: Option<PaymentMeans>,
306}
307
308/// BG-20 / BG-21 document level allowance or charge.
309#[derive(Debug, Clone, PartialEq, Eq)]
310pub struct AllowanceCharge {
311    /// BT-92 / BT-99 amount.
312    pub amount: InvoiceAmount,
313    /// BT-93 / BT-100 base amount.
314    pub base: Option<InvoiceAmount>,
315    /// BT-94 / BT-101 percentage.
316    pub percent: Option<Percentage>,
317    /// BT-97 / BT-104 reason.
318    pub reason: Option<String>,
319    /// BT-98 / BT-105 reason code.
320    pub reason_code: Option<Code>,
321    /// BT-95/96 or BT-102/103 tax category and rate.
322    pub tax: Option<TaxCategory>,
323}
324
325/// BG-23 VAT/GST/SST breakdown row.
326#[derive(Debug, Clone, PartialEq, Eq)]
327pub struct TaxBreakdown {
328    pub system: TaxSystem,
329    pub scheme: String,
330    /// BT-118 category code.
331    pub category: Code,
332    /// BT-119 rate. None for EN `O` and PINT-MY TTX.
333    pub rate: Option<Percentage>,
334    /// BT-116 taxable amount.
335    pub taxable: InvoiceAmount,
336    /// BT-117 tax amount.
337    pub tax: InvoiceAmount,
338    /// BT-120 exemption reason.
339    pub exemption_reason: Option<String>,
340    /// BT-121 exemption reason code.
341    pub exemption_code: Option<Code>,
342}
343
344/// BG-22 document totals. Absent optional amounts are `None`, not 0.00.
345#[derive(Debug, Clone, PartialEq, Eq, Default)]
346pub struct DocumentTotals {
347    /// BT-106 sum of invoice line net amounts.
348    pub line_net: Option<InvoiceAmount>,
349    /// BT-107 sum of allowances on document level.
350    pub allowance_total: Option<InvoiceAmount>,
351    /// BT-108 sum of charges on document level.
352    pub charge_total: Option<InvoiceAmount>,
353    /// BT-109 invoice total amount without VAT.
354    pub without_tax: Option<InvoiceAmount>,
355    /// BT-110 invoice total VAT amount.
356    pub tax_total: Option<InvoiceAmount>,
357    /// BT-111 invoice total VAT amount in accounting currency.
358    pub tax_total_accounting: Option<InvoiceAmount>,
359    /// BT-112 invoice total amount with VAT.
360    pub with_tax: Option<InvoiceAmount>,
361    /// BT-113 paid amount.
362    pub paid: Option<InvoiceAmount>,
363    /// BT-114 rounding amount.
364    pub rounding: Option<InvoiceAmount>,
365    /// BT-115 amount due for payment. Missing PayableAmount is `None`, not 0 (BR-15).
366    pub payable: Option<InvoiceAmount>,
367}
368
369/// BG-24 additional supporting document.
370#[derive(Debug, Clone, PartialEq, Eq)]
371pub struct SupportingDocument {
372    /// BT-122 supporting document reference.
373    pub id: DocumentReference,
374    /// BT-123 supporting document description.
375    pub description: Option<String>,
376    /// BT-124 external document location.
377    pub uri: Option<String>,
378    /// BT-125 attached document.
379    pub attachment: Option<Attachment>,
380}
381
382/// Semantic invoice. Fields stay public through 0.1–0.2; Table 2 terms will
383/// still be added. Construct with [`Invoice::blank`], then set fields. Do not
384/// match on the struct layout.
385#[derive(Debug, Clone, PartialEq, Eq)]
386pub struct Invoice {
387    pub profile: Profile,
388    /// BT-24 specification identifier.
389    pub specification_id: Option<String>,
390    /// Syntax root analogue. Not derived from BT-3.
391    pub kind: DocumentKind,
392    /// BT-1 invoice number.
393    pub number: String,
394    /// BT-5 invoice currency.
395    pub currency: String,
396    /// BT-2 issue date.
397    pub issue_date: Option<Date>,
398    /// BT-3 invoice type code.
399    pub type_code: Option<Code>,
400    /// BT-6 VAT accounting currency.
401    pub tax_currency: Option<Code>,
402    /// BT-9 payment due date.
403    pub due_date: Option<Date>,
404    /// BT-7 / BT-8. BR-CO-03 when both the date and the code rules apply.
405    pub tax_point_date: Option<Date>,
406    /// BT-8 VAT point date code.
407    pub tax_point_code: Option<Code>,
408    /// BT-23 business process type.
409    pub business_process: Option<String>,
410    /// BT-10 buyer reference. Do not overload with BT-13.
411    pub buyer_reference: Option<DocumentReference>,
412    /// BT-11 project reference.
413    pub project: Option<DocumentReference>,
414    /// BT-12 contract reference.
415    pub contract: Option<DocumentReference>,
416    /// BT-13 purchase order reference (Peppol R003 with BT-10).
417    pub purchase_order: Option<DocumentReference>,
418    /// BT-14 sales order reference.
419    pub sales_order: Option<DocumentReference>,
420    /// BT-15 receiving advice reference.
421    pub receiving_advice: Option<DocumentReference>,
422    /// BT-16 despatch advice reference.
423    pub despatch: Option<DocumentReference>,
424    /// BT-17 tender or lot reference.
425    pub tender: Option<DocumentReference>,
426    /// BT-18 invoiced object identifier (not a BG-24 supporting document).
427    pub invoiced_object: Option<Identifier>,
428    /// BT-19 buyer accounting reference.
429    pub buyer_accounting: Option<String>,
430    /// BT-20 payment terms.
431    pub payment_terms: Option<String>,
432    /// BG-1 notes (BT-21/22).
433    pub notes: Vec<InvoiceNote>,
434    /// BG-3 preceding invoice reference.
435    pub preceding: Vec<PrecedingInvoice>,
436    /// BG-4 seller.
437    pub seller: Party,
438    /// BG-7 buyer.
439    pub buyer: Party,
440    /// BG-10 payee.
441    pub payee: Option<Payee>,
442    /// BG-11 seller tax representative.
443    pub tax_representative: Option<TaxRepresentative>,
444    /// BG-13 delivery.
445    pub delivery: Option<Delivery>,
446    /// BG-14 invoicing period.
447    pub period: Option<Period>,
448    /// BG-16 payment instructions.
449    pub payment: Option<PaymentInstructions>,
450    /// BG-20 document level allowances.
451    pub document_allowances: Vec<AllowanceCharge>,
452    /// BG-21 document level charges.
453    pub document_charges: Vec<AllowanceCharge>,
454    /// BG-23 VAT/GST/SST breakdown.
455    pub tax_breakdown: Vec<TaxBreakdown>,
456    /// BG-22 document totals.
457    pub totals: Option<DocumentTotals>,
458    /// BG-24 additional supporting documents.
459    pub supporting_documents: Vec<SupportingDocument>,
460    /// BG-25 invoice lines.
461    pub lines: Vec<Line>,
462}
463
464impl Invoice {
465    pub fn blank(
466        profile: Profile,
467        number: impl Into<String>,
468        currency: impl Into<String>,
469        seller: Party,
470        buyer: Party,
471    ) -> Self {
472        Self {
473            profile,
474            specification_id: Some(profile.specification_id().into()),
475            kind: DocumentKind::Invoice,
476            number: number.into(),
477            currency: currency.into(),
478            issue_date: None,
479            type_code: None,
480            tax_currency: None,
481            due_date: None,
482            tax_point_date: None,
483            tax_point_code: None,
484            business_process: None,
485            buyer_reference: None,
486            project: None,
487            contract: None,
488            purchase_order: None,
489            sales_order: None,
490            receiving_advice: None,
491            despatch: None,
492            tender: None,
493            invoiced_object: None,
494            buyer_accounting: None,
495            payment_terms: None,
496            notes: vec![],
497            preceding: vec![],
498            seller,
499            buyer,
500            payee: None,
501            tax_representative: None,
502            delivery: None,
503            period: None,
504            payment: None,
505            document_allowances: vec![],
506            document_charges: vec![],
507            tax_breakdown: vec![],
508            totals: None,
509            supporting_documents: vec![],
510            lines: vec![],
511        }
512    }
513
514    /// BT-115 from [`DocumentTotals`]. Absent BG-22 or absent PayableAmount is not 0.00 (BR-15).
515    pub fn payable(&self) -> Option<Amount> {
516        self.totals.as_ref().and_then(|t| t.payable)
517    }
518
519    /// BT-110 from [`DocumentTotals`]. Absent totals is not 0.00.
520    pub fn tax_total(&self) -> Option<Amount> {
521        self.totals.as_ref().and_then(|t| t.tax_total)
522    }
523
524    /// BT-24 and BT-23 come from the proved profile, not leftover fields on Invoice.
525    ///
526    /// EN 16931 has no required ProfileID: BT-23 is left as-is (the UBL writer omits it).
527    pub fn stamp_profile(&mut self, profile: Profile) {
528        self.profile = profile;
529        if profile == Profile::Unknown {
530            return;
531        }
532        self.specification_id = Some(profile.specification_id().into());
533        if let Some(bt23) = profile.process_id() {
534            self.business_process = Some(bt23.into());
535        }
536    }
537
538    pub fn line_net_sum(&self) -> Option<Amount> {
539        self.lines
540            .iter()
541            .try_fold(Amount::ZERO, |acc, line| acc.checked_add(line.net))
542    }
543
544    /// Credit note: new number/date, BG-3 to the original, amounts **not** negated.
545    pub fn to_credit_note(&self, new_number: impl Into<String>, new_issue_date: Date) -> Self {
546        let mut next = self.clone();
547        next.kind = DocumentKind::CreditNote;
548        next.type_code = Some(Code::new("381"));
549        next.preceding = vec![PrecedingInvoice {
550            reference: DocumentReference::new(self.number.clone()),
551            issue_date: self.issue_date,
552        }];
553        next.number = new_number.into();
554        next.issue_date = Some(new_issue_date);
555        next
556    }
557}
558
559#[cfg(test)]
560mod tests {
561    use super::*;
562    use crate::identifier::Identifier;
563    use crate::tax::TaxCategory;
564    use rust_decimal::Decimal;
565
566    #[test]
567    fn credit_note_does_not_negate() {
568        let mut inv = Invoice::blank(
569            Profile::En16931,
570            "INV-1",
571            "EUR",
572            Party::new("S", "DE"),
573            Party::new("B", "FR"),
574        );
575        inv.issue_date = Date::parse("2026-01-15").ok();
576        inv.type_code = Some(Code::new("380"));
577        inv.lines = vec![Line::new(
578            "1",
579            "A",
580            Amount::parse("100.00").unwrap(),
581            TaxCategory::vat("S", Decimal::from(19)),
582        )];
583        let _ = crate::reconcile::reconcile(&mut inv);
584        let cn = inv.to_credit_note("CN-1", Date::parse("2026-01-16").unwrap());
585        assert_eq!(cn.kind, DocumentKind::CreditNote);
586        assert_eq!(cn.payable(), inv.payable());
587        assert_eq!(cn.preceding[0].reference.as_str(), "INV-1");
588        assert_eq!(cn.type_code.as_ref().map(Code::as_str), Some("381"));
589    }
590
591    #[test]
592    fn table2_refs_and_line_groups_exist() {
593        let mut inv = Invoice::blank(
594            Profile::En16931,
595            "INV-1",
596            "EUR",
597            Party::new("S", "DE"),
598            Party::new("B", "FR"),
599        );
600        inv.tax_point_date = Date::parse("2026-01-10").ok();
601        inv.tax_point_code = Some(Code::new("3"));
602        inv.purchase_order = Some(DocumentReference::new("PO-9"));
603        inv.invoiced_object = Some(Identifier::new("OBJ-1"));
604        inv.lines.push({
605            let mut line = Line::new(
606                "1",
607                "A",
608                Amount::parse("90.00").unwrap(),
609                TaxCategory::vat("S", Decimal::from(19)),
610            );
611            line.period = Some(Period {
612                start: Date::parse("2026-01-01").ok(),
613                end: Date::parse("2026-01-31").ok(),
614            });
615            line.allowances.push(LineAllowanceCharge {
616                amount: Amount::parse("10.00").unwrap(),
617                base: None,
618                percent: None,
619                reason: Some("discount".into()),
620                reason_code: None,
621            });
622            line.standard_id = Some(Identifier::schemed("01234567890128", "0160"));
623            line.origin_country = Some(Code::new("DE"));
624            line
625        });
626        assert_eq!(inv.purchase_order.as_ref().unwrap().as_str(), "PO-9");
627        assert_eq!(inv.lines[0].allowances.len(), 1);
628        assert_eq!(inv.lines[0].origin_country.as_ref().unwrap().as_str(), "DE");
629        assert!(inv.invoiced_object.is_some());
630    }
631}