[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:
@@ -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.",
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.")
|
||||
|
||||
Reference in New Issue
Block a user