[FIX] account_facturx: make factur-x xml compliant

The xml we generate for factur-x is no longer compliant with all
the latest standards. With these changes, we are providing the tools
to make it work once again and make sure it is validated by the
factur-x and zugferd validators in all aspect (PDFA/3, XMP, XML)
It should also be valid to be sent to Chorus pro if applicable.

This adds a mapping to the UNECE unit of measure codes for uom, that
are required for factur-x.

It will also calculate a category for each taxes as such:
    - If the tax is an export from EU to outside EU, G.
    - If the supplier and customer are both in EU but different country,
        K
    - If both are in EU, in the same country but the tax amount is 0, E
    - Otherwise, it will be S.
See https://unece.org/fileadmin/DAM/trade/untdid/d16b/tred/tred5305.htm

opw-2714544

closes odoo/odoo#90105

X-original-commit: e1bfb363bccd7b84f660d1e6c5b1e4d4fe02d398
Signed-off-by: Laurent Smet <las@odoo.com>
This commit is contained in:
Nicolas (vin)
2022-05-04 13:53:42 +02:00
parent 075152d1a0
commit 1ff279f11c
9 changed files with 232 additions and 119 deletions
+1
View File
@@ -8,3 +8,4 @@ from . import account_payment
from . import ir_actions_report
from . import mail_template
from . import ir_attachment
from . import uom
@@ -159,6 +159,7 @@ class AccountMove(models.Model):
tax_details['base_amount_currency'] += tax_values['base_amount_currency']
tax_details['tax_amount'] += tax_values['tax_amount']
tax_details['tax_amount_currency'] += tax_values['tax_amount_currency']
tax_details['exemption_reason'] = tax_values['tax_id'].name
tax_details['group_tax_details'].append(tax_values)
def _prepare_edi_tax_details(self, filter_to_apply=None, filter_invl_to_apply=None, grouping_key_generator=None, compute_mode='tax_details'):
@@ -662,7 +663,9 @@ class AccountMoveLine(models.Model):
'price_subtotal_unit': self.currency_id.round(self.price_subtotal / self.quantity) if self.quantity else 0.0,
'price_total_unit': self.currency_id.round(self.price_total / self.quantity) if self.quantity else 0.0,
'price_discount': gross_price_subtotal - self.price_subtotal,
'price_discount_unit': (gross_price_subtotal - self.price_subtotal) / self.quantity if self.quantity else 0.0,
'gross_price_total_unit': self.currency_id.round(gross_price_subtotal / self.quantity) if self.quantity else 0.0,
'unece_uom_code': self.product_id.product_tmpl_id.uom_id._get_unece_code(),
}
return res
+44
View File
@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
from odoo import models
class UoM(models.Model):
_inherit = 'uom.uom'
def _get_unece_code(self):
""" Returns the UNECE code used for international trading for corresponding to the UoM as per
https://unece.org/fileadmin/DAM/cefact/recommendations/bkup_htm/add2d.htm.
"""
if len(self) != 1:
return 'C62'
xml_id = self.env['ir.model.data'].sudo().search([
('model', '=', 'uom.uom'),
('res_id', '=', self.id),
]).name
mapping = {
'product_uom_unit': 'C62',
'product_uom_dozen': 'DZN',
'product_uom_kgm': 'KGM',
'product_uom_gram': 'GRM',
'product_uom_day': 'DAY',
'product_uom_hour': 'HUR',
'product_uom_ton': 'TNE',
'product_uom_meter': 'MTR',
'product_uom_km': 'KTM',
'product_uom_cm': 'CMT',
'product_uom_litre': 'LTR',
'product_uom_lb': 'LBR',
'product_uom_oz': 'ONZ',
'product_uom_inch': 'INH',
'product_uom_foot': 'FOT',
'product_uom_mile': 'SMI',
'product_uom_floz': 'OZA',
'product_uom_qt': 'QT',
'product_uom_gal': 'GLL',
'product_uom_cubic_meter': 'MTQ',
'product_uom_cubic_inch': 'INQ',
'product_uom_cubic_foot': 'FTQ',
}
return mapping.get(xml_id, 'C62')
@@ -8,4 +8,10 @@
</record>
</data>
</odoo>
<data noupdate="1">
<record model="ir.config_parameter" id="ir_config_parameter_use_pdfa">
<field name="key">edi.use_pdfa</field>
<field name="value">false</field>
</record>
</data>
</odoo>
@@ -29,50 +29,40 @@
<!-- Line information, with discount and unit price separate -->
<ram:GrossPriceProductTradePrice>
<ram:ChargeAmount
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(line.price_unit, record.currency_id)"/>
<!-- Discount. -->
<ram:AppliedTradeAllowanceCharge t-if="line.discount">
<ram:ChargeIndicator>
<udt:Indicator t-esc="'false' if line.discount == 100 else 'true'"/>
<udt:Indicator>false</udt:Indicator>
</ram:ChargeIndicator>
<ram:CalculationPercent t-esc="line.discount"/>
<ram:ActualAmount t-esc="format_monetary(line_vals['price_discount_unit'], record.currency_id)"/>
</ram:AppliedTradeAllowanceCharge>
</ram:GrossPriceProductTradePrice>
<!-- Line unit price, with discount applied -->
<ram:NetPriceProductTradePrice>
<t t-if="line.quantity > 0">
<ram:ChargeAmount
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(line.price_subtotal/line.quantity, record.currency_id)"/>
</t>
<t t-else="">
<ram:ChargeAmount
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(0.0, record.currency_id)"/>
</t>
<ram:ChargeAmount
t-esc="format_monetary(line_vals['price_subtotal_unit'], record.currency_id)"/>
</ram:NetPriceProductTradePrice>
</ram:SpecifiedLineTradeAgreement>
<!-- Quantity. -->
<ram:SpecifiedLineTradeDelivery>
<ram:BilledQuantity
t-esc="line.quantity"/>
<ram:BilledQuantity t-att-unitCode="line_vals['unece_uom_code']" t-esc="line.quantity"/>
</ram:SpecifiedLineTradeDelivery>
<ram:SpecifiedLineTradeSettlement>
<t t-foreach="tax_details['invoice_line_tax_details'][line]['tax_details'].values()"
t-as="tax_detail_vals">
<t t-set="tax" t-value="tax_detail_vals['tax']"/>
<ram:ApplicableTradeTax t-if="tax.amount_type == 'percent'">
<ram:RateApplicablePercent t-esc="tax.amount"/>
<ram:ApplicableTradeTax t-if="tax_detail_vals['amount_type'] == 'percent'">
<ram:TypeCode>VAT</ram:TypeCode>
<ram:CategoryCode t-esc="tax_detail_vals['unece_tax_category_code']"/>
<ram:RateApplicablePercent t-esc="tax_detail_vals['amount']"/>
</ram:ApplicableTradeTax>
</t>
<!-- Subtotal. -->
<ram:SpecifiedTradeSettlementLineMonetarySummation>
<ram:LineTotalAmount
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(line.price_subtotal, record.currency_id)"/>
</ram:SpecifiedTradeSettlementLineMonetarySummation>
</ram:SpecifiedLineTradeSettlement>
@@ -87,15 +77,10 @@
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
<!-- Contact. -->
<ram:Name t-if="partner.name" t-esc="partner.name"/>
<ram:DefinedTradeContact>
<ram:PersonName t-esc="partner.name"/>
<ram:TelephoneUniversalCommunication t-if="partner.phone or partner.mobile">
<ram:CompleteNumber t-esc="partner.phone or partner.mobile"/>
</ram:TelephoneUniversalCommunication>
<ram:EmailURIUniversalCommunication t-if="partner.email">
<ram:URIID schemeID='SMTP' t-esc="partner.email"/>
</ram:EmailURIUniversalCommunication>
</ram:DefinedTradeContact>
<ram:SpecifiedLegalOrganization t-if="SpecifiedLegalOrganization">
<ram:ID t-esc="SpecifiedLegalOrganization"/>
</ram:SpecifiedLegalOrganization>
<!-- Address. -->
<ram:PostalTradeAddress>
@@ -149,6 +134,7 @@
<!-- Address. -->
<t t-call="account_edi_facturx.account_invoice_partner_facturx_export">
<t t-set="partner" t-value="record.company_id.partner_id"/>
<t t-set="SpecifiedLegalOrganization" t-value="seller_specified_legal_organization"/>
</t>
<!-- VAT. -->
@@ -162,6 +148,7 @@
<!-- Address. -->
<t t-call="account_edi_facturx.account_invoice_partner_facturx_export">
<t t-set="partner" t-value="record.commercial_partner_id"/>
<t t-set="SpecifiedLegalOrganization" t-value="buyer_specified_legal_organization"/>
</t>
<!-- VAT. -->
@@ -172,25 +159,27 @@
<!-- Reference. -->
<ram:BuyerOrderReferencedDocument>
<ram:IssuerAssignedID t-esc="'%s: %s' % (record.name, record.payment_reference) if record.payment_reference else record.name"/>
<ram:IssuerAssignedID t-esc="record.payment_reference if record.payment_reference else record.name"/>
</ram:BuyerOrderReferencedDocument>
</ram:ApplicableHeaderTradeAgreement>
<!-- Delivery. Don't make a dependency with sale only for one field. -->
<ram:ApplicableHeaderTradeDelivery>
<ram:ShipToTradeParty
t-if="record.partner_shipping_id">
<ram:ShipToTradeParty>
<t t-call="account_edi_facturx.account_invoice_partner_facturx_export">
<t t-set="partner" t-value="record.partner_shipping_id"/>
<t t-if="record.partner_shipping_id" t-set="partner" t-value="record.partner_shipping_id"/>
<t t-else="" t-set="partner" t-value="record.commercial_partner_id"/>
</t>
</ram:ShipToTradeParty>
</ram:ApplicableHeaderTradeDelivery>
<!-- Taxes. -->
<ram:ApplicableHeaderTradeSettlement>
<ram:InvoiceCurrencyCode t-esc="record.currency_id.name"/>
<!-- Bank account. -->
<ram:SpecifiedTradeSettlementPaymentMeans t-if="record.partner_bank_id.acc_type == 'iban'">
<ram:TypeCode>42</ram:TypeCode>
<ram:PayeePartyCreditorFinancialAccount>
<ram:IBANID t-esc="record.partner_bank_id.sanitized_acc_number"/>
</ram:PayeePartyCreditorFinancialAccount>
@@ -198,20 +187,28 @@
<!-- Tax Summary. -->
<t t-foreach="tax_details['tax_details'].values()" t-as="tax_detail_vals">
<t t-set="tax" t-value="tax_detail_vals['tax']"/>
<ram:ApplicableTradeTax>
<ram:CalculatedAmount
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(balance_multiplicator * tax_detail_vals['tax_amount_currency'], record.currency_id)"/>
<ram:TypeCode>VAT</ram:TypeCode>
<ram:ExemptionReason t-if="tax_detail_vals['unece_tax_category_code'] == 'E'" t-esc="tax_detail_vals['exemption_reason']"/>
<ram:ExemptionReason t-if="tax_detail_vals['unece_tax_category_code'] == 'G'" t-esc="'Export outside the EU'"/>
<ram:ExemptionReason t-if="tax_detail_vals['unece_tax_category_code'] == 'K'" t-esc="'Intra-community supply'"/>
<ram:BasisAmount
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(balance_multiplicator * tax_detail_vals['base_amount_currency'], record.currency_id)"/>
<ram:CategoryCode t-esc="tax_detail_vals['unece_tax_category_code']"/>
<ram:RateApplicablePercent
t-if="tax.amount_type == 'percent'"
t-esc="tax.amount"/>
t-if="tax_detail_vals['amount_type'] == 'percent'"
t-esc="tax_detail_vals['amount']"/>
</ram:ApplicableTradeTax>
</t>
<ram:BillingSpecifiedPeriod>
<ram:StartDateTime>
<udt:DateTimeString format="102" t-esc="format_date(record.invoice_date)"/>
</ram:StartDateTime>
</ram:BillingSpecifiedPeriod>
<!-- Payment Term. -->
<ram:SpecifiedTradePaymentTerms>
<ram:Description t-if="record.invoice_payment_term_id" t-esc="record.invoice_payment_term_id.name"/>
@@ -223,22 +220,17 @@
<!-- Summary. -->
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
<ram:LineTotalAmount
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(record.amount_untaxed, record.currency_id)"/>
<ram:TaxBasisTotalAmount
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(record.amount_untaxed, record.currency_id)"/>
<ram:TaxTotalAmount
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(record.amount_tax, record.currency_id)"/>
<ram:GrandTotalAmount
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(record.amount_total, record.currency_id)"/>
<ram:TotalPrepaidAmount
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(record.amount_total - record.amount_residual, record.currency_id)"/>
<ram:DuePayableAmount
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(record.amount_residual, record.currency_id)"/>
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
</ram:ApplicableHeaderTradeSettlement>
@@ -1,4 +1,5 @@
# -*- encoding: utf-8 -*-
from . import account_edi_format
from . import account_tax
from . import mail_template
@@ -36,7 +36,7 @@ class AccountEdiFormat(models.Model):
if not edi_document.attachment_id:
return
pdf_writer.embed_odoo_attachment(edi_document.attachment_id, subtype='application/xml')
pdf_writer.embed_odoo_attachment(edi_document.attachment_id, subtype='text/xml')
if not pdf_writer.is_pdfa and str2bool(self.env['ir.config_parameter'].sudo().get_param('edi.use_pdfa', 'False')):
try:
pdf_writer.convert_to_pdfa()
@@ -59,16 +59,29 @@ class AccountEdiFormat(models.Model):
def format_monetary(number, currency):
# Format the monetary values to avoid trailing decimals (e.g. 90.85000000000001).
if currency.is_zero(number): # Ensure that we never return -0.0
number = 0.0
return float_repr(number, currency.decimal_places)
self.ensure_one()
# Create file content.
seller_siret = 'siret' in invoice.company_id._fields and invoice.company_id.siret or invoice.company_id.company_registry
buyer_siret = 'siret' in invoice.commercial_partner_id._fields and invoice.commercial_partner_id.siret
tax_detail_vals = invoice._prepare_edi_tax_details(
grouping_key_generator=lambda tax_values: {
'unece_tax_category_code': tax_values['tax_id']._get_unece_category_code(invoice.commercial_partner_id, invoice.company_id),
'amount': tax_values['tax_id'].amount,
'amount_type': tax_values['tax_id'].amount_type, # We need to check the type
}
)
template_values = {
**invoice._prepare_edi_vals_to_export(),
'tax_details': invoice._prepare_edi_tax_details(),
'tax_details': tax_detail_vals,
'format_date': format_date,
'format_monetary': format_monetary,
'is_html_empty': is_html_empty,
'seller_specified_legal_organization': seller_siret,
'buyer_specified_legal_organization': buyer_siret,
}
xml_content = markupsafe.Markup("<?xml version='1.0' encoding='UTF-8'?>")
@@ -172,17 +185,22 @@ class AccountEdiFormat(models.Model):
if elements:
invoice_form.narration = elements[0].text
# Total amount.
elements = tree.xpath('//ram:GrandTotalAmount', namespaces=tree.nsmap)
# Get currency string for new invoices, or invoices coming from outside
elements = tree.xpath('//ram:InvoiceCurrencyCode', namespaces=tree.nsmap)
if elements:
currency_str = elements[0].text
# Fallback for old invoices from odoo where the InvoiceCurrencyCode was not present
else:
elements = tree.xpath('//ram:TaxTotalAmount', namespaces=tree.nsmap)
if elements:
currency_str = elements[0].attrib.get('currencyID', None)
# Currency.
currency_str = elements[0].attrib.get('currencyID', None)
if currency_str:
invoice_form.currency_id = self._retrieve_currency(currency_str)
# Currency.
if currency_str:
invoice_form.currency_id = self._retrieve_currency(currency_str)
# Store xml total amount.
amount_total_import = total_amount * refund_sign
# Store xml total amount.
amount_total_import = total_amount * refund_sign
# Date.
elements = tree.xpath('//rsm:ExchangedDocument/ram:IssueDateTime/udt:DateTimeString', namespaces=tree.nsmap)
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from odoo import models
class AccountTax(models.Model):
_inherit = 'account.tax'
def _get_unece_category_code(self, customer, supplier):
""" By default, this method will try to compute the tax category (used by EDI for example) based on the amount
and the tax repartition lines. This is hack-ish~ but a valid solution to get a default value in stable.
See https://unece.org/fileadmin/DAM/trade/untdid/d16b/tred/tred5305.htm for the codes.
"""
self.ensure_one()
# Defaulting to standard tax.
category = 'S'
if self.type_tax_use == 'sale':
eu_countries = self.env.ref('base.europe').country_ids
if supplier.country_id in eu_countries and customer.country_id not in eu_countries:
category = 'G'
else:
if customer.country_id != supplier.country_id \
and customer.country_id in eu_countries \
and supplier.country_id in eu_countries:
category = 'K'
# Taxes with a Zero amount will get the E code. (Exempt)
elif self.amount == 0:
category = 'E'
return category
@@ -82,76 +82,77 @@ class TestAccountEdiFacturx(AccountEdiTestCommon):
</SpecifiedTradeProduct>
<SpecifiedLineTradeAgreement>
<GrossPriceProductTradePrice>
<ChargeAmount currencyID="Gol">275.000</ChargeAmount>
<ChargeAmount>275.000</ChargeAmount>
<AppliedTradeAllowanceCharge>
<ChargeIndicator>
<Indicator>true</Indicator>
<Indicator>false</Indicator>
</ChargeIndicator>
<CalculationPercent>20.0</CalculationPercent>
<ActualAmount>55.000</ActualAmount>
</AppliedTradeAllowanceCharge>
</GrossPriceProductTradePrice>
<NetPriceProductTradePrice>
<ChargeAmount currencyID="Gol">220.000</ChargeAmount>
<ChargeAmount>220.000</ChargeAmount>
</NetPriceProductTradePrice>
</SpecifiedLineTradeAgreement>
<SpecifiedLineTradeDelivery>
<BilledQuantity>5.0</BilledQuantity>
<BilledQuantity unitCode="C62">5.0</BilledQuantity>
</SpecifiedLineTradeDelivery>
<SpecifiedLineTradeSettlement>
<ApplicableTradeTax>
<TypeCode>VAT</TypeCode>
<CategoryCode>S</CategoryCode>
<RateApplicablePercent>20.0</RateApplicablePercent>
</ApplicableTradeTax>
<SpecifiedTradeSettlementLineMonetarySummation>
<LineTotalAmount currencyID="Gol">1100.000</LineTotalAmount>
<LineTotalAmount>1100.000</LineTotalAmount>
</SpecifiedTradeSettlementLineMonetarySummation>
</SpecifiedLineTradeSettlement>
</IncludedSupplyChainTradeLineItem>
<ApplicableHeaderTradeAgreement>
<SellerTradeParty>
<Name>company_1_data</Name>
<DefinedTradeContact>
<PersonName>company_1_data</PersonName>
</DefinedTradeContact>
<PostalTradeAddress/>
</SellerTradeParty>
<BuyerTradeParty>
<Name>partner_b</Name>
<DefinedTradeContact>
<PersonName>partner_b</PersonName>
</DefinedTradeContact>
<PostalTradeAddress/>
</BuyerTradeParty>
<BuyerOrderReferencedDocument>
<IssuerAssignedID>INV/2017/00001: INV/2017/00001</IssuerAssignedID>
<IssuerAssignedID>INV/2017/00001</IssuerAssignedID>
</BuyerOrderReferencedDocument>
</ApplicableHeaderTradeAgreement>
<ApplicableHeaderTradeDelivery>
<ShipToTradeParty>
<Name>partner_b</Name>
<DefinedTradeContact>
<PersonName>partner_b</PersonName>
</DefinedTradeContact>
<PostalTradeAddress/>
</ShipToTradeParty>
</ApplicableHeaderTradeDelivery>
<ApplicableHeaderTradeSettlement>
<InvoiceCurrencyCode>Gol</InvoiceCurrencyCode>
<ApplicableTradeTax>
<CalculatedAmount currencyID="Gol">220.000</CalculatedAmount>
<BasisAmount currencyID="Gol">1100.000</BasisAmount>
<CalculatedAmount>220.000</CalculatedAmount>
<TypeCode>VAT</TypeCode>
<BasisAmount>1100.000</BasisAmount>
<CategoryCode>S</CategoryCode>
<RateApplicablePercent>20.0</RateApplicablePercent>
</ApplicableTradeTax>
<BillingSpecifiedPeriod>
<StartDateTime>
<DateTimeString format="102">20170101</DateTimeString>
</StartDateTime>
</BillingSpecifiedPeriod>
<SpecifiedTradePaymentTerms>
<DueDateDateTime>
<DateTimeString format="102">20170101</DateTimeString>
</DueDateDateTime>
</SpecifiedTradePaymentTerms>
<SpecifiedTradeSettlementHeaderMonetarySummation>
<LineTotalAmount currencyID="Gol">1100.000</LineTotalAmount>
<TaxBasisTotalAmount currencyID="Gol">1100.000</TaxBasisTotalAmount>
<LineTotalAmount>1100.000</LineTotalAmount>
<TaxBasisTotalAmount>1100.000</TaxBasisTotalAmount>
<TaxTotalAmount currencyID="Gol">220.000</TaxTotalAmount>
<GrandTotalAmount currencyID="Gol">1320.000</GrandTotalAmount>
<TotalPrepaidAmount currencyID="Gol">0.000</TotalPrepaidAmount>
<DuePayableAmount currencyID="Gol">1320.000</DuePayableAmount>
<GrandTotalAmount>1320.000</GrandTotalAmount>
<TotalPrepaidAmount>0.000</TotalPrepaidAmount>
<DuePayableAmount>1320.000</DuePayableAmount>
</SpecifiedTradeSettlementHeaderMonetarySummation>
</ApplicableHeaderTradeSettlement>
</SupplyChainTradeTransaction>
@@ -174,49 +175,66 @@ class TestAccountEdiFacturx(AccountEdiTestCommon):
})
applied_xpath = '''
<xpath expr="//NetPriceProductTradePrice/ChargeAmount" position="replace">
<ChargeAmount currencyID="Gol">200.000</ChargeAmount>
</xpath>
<xpath expr="//SpecifiedLineTradeSettlement" position="replace">
<SpecifiedLineTradeSettlement>
<ApplicableTradeTax>
<RateApplicablePercent>10.0</RateApplicablePercent>
</ApplicableTradeTax>
<ApplicableTradeTax>
<RateApplicablePercent>20.0</RateApplicablePercent>
</ApplicableTradeTax>
<SpecifiedTradeSettlementLineMonetarySummation>
<LineTotalAmount currencyID="Gol">1000.000</LineTotalAmount>
</SpecifiedTradeSettlementLineMonetarySummation>
</SpecifiedLineTradeSettlement>
</xpath>
<xpath expr="//ApplicableHeaderTradeSettlement" position="replace">
<ApplicableHeaderTradeSettlement>
<ApplicableTradeTax>
<CalculatedAmount currencyID="Gol">100.000</CalculatedAmount>
<BasisAmount currencyID="Gol">1000.000</BasisAmount>
<RateApplicablePercent>10.0</RateApplicablePercent>
</ApplicableTradeTax>
<ApplicableTradeTax>
<CalculatedAmount currencyID="Gol">220.000</CalculatedAmount>
<BasisAmount currencyID="Gol">1100.000</BasisAmount>
<RateApplicablePercent>20.0</RateApplicablePercent>
</ApplicableTradeTax>
<SpecifiedTradePaymentTerms>
<DueDateDateTime>
<DateTimeString format="102">20170101</DateTimeString>
</DueDateDateTime>
</SpecifiedTradePaymentTerms>
<SpecifiedTradeSettlementHeaderMonetarySummation>
<LineTotalAmount currencyID="Gol">1000.000</LineTotalAmount>
<TaxBasisTotalAmount currencyID="Gol">1000.000</TaxBasisTotalAmount>
<TaxTotalAmount currencyID="Gol">320.000</TaxTotalAmount>
<GrandTotalAmount currencyID="Gol">1320.000</GrandTotalAmount>
<TotalPrepaidAmount currencyID="Gol">0.000</TotalPrepaidAmount>
<DuePayableAmount currencyID="Gol">1320.000</DuePayableAmount>
</SpecifiedTradeSettlementHeaderMonetarySummation>
</ApplicableHeaderTradeSettlement>
</xpath>
<xpath expr="//AppliedTradeAllowanceCharge/ActualAmount" position="replace">
<ActualAmount>50.000</ActualAmount>
</xpath>
<xpath expr="//NetPriceProductTradePrice/ChargeAmount" position="replace">
<ChargeAmount>200.000</ChargeAmount>
</xpath>
<xpath expr="//SpecifiedLineTradeSettlement" position="replace">
<SpecifiedLineTradeSettlement>
<ApplicableTradeTax>
<TypeCode>VAT</TypeCode>
<CategoryCode>S</CategoryCode>
<RateApplicablePercent>10.0</RateApplicablePercent>
</ApplicableTradeTax>
<ApplicableTradeTax>
<TypeCode>VAT</TypeCode>
<CategoryCode>S</CategoryCode>
<RateApplicablePercent>20.0</RateApplicablePercent>
</ApplicableTradeTax>
<SpecifiedTradeSettlementLineMonetarySummation>
<LineTotalAmount>1000.000</LineTotalAmount>
</SpecifiedTradeSettlementLineMonetarySummation>
</SpecifiedLineTradeSettlement>
</xpath>
<xpath expr="//ApplicableHeaderTradeSettlement" position="replace">
<ApplicableHeaderTradeSettlement>
<InvoiceCurrencyCode>Gol</InvoiceCurrencyCode>
<ApplicableTradeTax>
<CalculatedAmount>100.000</CalculatedAmount>
<TypeCode>VAT</TypeCode>
<BasisAmount>1000.000</BasisAmount>
<CategoryCode>S</CategoryCode>
<RateApplicablePercent>10.0</RateApplicablePercent>
</ApplicableTradeTax>
<ApplicableTradeTax>
<CalculatedAmount>220.000</CalculatedAmount>
<TypeCode>VAT</TypeCode>
<BasisAmount>1100.000</BasisAmount>
<CategoryCode>S</CategoryCode>
<RateApplicablePercent>20.0</RateApplicablePercent>
</ApplicableTradeTax>
<BillingSpecifiedPeriod>
<StartDateTime>
<DateTimeString format="102">20170101</DateTimeString>
</StartDateTime>
</BillingSpecifiedPeriod>
<SpecifiedTradePaymentTerms>
<DueDateDateTime>
<DateTimeString format="102">20170101</DateTimeString>
</DueDateDateTime>
</SpecifiedTradePaymentTerms>
<SpecifiedTradeSettlementHeaderMonetarySummation>
<LineTotalAmount>1000.000</LineTotalAmount>
<TaxBasisTotalAmount>1000.000</TaxBasisTotalAmount>
<TaxTotalAmount currencyID="Gol">320.000</TaxTotalAmount>
<GrandTotalAmount>1320.000</GrandTotalAmount>
<TotalPrepaidAmount>0.000</TotalPrepaidAmount>
<DuePayableAmount>1320.000</DuePayableAmount>
</SpecifiedTradeSettlementHeaderMonetarySummation>
</ApplicableHeaderTradeSettlement>
</xpath>
'''
self.assert_generated_file_equal(self.invoice, self.expected_invoice_facturx_values, applied_xpath)