diff --git a/addons/account/i18n/account.pot b/addons/account/i18n/account.pot
index 295fe8d8fa5..7a0325239c7 100644
--- a/addons/account/i18n/account.pot
+++ b/addons/account/i18n/account.pot
@@ -10233,6 +10233,15 @@ msgstr ""
msgid "Please create new accounts from the Chart of Accounts menu."
msgstr ""
+#. module: account
+#: code:addons/account/models/chart_template.py:0
+#, python-format
+msgid ""
+"Please check these taxes. They might be outdated. We did not update them. "
+"Indeed, they do not exactly match the taxes of the original version of the localization module."
+"
You might want to archive or adapt them.
"
+msgstr ""
+
#. module: account
#: code:addons/account/models/account_payment.py:0
#, python-format
@@ -15434,6 +15443,12 @@ msgstr ""
msgid "You should update this document to reflect your T&C."
msgstr ""
+#. module: account
+#: code:addons/account/models/chart_template.py:0
+#, python-format
+msgid "Your taxes have been updated !"
+msgstr ""
+
#. module: account
#: model_terms:ir.ui.view,arch_db:account.view_account_position_form
msgid "Zip Range"
diff --git a/addons/account/models/chart_template.py b/addons/account/models/chart_template.py
index feda64cb8f0..ade889af9cb 100644
--- a/addons/account/models/chart_template.py
+++ b/addons/account/models/chart_template.py
@@ -7,6 +7,7 @@ from odoo.exceptions import UserError, ValidationError
from odoo.http import request
from odoo.addons.account.models.account_tax import TYPE_TAX_USE
from odoo.addons.account.models.account_account import ACCOUNT_CODE_REGEX
+from odoo.tools import html_escape
import logging
import re
@@ -32,6 +33,127 @@ def preserve_existing_tags_on_taxes(cr, registry, module):
if xml_records:
cr.execute("update ir_model_data set noupdate = 't' where id in %s", [tuple(xml_records.ids)])
+def update_taxes_from_templates(cr, chart_template_xmlid):
+ def _create_tax_from_template(company, template, old_tax=None):
+ """
+ Create a new tax from template with template xmlid, if there was already an old tax with that xmlid we
+ remove the xmlid from it but don't modify anything else.
+ """
+ def _remove_xml_id(xml_id):
+ module, name = xml_id.split(".", 1)
+ env['ir.model.data'].search([('module', '=', module), ('name', '=', name)]).unlink()
+
+ template_vals = template._get_tax_vals_complete(company)
+ chart_template = env["account.chart.template"].with_context(default_company_id=company.id)
+ if old_tax:
+ xml_id = old_tax.get_external_id().get(old_tax.id)
+ if xml_id:
+ _remove_xml_id(xml_id)
+ chart_template.create_record_with_xmlid(company, template, "account.tax", template_vals)
+
+ def _update_tax_from_template(template, tax):
+ # -> update the tax : we only updates tax tags
+ tax_rep_lines = tax.invoice_repartition_line_ids + tax.refund_repartition_line_ids
+ template_rep_lines = template.invoice_repartition_line_ids + template.refund_repartition_line_ids
+ for tax_line, template_line in zip(tax_rep_lines, template_rep_lines):
+ tags_to_add = template_line._get_tags_to_add()
+ tags_to_unlink = tax_line.tag_ids
+ if tags_to_add != tags_to_unlink:
+ tax_line.write({"tag_ids": [(6, 0, tags_to_add.ids)]})
+ _cleanup_tags(tags_to_unlink)
+
+ def _get_template_to_tax_xmlid_mapping(company):
+ """
+ This function uses ir_model_data to return a mapping between the tax templates and the taxes, using their xmlid
+ :returns: {
+ account.tax.template.id: account.tax.id
+ }
+ """
+ env['ir.model.data'].flush_model()
+ env.cr.execute(
+ """
+ SELECT template.res_id AS template_res_id,
+ tax.res_id AS tax_res_id
+ FROM ir_model_data tax
+ JOIN ir_model_data template
+ ON template.name = substr(tax.name, strpos(tax.name, '_') + 1)
+ WHERE tax.model = 'account.tax'
+ AND tax.name LIKE %s
+ -- tax.name is of the form: {company_id}_{account.tax.template.name}
+ """,
+ [r"%s\_%%" % company.id],
+ )
+ tuples = env.cr.fetchall()
+ return dict(tuples)
+
+ def _is_tax_and_template_same(template, tax):
+ """
+ This function compares account.tax and account.tax.template repartition lines.
+ A tax is considered the same as the template if they have the same:
+ - amount_type
+ - amount
+ - repartition lines percentages in the same order
+ """
+ tax_rep_lines = tax.invoice_repartition_line_ids + tax.refund_repartition_line_ids
+ template_rep_lines = template.invoice_repartition_line_ids + template.refund_repartition_line_ids
+ return (
+ tax.amount_type == template.amount_type
+ and tax.amount == template.amount
+ and len(tax_rep_lines) == len(template_rep_lines)
+ and all(
+ rep_line_tax.factor_percent == rep_line_template.factor_percent
+ for rep_line_tax, rep_line_template in zip(tax_rep_lines, template_rep_lines)
+ )
+ )
+
+ def _cleanup_tags(tags):
+ """
+ Checks if the tags are still used in taxes or move lines. If not we delete it.
+ """
+ for tag in tags:
+ tax_using_tag = env['account.tax.repartition.line'].sudo().search([('tag_ids', 'in', tag.id)], limit=1)
+ aml_using_tag = env['account.move.line'].sudo().search([('tax_tag_ids', 'in', tag.id)], limit=1)
+ report_expr_using_tag = tag._get_related_tax_report_expressions()
+ if not (aml_using_tag or tax_using_tag or report_expr_using_tag):
+ tag.unlink()
+
+ def _notify_accountant_managers(taxes_to_check):
+ accountant_manager_group = env.ref("account.group_account_manager")
+ partner_managers_ids = accountant_manager_group.users.mapped('partner_id')
+ odoobot = env.ref('base.partner_root')
+ message_body = _(
+ "Please check these taxes. They might be outdated. We did not update them. "
+ "Indeed, they do not exactly match the taxes of the original version of the localization module.
"
+ "You might want to archive or adapt them.
"
+ )
+ for account_tax in taxes_to_check:
+ message_body += f"- {html_escape(account_tax.name)}
"
+ message_body += "
"
+ env['mail.thread'].message_notify(
+ subject=_('Your taxes have been updated !'),
+ author_id=odoobot.id,
+ body=message_body,
+ partner_ids=[partner.id for partner in partner_managers_ids],
+ )
+
+ env = api.Environment(cr, SUPERUSER_ID, {})
+ chart_template_id = env['ir.model.data']._xmlid_to_res_id(chart_template_xmlid)
+ companies = env['res.company'].search([('chart_template_id', '=', chart_template_id)])
+ outdated_taxes = []
+ for company in companies:
+ template_to_tax = _get_template_to_tax_xmlid_mapping(company)
+ templates = env['account.tax.template'].with_context(active_test=False).search([("chart_template_id", "=", chart_template_id)])
+ for template in templates:
+ tax = env["account.tax"].browse(template_to_tax.get(template.id))
+ if not tax or not _is_tax_and_template_same(template, tax):
+ _create_tax_from_template(company, template, old_tax=tax)
+ if tax:
+ outdated_taxes.append(tax)
+ else:
+ _update_tax_from_template(template, tax)
+ if outdated_taxes:
+ _notify_accountant_managers(outdated_taxes)
+
# ---------------------------------------------------------------
# Account Templates: Account, Tax, Tax Code and chart. + Wizard
# ---------------------------------------------------------------
diff --git a/addons/l10n_lu/__manifest__.py b/addons/l10n_lu/__manifest__.py
index 54178923efd..8c12cf61729 100644
--- a/addons/l10n_lu/__manifest__.py
+++ b/addons/l10n_lu/__manifest__.py
@@ -7,7 +7,7 @@
{
'name': 'Luxembourg - Accounting',
- 'version': '2.0',
+ 'version': '2.1',
'category': 'Accounting/Localizations/Account Charts',
'description': """
This is the base module to manage the accounting chart for Luxembourg.
diff --git a/addons/l10n_lu/migrations/2.1/post-migrate_update_taxes.py b/addons/l10n_lu/migrations/2.1/post-migrate_update_taxes.py
new file mode 100644
index 00000000000..a36a459d145
--- /dev/null
+++ b/addons/l10n_lu/migrations/2.1/post-migrate_update_taxes.py
@@ -0,0 +1,6 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from odoo.addons.account.models.chart_template import update_taxes_from_templates
+
+
+def migrate(cr, version):
+ update_taxes_from_templates(cr, 'l10n_lu.lu_2011_chart_1')