[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:
co-authored by
Laurent Smet
parent
1f6c655cbc
commit
586b72e0c9
@@ -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"),
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user