[FIX] account_edi_ubl_cii: tax exempt export

Previously, exporting BIS3 when there is a 0% tax in the invoice will
results in the XML showing warning of UBL-CR-601. But if we do not
include any TaxExemptionReason reason at all, a fatal error BR-E-10 will
pop up.

Here is the details of those 2 rules:

```xml
(with context: /*/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory
[normalize-space(cbc:ID) = 'E'][cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT'])
      <assert id="BR-E-10" flag="fatal" test="exists(cbc:TaxExemptionReason) or
        exists(cbc:TaxExemptionReasonCode)">
          [BR-E-10]-A VAT breakdown (BG-23) with VAT Category code (BT-118)
          "Exempt from VAT" shall have a VAT exemption reason code (BT-121)
          or a VAT exemption reason text (BT-120). </assert>

and

(no context)
<assert id="UBL-CR-601" flag="warning" test="
  not((cac:InvoiceLine|cac:CreditNoteLine)/cac:Item/
       cac:ClassifiedTaxCategory/cbc:TaxExemptionReason)">
          [UBL-CR-601]-A UBL invoice should not include the InvoiceLine
          Item ClassifiedTaxCategory TaxExemptionReason </assert>
```

Based on these rules, we can conclude that:
- TaxExemptionReason must not appear in InvoiceLine/Item/ClassifiedTaxCategory
- TaxExemptionReason must appear (when some line in invoice has exempt tax)
  in TaxTotal/TaxSubtotal/TaxCategory

Previously, the TaxExemptionReason will appear on both places.
This commit fixes that and adds a test to ensure that when a 0% tax is
present, TaxExemptionReason only appear in TaxTotal/TaxSubtotal/TaxCategory

closes odoo/odoo#155022

Task-id: 3703206
X-original-commit: 6c2f098f46c5d90d2384c596f4cef8fa874adbd5
Signed-off-by: Laurent Smet (las) <las@odoo.com>
Signed-off-by: Yosua Nicolaus (yoni) <yoni@odoo.com>
This commit is contained in:
Yosua Nicolaus
2024-02-22 16:24:02 +00:00
parent 871b2ecaea
commit 44175257de
3 changed files with 177 additions and 4 deletions
@@ -195,8 +195,6 @@ class AccountEdiXmlUBLBIS3(models.AbstractModel):
for vals in vals_list:
vals.pop('name')
# [UBL-CR-601]-A UBL invoice should not include the InvoiceLine Item ClassifiedTaxCategory TaxExemptionReason
#vals.pop('tax_exemption_reason')
return vals_list
@@ -212,6 +210,17 @@ class AccountEdiXmlUBLBIS3(models.AbstractModel):
return vals_list
def _get_invoice_line_item_vals(self, line, taxes_vals):
# EXTENDS account.edi.xml.ubl_21
line_item_vals = super()._get_invoice_line_item_vals(line, taxes_vals)
for val in line_item_vals['classified_tax_category_vals']:
# [UBL-CR-601] TaxExemptionReason must not appear in InvoiceLine Item ClassifiedTaxCategory
# [BR-E-10] TaxExemptionReason must only appear in TaxTotal TaxSubtotal TaxCategory
val.pop('tax_exemption_reason')
return line_item_vals
def _get_invoice_line_allowance_vals_list(self, line, tax_values_list=None):
# EXTENDS account.edi.xml.ubl_21
vals_list = super()._get_invoice_line_allowance_vals_list(line, tax_values_list=tax_values_list)
@@ -0,0 +1,142 @@
<?xml version='1.0' encoding='UTF-8'?>
<Invoice xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2">
<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0</cbc:CustomizationID>
<cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>
<cbc:ID>___ignore___</cbc:ID>
<cbc:IssueDate>2017-01-01</cbc:IssueDate>
<cbc:DueDate>2017-02-28</cbc:DueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:Note>test narration</cbc:Note>
<cbc:DocumentCurrencyCode>USD</cbc:DocumentCurrencyCode>
<cbc:BuyerReference>ref_partner_2</cbc:BuyerReference>
<cac:OrderReference>
<cbc:ID>___ignore___</cbc:ID>
</cac:OrderReference>
<cac:AdditionalDocumentReference>
<cbc:ID>___ignore___</cbc:ID>
<cac:Attachment>
<cbc:EmbeddedDocumentBinaryObject mimeCode="application/pdf" filename="INV_2017_00001.pdf">___ignore___</cbc:EmbeddedDocumentBinaryObject>
</cac:Attachment>
</cac:AdditionalDocumentReference>
<cac:AccountingSupplierParty>
<cac:Party>
<cbc:EndpointID schemeID="0208">0202239951</cbc:EndpointID>
<cac:PartyName>
<cbc:Name>partner_1</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Chaussée de Namur 40</cbc:StreetName>
<cbc:CityName>Ramillies</cbc:CityName>
<cbc:PostalZone>1367</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>BE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
<cac:PartyTaxScheme>
<cbc:CompanyID>BE0202239951</cbc:CompanyID>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>partner_1</cbc:RegistrationName>
<cbc:CompanyID>BE0202239951</cbc:CompanyID>
</cac:PartyLegalEntity>
<cac:Contact>
<cbc:Name>partner_1</cbc:Name>
</cac:Contact>
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cac:Party>
<cbc:EndpointID schemeID="0208">0477472701</cbc:EndpointID>
<cac:PartyName>
<cbc:Name>partner_2</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Rue des Bourlottes 9</cbc:StreetName>
<cbc:CityName>Ramillies</cbc:CityName>
<cbc:PostalZone>1367</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>BE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
<cac:PartyTaxScheme>
<cbc:CompanyID>BE0477472701</cbc:CompanyID>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>partner_2</cbc:RegistrationName>
<cbc:CompanyID>BE0477472701</cbc:CompanyID>
</cac:PartyLegalEntity>
<cac:Contact>
<cbc:Name>partner_2</cbc:Name>
</cac:Contact>
</cac:Party>
</cac:AccountingCustomerParty>
<cac:Delivery>
<cac:DeliveryLocation>
<cac:Address>
<cbc:StreetName>Rue des Bourlottes 9</cbc:StreetName>
<cbc:CityName>Ramillies</cbc:CityName>
<cbc:PostalZone>1367</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>BE</cbc:IdentificationCode>
</cac:Country>
</cac:Address>
</cac:DeliveryLocation>
</cac:Delivery>
<cac:PaymentMeans>
<cbc:PaymentMeansCode name="credit transfer">30</cbc:PaymentMeansCode>
<cbc:PaymentID>___ignore___</cbc:PaymentID>
<cac:PayeeFinancialAccount>
<cbc:ID>BE15001559627230</cbc:ID>
</cac:PayeeFinancialAccount>
</cac:PaymentMeans>
<cac:PaymentTerms>
<cbc:Note>Payment terms: 30% Advance End of Following Month</cbc:Note>
</cac:PaymentTerms>
<cac:TaxTotal>
<cbc:TaxAmount currencyID="USD">0.00</cbc:TaxAmount>
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="USD">990.00</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="USD">0.00</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:ID>E</cbc:ID>
<cbc:Percent>0.0</cbc:Percent>
<cbc:TaxExemptionReason>Articles 226 items 11 to 15 Directive 2006/112/EN</cbc:TaxExemptionReason>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
</cac:TaxTotal>
<cac:LegalMonetaryTotal>
<cbc:LineExtensionAmount currencyID="USD">990.00</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="USD">990.00</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="USD">990.00</cbc:TaxInclusiveAmount>
<cbc:PrepaidAmount currencyID="USD">0.00</cbc:PrepaidAmount>
<cbc:PayableAmount currencyID="USD">990.00</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="C62">1.0</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="USD">990.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Description>product_a</cbc:Description>
<cbc:Name>product_a</cbc:Name>
<cac:ClassifiedTaxCategory>
<cbc:ID>E</cbc:ID>
<cbc:Percent>0.0</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:ClassifiedTaxCategory>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="USD">990.0</cbc:PriceAmount>
</cac:Price>
</cac:InvoiceLine>
</Invoice>
@@ -80,6 +80,14 @@ class TestUBLBE(TestUBLCommon, TestAccountMoveSendCommon):
'country_id': cls.env.ref('base.be').id,
})
cls.tax_0 = cls.env['account.tax'].create({
'name': 'tax_0',
'amount_type': 'percent',
'amount': 0,
'type_tax_use': 'sale',
'country_id': cls.env.ref('base.be').id,
})
cls.env.company.invoice_is_ubl_cii = True
cls.pay_term = cls.env['account.payment.term'].create({
@@ -504,6 +512,21 @@ class TestUBLBE(TestUBLCommon, TestAccountMoveSendCommon):
price_amount = etree.fromstring(invoice.ubl_cii_xml_id.raw).find('.//{*}InvoiceLine/{*}Price/{*}PriceAmount')
self.assertEqual(price_amount.text, '102.15')
def test_export_tax_exempt(self):
invoice = self._generate_move(
self.partner_1,
self.partner_2,
move_type='out_invoice',
invoice_line_ids=[
{
'product_id': self.product_a.id,
'price_unit': 990.0,
'tax_ids': [(6, 0, self.tax_0.ids)],
},
],
)
self._assert_invoice_attachment(invoice.ubl_cii_xml_id, None, 'from_odoo/bis3_out_invoice_tax_exempt.xml')
####################################################
# Test import
####################################################
@@ -677,12 +700,11 @@ class TestUBLBE(TestUBLCommon, TestAccountMoveSendCommon):
def test_import_payment_terms(self):
# The tax 21% from l10n_be is retrieved since it's a duplicate of self.tax_21
tax_21 = self.env.ref(f'account.{self.env.company.id}_attn_VAT-OUT-21-L')
tax_0 = self.env.ref(f'account.{self.env.company.id}_attn_VAT-OUT-00-L')
self._assert_imported_invoice_from_file(
subfolder='tests/test_files/from_odoo', filename='bis3_pay_term.xml', amount_total=3105.68,
amount_tax=505.68, list_line_subtotals=[-4, -48, 52, 200, 2400],
currency_id=self.currency_data['currency'].id, list_line_price_unit=[-4, -48, 52, 200, 2400],
list_line_discount=[0, 0, 0, 0, 0], list_line_taxes=[self.tax_6, tax_21, tax_0, self.tax_6, tax_21],
list_line_discount=[0, 0, 0, 0, 0], list_line_taxes=[self.tax_6, tax_21, self.tax_0, self.tax_6, tax_21],
move_type='out_invoice',
)