[FIX] account: fix interaction between early payment discount and intra-community taxes

Bug:
Currently, creating an intra-community bill and reconciling it with an
early payment can break the tax report.

Steps to reproduce:
1. install the Austrian localization (l10n_at)
2. set `Cash Discount Tax Reduction` to `On early payment`
3. create a €1000 intra-community bill (you can just use the tax called
   `IGE 20%`)
4. set the payment term to`2/7 Net 30` and confirm
5. reconcile the bill with a payment within the discount period
   (2% discount: €980).
6. check the Tax Report: line 5.4 of the report should be €196. If you
   reconciled the bill with a back statement directly, sections
   `Innergemeinschaftliche Erwerb` and `Bemessungsgrundlage` will be
   wrong as well.

Cause:
Since the intra-community applies here, the bill will produce two tax
lines. And because `Cash Discount Tax Reduction` is set to `On early
payment`, those two tax lines will be reduced when an early payment is
made.
However, because of the way the `is_refund` property of
`account.move.line` field is computed on moves of type `entry`, one of
those *tax reduction line* will be considered a refund and the other
will not. Furthermore, the computed tags are correct only because the taxes
are recomputed again when creating the payment.
After removing this extra taxes computation, both tax_tag_ids/tax_tag_invert
are invalid.
So the solution is to fix the method computing the taxes for cash discount lines.

Note: This commit also fixes the cash discount engine not working with multiple tax repartition lines since:
https://github.com/odoo/odoo/commit/9c134ec379819053e3fdd1e252e3a7ae8a949f42

opw-3112197

Enterprise PR: odoo/enterprise#39181

closes odoo/odoo#118588

X-original-commit: de0db2429f7c47d52c1bad37b9d35d425c8d5b74
Related: odoo/enterprise#39775
Signed-off-by: Laurent Smet <las@odoo.com>
Signed-off-by: Nshimiyimana Serge Séna (sesn) <sesn@odoo.com>
Co-authored-by: Laurent Smet <las@odoo.com>
This commit is contained in:
Nshimiyimana Séna
2023-04-14 15:06:02 +02:00
co-authored by Laurent Smet
parent 1f6c655cbc
commit 586b72e0c9
4 changed files with 118 additions and 28 deletions
+35 -21
View File
@@ -2948,9 +2948,14 @@ class AccountMove(models.Model):
def grouping_key_generator(base_line, tax_values):
return self.env['account.tax']._get_generation_dict_from_base_line(base_line, tax_values)
def inverse_tax_rep(tax_rep):
tax = tax_rep.tax_id
index = list(tax.invoice_repartition_line_ids).index(tax_rep)
return tax.refund_repartition_line_ids[index]
# Get the current tax amounts in the current invoice.
tax_amounts = {
line.tax_repartition_line_id.id: {
inverse_tax_rep(line.tax_repartition_line_id).id: {
'amount_currency': line.amount_currency,
'balance': line.balance,
}
@@ -2958,7 +2963,13 @@ class AccountMove(models.Model):
}
product_lines = self.line_ids.filtered(lambda x: x.display_type == 'product')
base_lines = [x._convert_to_tax_base_line_dict() for x in product_lines]
base_lines = [
{
**x._convert_to_tax_base_line_dict(),
'is_refund': True,
}
for x in product_lines
]
for base_line in base_lines:
base_line['taxes'] = base_line['taxes'].filtered(lambda t: t.amount_type != 'fixed')
@@ -2982,8 +2993,9 @@ class AccountMove(models.Model):
term_amount_currency = payment_term_line.amount_currency - payment_term_line.discount_amount_currency
term_balance = payment_term_line.balance - payment_term_line.discount_balance
if early_pay_discount_computation == 'included' and product_lines.tax_ids:
# Compute the base amounts.
# Compute the base amounts.
resulting_delta_base_details = {}
resulting_delta_tax_details = {}
to_process = []
for base_line in base_lines:
invoice_line = base_line['record']
@@ -3023,7 +3035,6 @@ class AccountMove(models.Model):
grouping_key_generator=grouping_key_generator,
)
resulting_delta_tax_details = {}
for tax_detail in tax_details_with_epd['tax_details'].values():
tax_amount_without_epd = tax_amounts.get(tax_detail['tax_repartition_line_id'])
if not tax_amount_without_epd:
@@ -3045,25 +3056,28 @@ class AccountMove(models.Model):
# Multiply the amount by the percentage
percentage_paid = abs(payment_term_line.amount_residual_currency / self.amount_total)
tax_rep = self.env['account.tax.repartition.line'].browse(tax_detail['tax_repartition_line_id'])
tax = tax_rep.tax_id
for tax_detail in resulting_delta_tax_details.values():
tax_rep = self.env['account.tax.repartition.line'].browse(tax_detail['tax_repartition_line_id'])
tax = tax_rep.tax_id
grouping_dict = {
'account_id': tax_detail['account_id'],
'partner_id': tax_detail['partner_id'],
'currency_id': tax_detail['currency_id'],
'analytic_distribution': tax_detail['analytic_distribution'],
'tax_repartition_line_id': tax_rep.id,
'tax_ids': tax_detail['tax_ids'],
'tax_tag_ids': tax_detail['tax_tag_ids'],
'group_tax_id': tax_detail['tax_id'] if tax_detail['tax_id'] != tax.id else None,
}
grouping_dict = {
'account_id': tax_detail['account_id'],
'partner_id': tax_detail['partner_id'],
'currency_id': tax_detail['currency_id'],
'analytic_distribution': tax_detail['analytic_distribution'],
'tax_repartition_line_id': tax_rep.id,
'tax_ids': tax_detail['tax_ids'],
'tax_tag_ids': tax_detail['tax_tag_ids'],
'group_tax_id': tax_detail['tax_id'] if tax_detail['tax_id'] != tax.id else None,
}
res['tax_lines'][payment_term_line][frozendict(grouping_dict)] = {
'name': _("Early Payment Discount (%s)", tax.name),
'amount_currency': payment_term_line.currency_id.round(tax_detail['amount_currency'] * percentage_paid),
'balance': payment_term_line.company_currency_id.round(tax_detail['balance'] * percentage_paid),
'tax_tag_invert': True,
}
res['tax_lines'][payment_term_line][frozendict(grouping_dict)] = {
'name': _("Early Payment Discount (%s)", tax.name),
'amount_currency': payment_term_line.currency_id.round(tax_detail['tax_amount_currency'] * percentage_paid),
'balance': payment_term_line.company_currency_id.round(tax_detail['tax_amount'] * percentage_paid),
}
for grouping_dict, base_detail in bases_details.items():
res['base_lines'][payment_term_line][grouping_dict] = {
'name': _("Early Payment Discount"),
+8 -6
View File
@@ -873,12 +873,14 @@ class AccountPayment(models.Model):
# Update the existing journal items.
# If dealing with multiple write-off lines, they are dropped and a new one is generated.
pay.move_id.write({
'partner_id': pay.partner_id.id,
'currency_id': pay.currency_id.id,
'partner_bank_id': pay.partner_bank_id.id,
'line_ids': line_ids_commands,
})
pay.move_id\
.with_context(skip_invoice_sync=True)\
.write({
'partner_id': pay.partner_id.id,
'currency_id': pay.currency_id.id,
'partner_bank_id': pay.partner_bank_id.id,
'line_ids': line_ids_commands,
})
def _create_paired_internal_transfer_payment(self):
''' When an internal transfer is posted, a paired payment is created
@@ -375,3 +375,75 @@ class TestAccountEarlyPaymentDiscount(AccountTestInvoicingCommon):
}
],
})
def test_intracomm_bill_with_early_payment_included(self):
tax_tags = self.env['account.account.tag'].create({
'name': f'tax_tag_{i}',
'applicability': 'taxes',
'country_id': self.env.company.account_fiscal_country_id.id,
} for i in range(6))
intracomm_tax = self.env['account.tax'].create({
'name': 'tax20',
'amount_type': 'percent',
'amount': 20,
'type_tax_use': 'purchase',
'invoice_repartition_line_ids': [
# pylint: disable=bad-whitespace
Command.create({'repartition_type': 'base', 'factor_percent': 100.0, 'tag_ids': [Command.set(tax_tags[0].ids)]}),
Command.create({'repartition_type': 'tax', 'factor_percent': 100.0, 'tag_ids': [Command.set(tax_tags[1].ids)]}),
Command.create({'repartition_type': 'tax', 'factor_percent': -100.0, 'tag_ids': [Command.set(tax_tags[2].ids)]}),
],
'refund_repartition_line_ids': [
# pylint: disable=bad-whitespace
Command.create({'repartition_type': 'base', 'factor_percent': 100.0, 'tag_ids': [Command.set(tax_tags[3].ids)]}),
Command.create({'repartition_type': 'tax', 'factor_percent': 100.0, 'tag_ids': [Command.set(tax_tags[4].ids)]}),
Command.create({'repartition_type': 'tax', 'factor_percent': -100.0, 'tag_ids': [Command.set(tax_tags[5].ids)]}),
],
})
early_payment_term = self.env['account.payment.term'].create({
'name': "early_payment_term",
'company_id': self.company_data['company'].id,
'early_pay_discount_computation': 'included',
'early_discount': True,
'discount_percentage': 2,
'discount_days': 7,
'line_ids': [
Command.create({
'value': 'percent',
'value_amount': 100.0,
'nb_days': 30,
}),
],
})
bill = self.env['account.move'].create({
'move_type': 'in_invoice',
'partner_id': self.partner_a.id,
'invoice_payment_term_id': early_payment_term.id,
'invoice_date': '2019-01-01',
'date': '2019-01-01',
'invoice_line_ids': [
Command.create({
'name': 'line',
'price_unit': 1000.0,
'tax_ids': [Command.set(intracomm_tax.ids)],
}),
],
})
bill.action_post()
payment = self.env['account.payment.register']\
.with_context(active_model='account.move', active_ids=bill.ids)\
.create({'payment_date': '2019-01-01'})\
._create_payments()
self.assertRecordValues(payment.line_ids.sorted('balance'), [
# pylint: disable=bad-whitespace
{'amount_currency': -980.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_tag_invert': False},
{'amount_currency': -20.0, 'tax_ids': intracomm_tax.ids, 'tax_tag_ids': tax_tags[3].ids, 'tax_tag_invert': True},
{'amount_currency': -4.0, 'tax_ids': [], 'tax_tag_ids': tax_tags[4].ids, 'tax_tag_invert': True},
{'amount_currency': 4.0, 'tax_ids': [], 'tax_tag_ids': tax_tags[5].ids, 'tax_tag_invert': True},
{'amount_currency': 1000.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_tag_invert': False},
])
@@ -774,7 +774,9 @@ class AccountPaymentRegister(models.TransientModel):
:param edit_mode: Is the wizard in edition mode.
"""
payments = self.env['account.payment'].create([x['create_vals'] for x in to_process])
payments = self.env['account.payment']\
.with_context(skip_invoice_sync=True)\
.create([x['create_vals'] for x in to_process])
for payment, vals in zip(payments, to_process):
vals['payment'] = payment