[FIX] account: respect modified invoice tax amount in EDI XML
There is the following problem when editing the tax amounts on an
invoice (in quick-edit mode or via the journal item):
The changed amounts are displayed correctly on the PDF but not in the
EDI XMLs (e.g. the embedded factur-x).
This commit corrects this.
Reproduce
1. Create a new invoice
2. In tab "Invoice Lines" add 2 lines with taxes from different tax group
3. Go to tab "Journal Items" and change the tax amounts
(or use quick-edit mode; needs to be enabled in the settings)
4. Go back to tab "Invoice Lines" and notice the tax amounts of the groups
were changed.
5. Generate a PDF: Here the tax amounts are correct (the changed amounts)
6. Look at the embedded XML: Here the tax amounts are
wrong (initial / unchanged amounts).
The same issue applies to multiple other EDI exports.
To activate the EDIs go to:
Accounting > Configuration > Journals > Customer Invoices
> Advanced Settings tab > Electronic Data Interchange
The EDIs can then be found as attachment in the chatter after confirming
and/or printing an invoice.
task-3535411
closes odoo/odoo#151090
X-original-commit: 5d456ce13970980efa8b1efab92323e995eaa848
Signed-off-by: Laurent Smet (las) <las@odoo.com>
Signed-off-by: Sven Führ (svfu) <svfu@odoo.com>
This commit is contained in:
@@ -8,6 +8,7 @@ from json import dumps
|
||||
import logging
|
||||
from markupsafe import Markup
|
||||
from psycopg2 import OperationalError
|
||||
import math
|
||||
import re
|
||||
from textwrap import shorten
|
||||
from unittest.mock import patch
|
||||
@@ -3185,6 +3186,72 @@ class AccountMove(models.Model):
|
||||
to_update_vals, tax_values_list = self.env['account.tax']._compute_taxes_for_single_line(base_line)
|
||||
to_process.append((base_line, to_update_vals, tax_values_list))
|
||||
|
||||
# Handle manually changed tax amounts (via quick-edit or journal entry manipulation):
|
||||
# For each tax repartition line we compute the difference between the following 2 amounts
|
||||
# * Manual tax amount:
|
||||
# The sum of the amounts on the tax lines belonging to the tax repartition line.
|
||||
# These amounts may have been manually changed.
|
||||
# * Computed tax amount:
|
||||
# The sum of the amounts on the items in 'tax_values_list' in 'to_process' belonging to the tax repartition line.
|
||||
# This difference is then distributed evenly across the 'tax_values_list' in 'to_process'
|
||||
# such that the manual and computed tax amounts match.
|
||||
# The updated tax information is later used by '_aggregate_taxes' to compute the right tax amounts (consistently on all levels).
|
||||
tax_lines = self.line_ids.filtered(lambda x: x.display_type == 'tax')
|
||||
sign = -1 if self.is_inbound(include_receipts=True) else 1
|
||||
|
||||
# Collect the tax_amount_currency/balance from tax lines.
|
||||
current_tax_amount_per_rep_line = {}
|
||||
for tax_line in tax_lines:
|
||||
tax_rep_amounts = current_tax_amount_per_rep_line.setdefault(tax_line.tax_repartition_line_id.id, {
|
||||
'tax_amount_currency': 0.0,
|
||||
'tax_amount': 0.0,
|
||||
})
|
||||
tax_rep_amounts['tax_amount_currency'] += sign * tax_line.amount_currency
|
||||
tax_rep_amounts['tax_amount'] += sign * tax_line.balance
|
||||
|
||||
# Collect the computed tax_amount_currency/tax_amount from the taxes computation.
|
||||
tax_details_per_rep_line = {}
|
||||
for _base_line, _to_update_vals, tax_values_list in to_process:
|
||||
for tax_values in tax_values_list:
|
||||
tax_rep_id = tax_values['tax_repartition_line_id']
|
||||
tax_rep_amounts = tax_details_per_rep_line.setdefault(tax_rep_id, {
|
||||
'tax_amount_currency': 0.0,
|
||||
'tax_amount': 0.0,
|
||||
'distribute_on': [],
|
||||
})
|
||||
tax_rep_amounts['tax_amount_currency'] += tax_values['tax_amount_currency']
|
||||
tax_rep_amounts['tax_amount'] += tax_values['tax_amount']
|
||||
tax_rep_amounts['distribute_on'].append(tax_values)
|
||||
|
||||
# Dispatch the delta on tax_values.
|
||||
for key, currency in (('tax_amount_currency', self.currency_id), ('tax_amount', self.company_currency_id)):
|
||||
for tax_rep_id, computed_tax_rep_amounts in tax_details_per_rep_line.items():
|
||||
current_tax_rep_amounts = current_tax_amount_per_rep_line.get(tax_rep_id, computed_tax_rep_amounts)
|
||||
diff = current_tax_rep_amounts[key] - computed_tax_rep_amounts[key]
|
||||
abs_diff = abs(diff)
|
||||
|
||||
if currency.is_zero(abs_diff):
|
||||
continue
|
||||
|
||||
diff_sign = -1 if diff < 0 else 1
|
||||
nb_error = math.ceil(abs_diff / currency.rounding)
|
||||
nb_cents_per_tax_values = math.floor(nb_error / len(computed_tax_rep_amounts['distribute_on']))
|
||||
nb_extra_cent = nb_error % len(computed_tax_rep_amounts['distribute_on'])
|
||||
for tax_values in computed_tax_rep_amounts['distribute_on']:
|
||||
|
||||
if currency.is_zero(abs_diff):
|
||||
break
|
||||
|
||||
nb_amount_curr_cent = nb_cents_per_tax_values
|
||||
if nb_extra_cent:
|
||||
nb_amount_curr_cent += 1
|
||||
nb_extra_cent -= 1
|
||||
|
||||
# We can have more than one cent to distribute on a single tax_values.
|
||||
abs_delta_to_add = min(abs_diff, currency.rounding * nb_amount_curr_cent)
|
||||
tax_values[key] += diff_sign * abs_delta_to_add
|
||||
abs_diff -= abs_delta_to_add
|
||||
|
||||
return self.env['account.tax']._aggregate_taxes(
|
||||
to_process,
|
||||
filter_tax_values_to_apply=filter_tax_values_to_apply,
|
||||
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<Invoice xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-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>ref_move</cbc:ID>
|
||||
</cac:OrderReference>
|
||||
<cac:AdditionalDocumentReference>
|
||||
<cbc:ID>INV_2017_00001.pdf</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">108.02</cbc:TaxAmount>
|
||||
<cac:TaxSubtotal>
|
||||
<cbc:TaxableAmount currencyID="USD">400.00</cbc:TaxableAmount>
|
||||
<cbc:TaxAmount currencyID="USD">84.03</cbc:TaxAmount>
|
||||
<cac:TaxCategory>
|
||||
<cbc:ID>S</cbc:ID>
|
||||
<cbc:Percent>21.0</cbc:Percent>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:TaxCategory>
|
||||
</cac:TaxSubtotal>
|
||||
<cac:TaxSubtotal>
|
||||
<cbc:TaxableAmount currencyID="USD">200.00</cbc:TaxableAmount>
|
||||
<cbc:TaxAmount currencyID="USD">23.99</cbc:TaxAmount>
|
||||
<cac:TaxCategory>
|
||||
<cbc:ID>S</cbc:ID>
|
||||
<cbc:Percent>12.0</cbc:Percent>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:TaxCategory>
|
||||
</cac:TaxSubtotal>
|
||||
</cac:TaxTotal>
|
||||
<cac:LegalMonetaryTotal>
|
||||
<cbc:LineExtensionAmount currencyID="USD">600.00</cbc:LineExtensionAmount>
|
||||
<cbc:TaxExclusiveAmount currencyID="USD">600.00</cbc:TaxExclusiveAmount>
|
||||
<cbc:TaxInclusiveAmount currencyID="USD">708.02</cbc:TaxInclusiveAmount>
|
||||
<cbc:PrepaidAmount currencyID="USD">0.00</cbc:PrepaidAmount>
|
||||
<cbc:PayableAmount currencyID="USD">708.02</cbc:PayableAmount>
|
||||
</cac:LegalMonetaryTotal>
|
||||
<cac:InvoiceLine>
|
||||
<cbc:ID>1</cbc:ID>
|
||||
<cbc:InvoicedQuantity unitCode="C62">1.0</cbc:InvoicedQuantity>
|
||||
<cbc:LineExtensionAmount currencyID="USD">200.00</cbc:LineExtensionAmount>
|
||||
<cac:Item>
|
||||
<cbc:Description>product_a</cbc:Description>
|
||||
<cbc:Name>product_a</cbc:Name>
|
||||
<cac:ClassifiedTaxCategory>
|
||||
<cbc:ID>S</cbc:ID>
|
||||
<cbc:Percent>21.0</cbc:Percent>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:ClassifiedTaxCategory>
|
||||
</cac:Item>
|
||||
<cac:Price>
|
||||
<cbc:PriceAmount currencyID="USD">200.0</cbc:PriceAmount>
|
||||
</cac:Price>
|
||||
</cac:InvoiceLine>
|
||||
<cac:InvoiceLine>
|
||||
<cbc:ID>2</cbc:ID>
|
||||
<cbc:InvoicedQuantity unitCode="C62">1.0</cbc:InvoicedQuantity>
|
||||
<cbc:LineExtensionAmount currencyID="USD">200.00</cbc:LineExtensionAmount>
|
||||
<cac:Item>
|
||||
<cbc:Description>product_a</cbc:Description>
|
||||
<cbc:Name>product_a</cbc:Name>
|
||||
<cac:ClassifiedTaxCategory>
|
||||
<cbc:ID>S</cbc:ID>
|
||||
<cbc:Percent>21.0</cbc:Percent>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:ClassifiedTaxCategory>
|
||||
</cac:Item>
|
||||
<cac:Price>
|
||||
<cbc:PriceAmount currencyID="USD">200.0</cbc:PriceAmount>
|
||||
</cac:Price>
|
||||
</cac:InvoiceLine>
|
||||
<cac:InvoiceLine>
|
||||
<cbc:ID>3</cbc:ID>
|
||||
<cbc:InvoicedQuantity unitCode="DZN">1.0</cbc:InvoicedQuantity>
|
||||
<cbc:LineExtensionAmount currencyID="USD">100.00</cbc:LineExtensionAmount>
|
||||
<cac:Item>
|
||||
<cbc:Description>product_b</cbc:Description>
|
||||
<cbc:Name>product_b</cbc:Name>
|
||||
<cac:ClassifiedTaxCategory>
|
||||
<cbc:ID>S</cbc:ID>
|
||||
<cbc:Percent>12.0</cbc:Percent>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:ClassifiedTaxCategory>
|
||||
</cac:Item>
|
||||
<cac:Price>
|
||||
<cbc:PriceAmount currencyID="USD">100.0</cbc:PriceAmount>
|
||||
</cac:Price>
|
||||
</cac:InvoiceLine>
|
||||
<cac:InvoiceLine>
|
||||
<cbc:ID>4</cbc:ID>
|
||||
<cbc:InvoicedQuantity unitCode="DZN">1.0</cbc:InvoicedQuantity>
|
||||
<cbc:LineExtensionAmount currencyID="USD">100.00</cbc:LineExtensionAmount>
|
||||
<cac:Item>
|
||||
<cbc:Description>product_b</cbc:Description>
|
||||
<cbc:Name>product_b</cbc:Name>
|
||||
<cac:ClassifiedTaxCategory>
|
||||
<cbc:ID>S</cbc:ID>
|
||||
<cbc:Percent>12.0</cbc:Percent>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:ClassifiedTaxCategory>
|
||||
</cac:Item>
|
||||
<cac:Price>
|
||||
<cbc:PriceAmount currencyID="USD">100.0</cbc:PriceAmount>
|
||||
</cac:Price>
|
||||
</cac:InvoiceLine>
|
||||
</Invoice>
|
||||
@@ -425,6 +425,65 @@ class TestUBLBE(TestUBLCommon, TestAccountMoveSendCommon):
|
||||
)
|
||||
self._assert_invoice_attachment(invoice.ubl_cii_xml_id, None, 'from_odoo/bis3_pay_term_ecotax.xml')
|
||||
|
||||
def test_export_with_changed_taxes(self):
|
||||
invoice = self._generate_move(
|
||||
self.partner_1,
|
||||
self.partner_2,
|
||||
send=False,
|
||||
move_type='out_invoice',
|
||||
invoice_line_ids=[
|
||||
{
|
||||
'product_id': self.product_a.id,
|
||||
'quantity': 1,
|
||||
'price_unit': 200,
|
||||
'tax_ids': [Command.set([self.tax_21.id])],
|
||||
},
|
||||
{
|
||||
'product_id': self.product_a.id,
|
||||
'quantity': 1,
|
||||
'price_unit': 200,
|
||||
'tax_ids': [Command.set([self.tax_21.id])],
|
||||
},
|
||||
{
|
||||
'product_id': self.product_b.id,
|
||||
'quantity': 1,
|
||||
'price_unit': 100,
|
||||
'tax_ids': [Command.set([self.tax_12.id])],
|
||||
},
|
||||
{
|
||||
'product_id': self.product_b.id,
|
||||
'quantity': 1,
|
||||
'price_unit': 100,
|
||||
'tax_ids': [Command.set([self.tax_12.id])],
|
||||
},
|
||||
],
|
||||
)
|
||||
self.assertRecordValues(invoice, [{
|
||||
'amount_untaxed': 600.00,
|
||||
'amount_tax': 108.00, # tax_12: 24.00 ; tax_21: 84.00
|
||||
'amount_total': 708.00
|
||||
}])
|
||||
|
||||
invoice.button_draft()
|
||||
tax_lines = invoice.line_ids.filtered(lambda line: line.display_type == 'tax')
|
||||
tax_line_21 = next((line for line in tax_lines if line.name == 'tax_21'))
|
||||
tax_line_12 = next((line for line in tax_lines if line.name == 'tax_12'))
|
||||
invoice.line_ids = [
|
||||
Command.update(tax_line_21.id, {'amount_currency': -84.03}), # distribute 3 cents over 2 lines
|
||||
Command.update(tax_line_12.id, {'amount_currency': -23.99}), # distribute -1 cent over 2 lines
|
||||
]
|
||||
|
||||
invoice.action_post()
|
||||
invoice._generate_pdf_and_send_invoice(self.move_template)
|
||||
|
||||
self.assertRecordValues(invoice, [{
|
||||
'amount_untaxed': 600.00,
|
||||
'amount_tax': 108.02, # tax_12: 23.99 ; tax_21: 84.03
|
||||
'amount_total': 708.02
|
||||
}])
|
||||
|
||||
self._assert_invoice_attachment(invoice.ubl_cii_xml_id, None, 'from_odoo/bis3_export_with_changed_taxes.xml')
|
||||
|
||||
####################################################
|
||||
# Test import
|
||||
####################################################
|
||||
|
||||
Reference in New Issue
Block a user