[IMP] account: Archive tax tags upon report changes

When unlinking or writing a tax report line, the related tags that are no longer relevant were not archived.
We now archive tags if they are still used in account.move.line and display a banner
in report that let the user know there might be a problem and invite him to correct tax tags.

closes odoo/odoo#111287

Task: 3084028
X-original-commit: 8fb35df6225f8c8f7c3aa7fec56acb3bec23d25f
Related: odoo/enterprise#36432
Signed-off-by: Josse Colpaert <jco@odoo.com>
Signed-off-by: Olivier Colson (oco) <oco@odoo.com>
Signed-off-by: Claire Bretton <clbr@odoo.com>
This commit is contained in:
Claire Bretton (clbr)
2023-01-30 09:39:45 +01:00
parent f8ebc45d61
commit 4d8fceac2e
5 changed files with 137 additions and 11 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ class AccountAccountTag(models.Model):
in the specified country.
"""
domain = self._get_tax_tags_domain(tag_name, country_id)
return self.env['account.account.tag'].search(domain)
return self.env['account.account.tag'].with_context(active_test=False).search(domain)
@api.model
def _get_tax_tags_domain(self, tag_name, country_id, sign=None):
@@ -203,6 +203,7 @@ class AccountMoveLine(models.Model):
string="Tags",
comodel_name='account.account.tag',
ondelete='restrict',
context={'active_test': False},
tracking=True,
help="Tags assigned to this line by the tax creating it, if any. It determines its impact on financial reports.",
)
+56 -6
View File
@@ -4,7 +4,7 @@
import re
from collections import defaultdict
from odoo import models, fields, api, _, osv
from odoo import models, fields, api, _, osv, Command
from odoo.exceptions import ValidationError, UserError
FIGURE_TYPE_SELECTION_VALUES = [
@@ -388,6 +388,16 @@ class AccountReportLine(models.Model):
if vals_list:
self.env['account.report.expression'].create(vals_list)
@api.ondelete(at_uninstall=False)
def _unlink_child_expressions(self):
"""
We explicitly unlink child expressions.
This is necessary even if there is an ondelete='cascade' on it, because
the @api.ondelete method _unlink_archive_used_tags is not automatically
called if the parent model is deleted.
"""
self.expression_ids.unlink()
class AccountReportExpression(models.Model):
_name = "account.report.expression"
@@ -472,8 +482,9 @@ class AccountReportExpression(models.Model):
country = expression.report_line_id.report_id.country_id
existing_tags = self.env['account.account.tag']._get_tax_tags(tag_name, country.id)
if not existing_tags:
tag_vals = self._get_tags_create_vals(tag_name, country.id)
if len(existing_tags) < 2:
# We can have only one tag in case we archived it and deleted its unused complement sign
tag_vals = self._get_tags_create_vals(tag_name, country.id, existing_tag=existing_tags)
self.env['account.account.tag'].create(tag_vals)
return result
@@ -510,6 +521,35 @@ class AccountReportExpression(models.Model):
return result
@api.ondelete(at_uninstall=False)
def _unlink_archive_used_tags(self):
"""
Manages unlink or archive of tax_tags when account.report.expression are deleted.
If a tag is still in use on amls, we archive it.
"""
expressions_tags = self._get_matching_tags()
tags_to_archive = self.env['account.account.tag']
tags_to_unlink = self.env['account.account.tag']
for tag in expressions_tags:
other_expression_using_tag = self.env['account.report.expression'].sudo().search([
('engine', '=', 'tax_tags'),
('formula', '=', tag.name[1:]), # we escape the +/- sign
('report_line_id.report_id.country_id.id', '=', tag.country_id.id),
('id', 'not in', self.ids),
], limit=1)
if not other_expression_using_tag:
aml_using_tag = self.env['account.move.line'].sudo().search([('tax_tag_ids', 'in', tag.id)], limit=1)
if aml_using_tag:
tags_to_archive += tag
else:
tags_to_unlink += tag
if tags_to_archive or tags_to_unlink:
rep_lines_with_tag = self.env['account.tax.repartition.line'].sudo().search([('tag_ids', 'in', (tags_to_archive + tags_to_unlink).ids)])
rep_lines_with_tag.write({'tag_ids': [Command.unlink(tag.id) for tag in tags_to_archive + tags_to_unlink]})
tags_to_archive.active = False
tags_to_unlink.unlink()
def name_get(self):
return [(expr.id, f'{expr.report_line_name} [{expr.label}]') for expr in self]
@@ -570,10 +610,15 @@ class AccountReportExpression(models.Model):
country = tag_expression.report_line_id.report_id.country_id
or_domains.append(self.env['account.account.tag']._get_tax_tags_domain(tag_expression.formula, country.id))
return self.env['account.account.tag'].search(osv.expression.OR(or_domains))
return self.env['account.account.tag'].with_context(active_test=False).search(osv.expression.OR(or_domains))
@api.model
def _get_tags_create_vals(self, tag_name, country_id):
def _get_tags_create_vals(self, tag_name, country_id, existing_tag=None):
"""
We create the plus and minus tags with tag_name.
In case there is an existing_tag (which can happen if we deleted its unused complement sign)
we only recreate the missing sign.
"""
minus_tag_vals = {
'name': '-' + tag_name,
'applicability': 'taxes',
@@ -586,7 +631,12 @@ class AccountReportExpression(models.Model):
'tax_negate': False,
'country_id': country_id,
}
return [(minus_tag_vals), (plus_tag_vals)]
res = []
if not existing_tag or not existing_tag.tax_negate:
res.append(minus_tag_vals)
if not existing_tag or existing_tag.tax_negate:
res.append(plus_tag_vals)
return res
def _get_carryover_target_expression(self, options):
self.ensure_one()
+1 -1
View File
@@ -1485,7 +1485,7 @@ class AccountTaxRepartitionLineTemplate(models.Model):
domains.append(self.env['account.account.tag']._get_tax_tags_domain(report_expression.formula, country.id, sign=sign))
if domains:
tags_to_add |= self.env['account.account.tag'].search(osv.expression.OR(domains))
tags_to_add |= self.env['account.account.tag'].with_context(active_test=False).search(osv.expression.OR(domains))
return tags_to_add
+78 -3
View File
@@ -68,11 +68,11 @@ class TaxReportTest(AccountTestInvoicingCommon):
],
})
def _get_tax_tags(self, country, tag_name=None):
def _get_tax_tags(self, country, tag_name=None, active_test=True):
domain = [('country_id', '=', country.id), ('applicability', '=', 'taxes')]
if tag_name:
domain.append(('name', 'like', '_' + tag_name ))
return self.env['account.account.tag'].search(domain)
domain.append(('name', 'like', '_' + tag_name))
return self.env['account.account.tag'].with_context(active_test=active_test).search(domain)
def test_create_shared_tags(self):
self.assertEqual(len(self._get_tax_tags(self.test_country_1, tag_name='01')), 2, "tax_tags expressions created for reports within the same countries using the same formula should create a single pair of tags.")
@@ -178,3 +178,78 @@ class TaxReportTest(AccountTestInvoicingCommon):
# Tags already exist since 'copied_report_1' belongs to 'test_country_2'
for tag in line_tags:
self.assertIn(tag, country_2_tags_after_change, "The tax_tags expressions sharing their tags with other report should not receive new tags since they already exist.")
def test_unlink_report_line_tags_used_by_amls(self):
"""
Deletion of a report line whose tags are still referenced by an aml should archive tags and not delete them.
"""
tag_name = "55b"
tax_report_line = self._create_basic_tax_report_line(self.tax_report_1, "Line 55 bis", tag_name)
test_tag = tax_report_line.expression_ids._get_matching_tags().filtered(lambda tag: not tag.tax_negate)
test_tax = self.env['account.tax'].create({
'name': "Test tax",
'amount_type': 'percent',
'amount': 25,
'country_id': self.tax_report_1.country_id.id,
'type_tax_use': 'sale',
'invoice_repartition_line_ids': [
(0, 0, {'factor_percent': 100, 'repartition_type': 'base'}),
(0, 0, {
'factor_percent': 100,
'repartition_type': 'tax',
'tag_ids': [Command.link(test_tag.id)],
}),
],
'refund_repartition_line_ids': [
(0, 0, {'factor_percent': 100, 'repartition_type': 'base'}),
(0, 0, {'factor_percent': 100, 'repartition_type': 'tax'}),
],
})
# Make sure the fiscal country allows using this tax directly
self.env.company.account_fiscal_country_id = self.test_country_1
test_invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': self.partner_a.id,
'date': '1992-12-22',
'invoice_line_ids': [
(0, 0, {'quantity': 1, 'price_unit': 42, 'tax_ids': [Command.set([test_tax.id])]}),
],
})
test_invoice.action_post()
tax_report_line.unlink()
tags_after = self._get_tax_tags(self.test_country_1, tag_name=tag_name, active_test=False)
# only the +tag_name should be kept (and archived), -tag_name should be unlinked
self.assertEqual(tags_after.mapped('tax_negate'), [False], "Unlinking a tax_tags expression should keep the tag if it was used on move lines, and unlink it otherwise.")
self.assertEqual(tags_after.mapped('active'), [False], "Unlinking a tax_tags expression should archive the tag if it was used on move lines, and unlink it otherwise.")
self.assertEqual(len(test_tax.invoice_repartition_line_ids.tag_ids), 0, "After a tag is archived it shouldn't be on tax repartition lines.")
def test_unlink_report_line_tags_used_by_other_expression(self):
"""
Deletion of a report line whose tags are still referenced in other expression should not delete nor archive tags.
"""
tag_name = self.tax_report_line_1_1.expression_ids.formula # tag "O1" is used in both line 1.1 and line 2.1
tags_before = self._get_tax_tags(self.test_country_1, tag_name=tag_name, active_test=False)
tags_archived_before = tags_before.filtered(lambda tag: not tag.active)
self.tax_report_line_1_1.unlink()
tags_after = self._get_tax_tags(self.test_country_1, tag_name=tag_name, active_test=False)
tags_archived_after = tags_after.filtered(lambda tag: not tag.active)
self.assertEqual(len(tags_after), len(tags_before), "Unlinking a report expression whose tags are used by another expression should not delete them.")
self.assertEqual(len(tags_archived_after), len(tags_archived_before), "Unlinking a report expression whose tags are used by another expression should not archive them.")
def test_tag_recreation_archived(self):
"""
In a situation where we have only one of the two (+ and -) sign that exist
we want only the missing sign to be re-created if we try to reuse the same tag name.
(We can get into this state when only one of the signs were used by aml: then we archived it and deleted the complement.)
"""
tag_name = self.tax_report_line_1_55.expression_ids.formula
tags_before = self._get_tax_tags(self.test_country_1, tag_name=tag_name, active_test=False)
tags_before[0].unlink() # we unlink one and archive the other, doesn't matter which one
tags_before[1].active = False
self._create_basic_tax_report_line(self.tax_report_1, "Line 55 bis", tag_name)
tags_after = self._get_tax_tags(self.test_country_1, tag_name=tag_name, active_test=False)
self.assertEqual(len(tags_after), 2, "When creating a tax report line with an archived tag and it's complement doesn't exist, it should be re-created.")
self.assertEqual(tags_after.mapped('name'), ['+' + tag_name, '-' + tag_name], "After creating a tax report line with an archived tag and when its complement doesn't exist, both a negative and a positive tag should be created.")