ed1730598d
1) In tax groups repartition lines are not used, however when creating tax groups from templates the default repartition lines are created. If you run the chart update again, it will detect those "useless" default repartition lines and mark them to removal, raising an error when trying to do so as a minial of 2 repartition lines are needed (on base and one tax). 2) When matching taxes, if not match, do not add `False` to the list.
1355 lines
53 KiB
Python
1355 lines
53 KiB
Python
# Copyright 2010 Jordi Esteve, Zikzakmedia S.L. (http://www.zikzakmedia.com)
|
|
# Copyright 2010 Pexego Sistemas Informáticos S.L.(http://www.pexego.es)
|
|
# Borja López Soilán
|
|
# Copyright 2013 Joaquin Gutierrez (http://www.gutierrezweb.es)
|
|
# Copyright 2015 Antonio Espinosa <antonioea@tecnativa.com>
|
|
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
|
# Copyright 2016 Jacques-Etienne Baudoux <je@bcim.be>
|
|
# Copyright 2018 Tecnativa - Pedro M. Baeza
|
|
# Copyright 2020 Noviat - Luc De Meyer
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
|
|
import logging
|
|
from contextlib import closing
|
|
from io import StringIO
|
|
|
|
from markupsafe import Markup
|
|
|
|
from odoo import _, api, exceptions, fields, models, tools
|
|
from odoo.tools import config
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
EXCEPTION_TEXT = "Traceback (most recent call last)"
|
|
|
|
|
|
class WizardUpdateChartsAccounts(models.TransientModel):
|
|
_name = "wizard.update.charts.accounts"
|
|
_description = "Wizard Update Charts Accounts"
|
|
|
|
state = fields.Selection(
|
|
selection=[
|
|
("init", "Configuration"),
|
|
("ready", "Select records to update"),
|
|
("done", "Wizard completed"),
|
|
],
|
|
string="Status",
|
|
readonly=True,
|
|
default="init",
|
|
)
|
|
company_id = fields.Many2one(
|
|
comodel_name="res.company",
|
|
string="Company",
|
|
required=True,
|
|
default=lambda self: self.env.user.company_id.id,
|
|
)
|
|
chart_template_id = fields.Many2one(
|
|
comodel_name="account.chart.template",
|
|
string="Chart Template",
|
|
ondelete="cascade",
|
|
required=True,
|
|
)
|
|
chart_template_ids = fields.Many2many(
|
|
"account.chart.template",
|
|
string="Chart Templates",
|
|
compute="_compute_chart_template_ids",
|
|
help="Includes all chart templates.",
|
|
)
|
|
code_digits = fields.Integer(related="chart_template_id.code_digits")
|
|
lang = fields.Selection(
|
|
lambda self: self._get_lang_selection_options(),
|
|
"Language",
|
|
required=True,
|
|
help="For records searched by name (taxes, fiscal "
|
|
"positions), the template name will be matched against the "
|
|
"record name on this language.",
|
|
default=lambda self: self.env.context.get("lang", self.env.user.lang),
|
|
)
|
|
update_tax = fields.Boolean(
|
|
string="Update taxes",
|
|
default=True,
|
|
help="Existing taxes are updated. Taxes are searched by name.",
|
|
)
|
|
update_tax_repartition_line_account = fields.Boolean(
|
|
string="Update Tax Accounts",
|
|
default=True,
|
|
help="Update account_id field on existing Tax repartition lines",
|
|
)
|
|
update_tax_repartition_line_tags = fields.Boolean(
|
|
string="Update Tax Tags",
|
|
default=True,
|
|
help="Update tag_ids field on existing Tax repartition lines",
|
|
)
|
|
update_account = fields.Boolean(
|
|
string="Update accounts",
|
|
default=True,
|
|
help="Existing accounts are updated. Accounts are searched by code.",
|
|
)
|
|
update_fiscal_position = fields.Boolean(
|
|
string="Update fiscal positions",
|
|
default=True,
|
|
help="Existing fiscal positions are updated. Fiscal positions are "
|
|
"searched by name.",
|
|
)
|
|
continue_on_errors = fields.Boolean(
|
|
default=False,
|
|
help="If set, the wizard will continue to the next step even if "
|
|
"there are minor errors.",
|
|
)
|
|
recreate_xml_ids = fields.Boolean(string="Recreate missing XML-IDs")
|
|
tax_ids = fields.One2many(
|
|
comodel_name="wizard.update.charts.accounts.tax",
|
|
inverse_name="update_chart_wizard_id",
|
|
string="Taxes",
|
|
)
|
|
account_ids = fields.One2many(
|
|
comodel_name="wizard.update.charts.accounts.account",
|
|
inverse_name="update_chart_wizard_id",
|
|
string="Accounts",
|
|
)
|
|
fiscal_position_ids = fields.One2many(
|
|
comodel_name="wizard.update.charts.accounts.fiscal.position",
|
|
inverse_name="update_chart_wizard_id",
|
|
string="Fiscal positions",
|
|
)
|
|
new_taxes = fields.Integer(compute="_compute_new_taxes_count")
|
|
new_accounts = fields.Integer(compute="_compute_new_accounts_count")
|
|
rejected_new_account_number = fields.Integer()
|
|
new_fps = fields.Integer(
|
|
string="New fiscal positions", compute="_compute_new_fps_count"
|
|
)
|
|
updated_taxes = fields.Integer(compute="_compute_updated_taxes_count")
|
|
rejected_updated_account_number = fields.Integer()
|
|
updated_accounts = fields.Integer(compute="_compute_updated_accounts_count")
|
|
updated_fps = fields.Integer(
|
|
string="Updated fiscal positions", compute="_compute_updated_fps_count"
|
|
)
|
|
deleted_taxes = fields.Integer(
|
|
string="Deactivated taxes", compute="_compute_deleted_taxes_count"
|
|
)
|
|
log = fields.Text(string="Messages and Errors", readonly=True)
|
|
tax_field_ids = fields.Many2many(
|
|
comodel_name="ir.model.fields",
|
|
relation="wizard_update_charts_tax_fields_rel",
|
|
string="Tax fields",
|
|
domain=lambda self: self._domain_tax_field_ids(),
|
|
default=lambda self: self._default_tax_field_ids(),
|
|
)
|
|
account_field_ids = fields.Many2many(
|
|
comodel_name="ir.model.fields",
|
|
relation="wizard_update_charts_account_fields_rel",
|
|
string="Account fields",
|
|
domain=lambda self: self._domain_account_field_ids(),
|
|
default=lambda self: self._default_account_field_ids(),
|
|
)
|
|
fp_field_ids = fields.Many2many(
|
|
comodel_name="ir.model.fields",
|
|
relation="wizard_update_charts_fp_fields_rel",
|
|
string="Fiscal position fields",
|
|
domain=lambda self: self._domain_fp_field_ids(),
|
|
default=lambda self: self._default_fp_field_ids(),
|
|
)
|
|
tax_matching_ids = fields.One2many(
|
|
comodel_name="wizard.tax.matching",
|
|
inverse_name="update_chart_wizard_id",
|
|
string="Taxes matching",
|
|
default=lambda self: self._default_tax_matching_ids(),
|
|
)
|
|
account_matching_ids = fields.One2many(
|
|
comodel_name="wizard.account.matching",
|
|
inverse_name="update_chart_wizard_id",
|
|
string="Accounts matching",
|
|
default=lambda self: self._default_account_matching_ids(),
|
|
)
|
|
fp_matching_ids = fields.One2many(
|
|
comodel_name="wizard.fp.matching",
|
|
inverse_name="update_chart_wizard_id",
|
|
string="Fiscal positions matching",
|
|
default=lambda self: self._default_fp_matching_ids(),
|
|
)
|
|
|
|
def _domain_per_name(self, name):
|
|
return [
|
|
("model", "=", name),
|
|
("name", "not in", tuple(self.fields_to_ignore(name))),
|
|
]
|
|
|
|
def _domain_tax_field_ids(self):
|
|
return self._domain_per_name("account.tax.template")
|
|
|
|
def _domain_account_field_ids(self):
|
|
return self._domain_per_name("account.account.template")
|
|
|
|
def _domain_fp_field_ids(self):
|
|
return self._domain_per_name("account.fiscal.position.template")
|
|
|
|
def _default_tax_field_ids(self):
|
|
return [
|
|
(4, x.id)
|
|
for x in self.env["ir.model.fields"].search(self._domain_tax_field_ids())
|
|
]
|
|
|
|
def _default_account_field_ids(self):
|
|
return [
|
|
(4, x.id)
|
|
for x in self.env["ir.model.fields"].search(
|
|
self._domain_account_field_ids()
|
|
)
|
|
]
|
|
|
|
def _default_fp_field_ids(self):
|
|
return [
|
|
(4, x.id)
|
|
for x in self.env["ir.model.fields"].search(self._domain_fp_field_ids())
|
|
]
|
|
|
|
def _get_matching_ids(self, model_name, ordered_opts):
|
|
vals = []
|
|
for seq, opt in enumerate(ordered_opts, 1):
|
|
vals.append((0, False, {"sequence": seq, "matching_value": opt}))
|
|
|
|
all_options = self.env[model_name]._get_matching_selection()
|
|
all_options = map(lambda x: x[0], all_options)
|
|
all_options = list(set(all_options) - set(ordered_opts))
|
|
|
|
for seq, opt in enumerate(all_options, len(ordered_opts) + 1):
|
|
vals.append((0, False, {"sequence": seq, "matching_value": opt}))
|
|
|
|
return vals
|
|
|
|
def _default_fp_matching_ids(self):
|
|
ordered_opts = ["xml_id", "name"]
|
|
return self._get_matching_ids("wizard.fp.matching", ordered_opts)
|
|
|
|
def _default_tax_matching_ids(self):
|
|
ordered_opts = ["xml_id", "description", "name"]
|
|
return self._get_matching_ids("wizard.tax.matching", ordered_opts)
|
|
|
|
def _default_account_matching_ids(self):
|
|
ordered_opts = ["xml_id", "code", "name"]
|
|
return self._get_matching_ids("wizard.account.matching", ordered_opts)
|
|
|
|
@api.model
|
|
def _get_lang_selection_options(self):
|
|
"""Gets the available languages for the selection."""
|
|
langs = self.env["res.lang"].search([])
|
|
return [(lang.code, lang.name) for lang in langs]
|
|
|
|
@api.depends("chart_template_id")
|
|
def _compute_chart_template_ids(self):
|
|
all_parents = self.chart_template_id._get_chart_parent_ids()
|
|
self.chart_template_ids = all_parents
|
|
|
|
@api.depends("tax_ids")
|
|
def _compute_new_taxes_count(self):
|
|
self.new_taxes = len(self.tax_ids.filtered(lambda x: x.type == "new"))
|
|
|
|
@api.depends("account_ids")
|
|
def _compute_new_accounts_count(self):
|
|
self.new_accounts = (
|
|
len(self.account_ids.filtered(lambda x: x.type == "new"))
|
|
- self.rejected_new_account_number
|
|
)
|
|
|
|
@api.depends("fiscal_position_ids")
|
|
def _compute_new_fps_count(self):
|
|
self.new_fps = len(self.fiscal_position_ids.filtered(lambda x: x.type == "new"))
|
|
|
|
@api.depends("tax_ids")
|
|
def _compute_updated_taxes_count(self):
|
|
self.updated_taxes = len(self.tax_ids.filtered(lambda x: x.type == "updated"))
|
|
|
|
@api.depends("account_ids")
|
|
def _compute_updated_accounts_count(self):
|
|
self.updated_accounts = (
|
|
len(self.account_ids.filtered(lambda x: x.type == "updated"))
|
|
- self.rejected_updated_account_number
|
|
)
|
|
|
|
@api.depends("fiscal_position_ids")
|
|
def _compute_updated_fps_count(self):
|
|
self.updated_fps = len(
|
|
self.fiscal_position_ids.filtered(lambda x: x.type == "updated")
|
|
)
|
|
|
|
@api.depends("tax_ids")
|
|
def _compute_deleted_taxes_count(self):
|
|
self.deleted_taxes = len(self.tax_ids.filtered(lambda x: x.type == "deleted"))
|
|
|
|
@api.onchange("company_id")
|
|
def _onchage_company_update_chart_template(self):
|
|
self.chart_template_id = self.company_id.chart_template_id
|
|
|
|
def _reopen(self):
|
|
return {
|
|
"type": "ir.actions.act_window",
|
|
"view_mode": "form",
|
|
"res_id": self.id,
|
|
"res_model": self._name,
|
|
"target": "new",
|
|
# save original model in context,
|
|
# because selecting the list of available
|
|
# templates requires a model in context
|
|
"context": {"default_model": self._name},
|
|
}
|
|
|
|
def action_init(self):
|
|
"""Initial action that sets the initial state."""
|
|
self.write(
|
|
{
|
|
"state": "init",
|
|
"tax_ids": [(2, r.id, False) for r in self.tax_ids],
|
|
"account_ids": [(2, r.id, False) for r in self.account_ids],
|
|
"fiscal_position_ids": [
|
|
(2, r.id, False) for r in self.fiscal_position_ids
|
|
],
|
|
}
|
|
)
|
|
return self._reopen()
|
|
|
|
def action_find_records(self):
|
|
"""Searchs for records to update/create and shows them."""
|
|
self.clear_caches()
|
|
self = self.with_context(lang=self.lang)
|
|
# Search for, and load, the records to create/update.
|
|
if self.update_tax:
|
|
self._find_taxes()
|
|
if self.update_account:
|
|
self._find_accounts()
|
|
if self.update_fiscal_position:
|
|
self._find_fiscal_positions()
|
|
# Write the results, and go to the next step.
|
|
self.state = "ready"
|
|
return self._reopen()
|
|
|
|
def _check_consistency(self):
|
|
"""Method for assuring consistency in operations before performing
|
|
them. For now, implemented:
|
|
|
|
- If a parent tax is tried to be created, children taxes must be
|
|
also included to be created.
|
|
|
|
TODO:
|
|
|
|
- Check that needed accounts in taxes/FPs are created at the same time.
|
|
- Check that needed taxes in FPs are created at the same time.
|
|
"""
|
|
taxes2create = self.tax_ids.filtered(lambda x: x.type == "new")
|
|
parents2create = taxes2create.filtered(lambda x: x.tax_id.children_tax_ids)
|
|
for parent in parents2create:
|
|
if bool(
|
|
parent.tax_id.children_tax_ids - taxes2create.mapped("tax_id")
|
|
): # some children taxes are not included to be added
|
|
raise exceptions.UserError(
|
|
_(
|
|
"You have at least one parent tax template (%s) whose "
|
|
"children taxes are not going to be created. Aborting "
|
|
"as this will provoke an infinite loop. Please check "
|
|
"if children have been matched, but not the parent one."
|
|
)
|
|
% parent.tax_id.name
|
|
)
|
|
|
|
def action_update_records(self):
|
|
"""Action that creates/updates/deletes the selected elements."""
|
|
self._check_consistency()
|
|
self = self.with_context(lang=self.lang)
|
|
self.rejected_new_account_number = 0
|
|
self.rejected_updated_account_number = 0
|
|
with closing(StringIO()) as log_output:
|
|
handler = logging.StreamHandler(log_output)
|
|
_logger.addHandler(handler)
|
|
# Create or update the records.
|
|
if self.update_tax:
|
|
todo_dict = self._update_taxes()
|
|
perform_rest = True
|
|
if self.update_account:
|
|
self._update_accounts()
|
|
if (
|
|
EXCEPTION_TEXT in log_output.getvalue()
|
|
and not self.continue_on_errors
|
|
): # Abort early
|
|
perform_rest = False
|
|
# Clear this cache for avoiding incorrect account hits (as it was
|
|
# queried before account creation)
|
|
self.find_account_by_templates.clear_cache(self)
|
|
if self.update_tax and perform_rest:
|
|
self._update_taxes_pending_for_accounts(todo_dict)
|
|
if self.update_fiscal_position and perform_rest:
|
|
self._update_fiscal_positions()
|
|
# Store new chart in the company
|
|
self.company_id.chart_template_id = self.chart_template_id
|
|
_logger.removeHandler(handler)
|
|
self.log = log_output.getvalue()
|
|
# Check if errors where detected and wether we should stop.
|
|
if EXCEPTION_TEXT in self.log and not self.continue_on_errors:
|
|
raise exceptions.UserError(
|
|
_("One or more errors detected!\n\n%s") % self.log
|
|
)
|
|
# Store the data and go to the next step.
|
|
self.state = "done"
|
|
return self._reopen()
|
|
|
|
def _get_real_xml_name(self, template):
|
|
[external_id] = template.get_external_id().values()
|
|
(name, module) = external_id.split(".")
|
|
return "%s.%d_%s" % (name, self.company_id.id, module)
|
|
|
|
@tools.ormcache("templates")
|
|
def find_taxes_by_templates(self, templates):
|
|
tax_ids = []
|
|
for tax in templates:
|
|
tax_id = self.find_tax_by_templates(tax)
|
|
if tax_id:
|
|
tax_ids.append(tax_id)
|
|
return self.env["account.tax"].browse(tax_ids)
|
|
|
|
@tools.ormcache("templates")
|
|
def find_tax_by_templates(self, templates):
|
|
"""Find a tax that matches the template."""
|
|
# search inactive taxes too, to avoid re-creating
|
|
# taxes that have been deactivated before
|
|
tax_model = self.env["account.tax"].with_context(active_test=False)
|
|
for template in templates:
|
|
for matching in self.tax_matching_ids.sorted("sequence"):
|
|
if matching.matching_value == "xml_id":
|
|
real = self.env.ref(
|
|
self._get_real_xml_name(template), raise_if_not_found=False
|
|
)
|
|
if not real:
|
|
continue
|
|
criteria = ("id", "=", real.id)
|
|
else:
|
|
field_name = matching.matching_value
|
|
if not template[field_name]:
|
|
continue
|
|
criteria = (field_name, "=", template[field_name])
|
|
|
|
result = tax_model.search(
|
|
[
|
|
criteria,
|
|
("company_id", "=", self.company_id.id),
|
|
("type_tax_use", "=", template.type_tax_use),
|
|
],
|
|
limit=1,
|
|
)
|
|
if result:
|
|
return result.id
|
|
|
|
return False
|
|
|
|
@tools.ormcache("templates", "current_repartition")
|
|
def find_repartition_by_templates(
|
|
self, templates, current_repartition, tax, inverse_name
|
|
):
|
|
upd_acc = self.update_tax_repartition_line_account
|
|
upd_tags = self.update_tax_repartition_line_tags
|
|
result = []
|
|
existing_ids = []
|
|
for i, tpl in enumerate(templates):
|
|
factor_percent = tpl.factor_percent
|
|
repartition_type = tpl.repartition_type
|
|
account_id = self.find_account_by_templates(tpl.account_id)
|
|
tags = self.env["account.account.tag"]
|
|
plus_expressions = tpl.plus_report_expression_ids.filtered(
|
|
lambda x: x.engine == "tax_tags"
|
|
)
|
|
for expression in plus_expressions:
|
|
country = expression.report_line_id.report_id.country_id
|
|
existing_tags = self.env["account.account.tag"]._get_tax_tags(
|
|
expression.formula, country.id
|
|
)
|
|
tags |= existing_tags.filtered(lambda x: not x.tax_negate)
|
|
minus_expressions = tpl.minus_report_expression_ids.filtered(
|
|
lambda x: x.engine == "tax_tags"
|
|
)
|
|
for expression in minus_expressions:
|
|
country = expression.report_line_id.report_id.country_id
|
|
existing_tags = self.env["account.account.tag"]._get_tax_tags(
|
|
expression.formula, country.id
|
|
)
|
|
tags |= existing_tags.filtered(lambda x: x.tax_negate)
|
|
tags += tpl.tag_ids
|
|
existing = self.env["account.tax.repartition.line"]
|
|
existing_candidates = current_repartition.filtered(
|
|
lambda r: r.factor_percent == factor_percent
|
|
and r.repartition_type == repartition_type
|
|
and r.id not in existing_ids
|
|
)
|
|
if len(existing_candidates) == 1:
|
|
existing = existing_candidates
|
|
elif len(existing_candidates) > 1:
|
|
# We may have this situation in case of e.g. 50%/50% on tax.
|
|
# In this case we assume that the repartition line order
|
|
# is the same between templates and actual tax objects
|
|
existing_candidate = current_repartition[i]
|
|
if existing_candidate in existing_candidates:
|
|
existing = existing_candidate
|
|
|
|
if existing:
|
|
existing_ids.append(existing.id)
|
|
upd_vals = {}
|
|
if upd_acc and existing.account_id.id != account_id:
|
|
upd_vals["account_id"] = account_id
|
|
if upd_tags:
|
|
if existing.tag_ids != tags:
|
|
upd_vals["tag_ids"] = [(6, 0, tags.ids)]
|
|
if upd_vals:
|
|
# update record
|
|
result.append((1, existing.id, upd_vals))
|
|
if not existing:
|
|
# create a new mapping
|
|
result.append(
|
|
(
|
|
0,
|
|
0,
|
|
{
|
|
inverse_name: tax.id,
|
|
"factor_percent": factor_percent,
|
|
"repartition_type": repartition_type,
|
|
"account_id": account_id,
|
|
"tag_ids": [(6, 0, tags.ids)],
|
|
},
|
|
)
|
|
)
|
|
|
|
if tax.amount_type != "group":
|
|
# Mark to be removed the lines not found
|
|
remove_ids = [x for x in current_repartition.ids if x not in existing_ids]
|
|
result += [(2, x) for x in remove_ids]
|
|
return result
|
|
|
|
@api.model
|
|
@tools.ormcache("code")
|
|
def padded_code(self, code):
|
|
"""Return a right-zero-padded code with the chosen digits."""
|
|
return code.ljust(self.code_digits, "0")
|
|
|
|
@tools.ormcache("templates")
|
|
def find_accounts_by_templates(self, templates):
|
|
account_ids = []
|
|
for account in templates:
|
|
account_ids.append(self.find_account_by_templates(account))
|
|
return self.env["account.account"].browse(account_ids)
|
|
|
|
@tools.ormcache("templates")
|
|
def find_account_by_templates(self, templates):
|
|
"""Find an account that matches the template."""
|
|
account_model = self.env["account.account"]
|
|
for matching in self.account_matching_ids.sorted("sequence"):
|
|
if matching.matching_value == "xml_id":
|
|
real = self.env["account.account"]
|
|
for template in templates:
|
|
try:
|
|
real |= self.env.ref(self._get_real_xml_name(template))
|
|
except BaseException:
|
|
_logger.info("Is not real xml Name")
|
|
|
|
if not real:
|
|
continue
|
|
criteria = ("id", "in", real.ids)
|
|
elif matching.matching_value == "code":
|
|
codes = templates.mapped("code")
|
|
if not codes:
|
|
continue
|
|
criteria = ("code", "in", list(map(self.padded_code, codes)))
|
|
else:
|
|
field_name = matching.matching_value
|
|
field_values = templates.mapped(field_name)
|
|
if not field_values:
|
|
continue
|
|
criteria = (field_name, "in", field_values)
|
|
|
|
result = account_model.search(
|
|
[criteria, ("company_id", "=", self.company_id.id)]
|
|
)
|
|
if result:
|
|
return result.id
|
|
|
|
return False
|
|
|
|
@tools.ormcache("templates")
|
|
def find_fp_by_templates(self, templates):
|
|
"""Find a real fiscal position from a template."""
|
|
fp_model = self.env["account.fiscal.position"]
|
|
for matching in self.fp_matching_ids.sorted("sequence"):
|
|
if matching.matching_value == "xml_id":
|
|
real = self.env["account.fiscal.position"]
|
|
for template in templates:
|
|
try:
|
|
real |= self.env.ref(self._get_real_xml_name(template))
|
|
except BaseException:
|
|
_logger.info("Is not real xml Name")
|
|
|
|
if not real:
|
|
continue
|
|
criteria = ("id", "in", real.ids)
|
|
else:
|
|
field_name = matching.matching_value
|
|
field_values = templates.mapped(field_name)
|
|
if not field_values:
|
|
continue
|
|
criteria = (field_name, "in", field_values)
|
|
|
|
result = fp_model.search(
|
|
[criteria, ("company_id", "=", self.company_id.id)], limit=1
|
|
)
|
|
if result:
|
|
return result.id
|
|
|
|
return False
|
|
|
|
@tools.ormcache("templates", "current_fp_accounts")
|
|
def find_fp_account_by_templates(self, templates, current_fp_accounts):
|
|
result = []
|
|
for tpl in templates:
|
|
pos_id = self.find_fp_by_templates(tpl.position_id)
|
|
src_id = self.find_account_by_templates(tpl.account_src_id)
|
|
dest_id = self.find_account_by_templates(tpl.account_dest_id)
|
|
existing = self.env["account.fiscal.position.account"].search(
|
|
[
|
|
("position_id", "=", pos_id),
|
|
("account_src_id", "=", src_id),
|
|
("account_dest_id", "=", dest_id),
|
|
]
|
|
)
|
|
if not existing:
|
|
# create a new mapping
|
|
result.append(
|
|
(
|
|
0,
|
|
0,
|
|
{
|
|
"position_id": pos_id,
|
|
"account_src_id": src_id,
|
|
"account_dest_id": dest_id,
|
|
},
|
|
)
|
|
)
|
|
else:
|
|
current_fp_accounts -= existing
|
|
# Mark to be removed the lines not found
|
|
if current_fp_accounts:
|
|
result += [(2, x.id) for x in current_fp_accounts]
|
|
return result
|
|
|
|
@tools.ormcache("templates", "current_fp_taxes")
|
|
def find_fp_tax_by_templates(self, templates, current_fp_taxes):
|
|
result = []
|
|
for tpl in templates:
|
|
pos_id = self.find_fp_by_templates(tpl.position_id)
|
|
src_id = self.find_tax_by_templates(tpl.tax_src_id)
|
|
dest_id = self.find_tax_by_templates(tpl.tax_dest_id)
|
|
existing = self.env["account.fiscal.position.tax"].search(
|
|
[
|
|
("position_id", "=", pos_id),
|
|
("tax_src_id", "=", src_id),
|
|
("tax_dest_id", "=", dest_id),
|
|
]
|
|
)
|
|
if not existing:
|
|
# create a new mapping
|
|
result.append(
|
|
(
|
|
0,
|
|
0,
|
|
{
|
|
"position_id": pos_id,
|
|
"tax_src_id": src_id,
|
|
"tax_dest_id": dest_id,
|
|
},
|
|
)
|
|
)
|
|
else:
|
|
current_fp_taxes -= existing
|
|
# Mark to be removed the lines not found
|
|
if current_fp_taxes:
|
|
result += [(2, x.id) for x in current_fp_taxes]
|
|
return result
|
|
|
|
@api.model
|
|
@tools.ormcache("name")
|
|
def fields_to_ignore(self, name):
|
|
"""Get fields that will not be used when checking differences.
|
|
|
|
:param str template: A template record.
|
|
:param str name: The name of the template model.
|
|
:return set: Fields to ignore in diff.
|
|
"""
|
|
specials_mapping = {
|
|
"account.tax.template": {"chart_template_id", "children_tax_ids"},
|
|
"account.account.template": set(self.env["mail.thread"]._fields)
|
|
| {"chart_template_id", "root_id", "nocreate"},
|
|
"account.fiscal.position.template": {"chart_template_id"},
|
|
}
|
|
specials = {
|
|
"display_name",
|
|
"__last_update",
|
|
"company_id",
|
|
} | specials_mapping.get(name, set())
|
|
return set(models.MAGIC_COLUMNS) | specials
|
|
|
|
@api.model
|
|
def diff_fields(self, template, real): # noqa: C901
|
|
"""Get fields that are different in template and real records.
|
|
|
|
:param odoo.models.Model template:
|
|
Template record.
|
|
:param odoo.models.Model real:
|
|
Real record.
|
|
|
|
:return dict:
|
|
Fields that are different in both records, and the expected value.
|
|
"""
|
|
result = dict()
|
|
ignore = self.fields_to_ignore(template._name)
|
|
template_field_mapping = {
|
|
"account.tax.template": self.tax_field_ids,
|
|
"account.account.template": self.account_field_ids,
|
|
"account.fiscal.position.template": self.fp_field_ids,
|
|
}
|
|
to_include = template_field_mapping[template._name].mapped("name")
|
|
for key, field in template._fields.items():
|
|
if key in ignore or key not in to_include or not hasattr(real, key):
|
|
continue
|
|
expected = None
|
|
# Translate template records to reals for comparison
|
|
relation = field.get_description(self.env).get("relation", "")
|
|
if relation:
|
|
if relation == "account.tax.template":
|
|
expected = self.find_taxes_by_templates(template[key])
|
|
elif relation == "account.account.template":
|
|
expected = self.find_accounts_by_templates(template[key])
|
|
elif relation == "account.fiscal.position.tax.template":
|
|
expected = self.find_fp_tax_by_templates(template[key], real[key])
|
|
elif relation == "account.fiscal.position.account.template":
|
|
expected = self.find_fp_account_by_templates(
|
|
template[key], real[key]
|
|
)
|
|
elif relation == "account.tax.repartition.line.template":
|
|
expected = self.find_repartition_by_templates(
|
|
template[key], real[key], real, field.inverse_name
|
|
)
|
|
# Register detected differences
|
|
if expected is not None:
|
|
if expected != [] and (
|
|
key
|
|
in ["invoice_repartition_line_ids", "refund_repartition_line_ids"]
|
|
or (isinstance(expected, models.Model) and expected != real[key])
|
|
or (not isinstance(expected, models.Model))
|
|
):
|
|
result[key] = expected
|
|
else:
|
|
template_value, real_value = template[key], real[key]
|
|
if template._name == "account.account.template" and key == "code":
|
|
template_value = self.padded_code(template["code"])
|
|
# Normalize values when one field is Char and the other is Html
|
|
if (
|
|
isinstance(template_value, str) and isinstance(real_value, Markup)
|
|
) or (
|
|
isinstance(template_value, Markup) and isinstance(real_value, str)
|
|
):
|
|
template_value = Markup(template_value).striptags()
|
|
real_value = Markup(real_value).striptags()
|
|
if template_value != real_value:
|
|
result[key] = template_value
|
|
# Avoid to cache recordset references
|
|
if key in result:
|
|
if isinstance(real._fields[key], fields.Many2many):
|
|
result[key] = [(6, 0, result[key].ids)]
|
|
elif isinstance(real._fields[key], fields.Many2one):
|
|
result[key] = result[key].id
|
|
return result
|
|
|
|
@api.model
|
|
def diff_notes(self, template, real):
|
|
"""Get notes for humans on why is this record going to be updated.
|
|
|
|
:param openerp.models.Model template:
|
|
Template record.
|
|
|
|
:param openerp.models.Model real:
|
|
Real record.
|
|
|
|
:return str:
|
|
Notes result.
|
|
"""
|
|
result = list()
|
|
different_fields = sorted(
|
|
template._fields[f].get_description(self.env)["string"]
|
|
for f in self.diff_fields(template, real).keys()
|
|
)
|
|
if different_fields:
|
|
result.append(
|
|
_("Differences in these fields: %s.") % ", ".join(different_fields)
|
|
)
|
|
# Special for taxes
|
|
if template._name == "account.tax.template":
|
|
if not real.active:
|
|
result.append(_("Tax is disabled."))
|
|
return "\n".join(result)
|
|
|
|
@tools.ormcache("self", "template", "real_obj")
|
|
def missing_xml_id(self, template, real_obj):
|
|
ir_model_data = self.env["ir.model.data"]
|
|
template_xmlid = ir_model_data.search(
|
|
[("model", "=", template._name), ("res_id", "=", template.id)]
|
|
)
|
|
new_xml_id = "%d_%s" % (self.company_id.id, template_xmlid.name)
|
|
return not ir_model_data.search(
|
|
[
|
|
("res_id", "=", real_obj.id),
|
|
("model", "=", real_obj._name),
|
|
("module", "=", template_xmlid.module),
|
|
("name", "=", new_xml_id),
|
|
]
|
|
)
|
|
|
|
def _domain_taxes_to_deactivate(self, found_taxes_ids):
|
|
return [
|
|
("company_id", "=", self.company_id.id),
|
|
("id", "not in", found_taxes_ids),
|
|
("active", "=", True),
|
|
]
|
|
|
|
def _find_taxes(self):
|
|
"""Search for, and load, tax templates to create/update/delete."""
|
|
found_taxes_ids = []
|
|
self.tax_ids.unlink()
|
|
# Search for changes between template and real tax
|
|
for template in self.chart_template_ids.with_context(active_test=False).mapped(
|
|
"tax_template_ids"
|
|
):
|
|
# Check if the template matches a real tax
|
|
tax_id = self.find_tax_by_templates(template)
|
|
if not tax_id:
|
|
# Tax to be created
|
|
self.tax_ids.create(
|
|
{
|
|
"tax_id": template.id,
|
|
"update_chart_wizard_id": self.id,
|
|
"type": "new",
|
|
"notes": _("Name or description not found."),
|
|
}
|
|
)
|
|
else:
|
|
found_taxes_ids.append(tax_id)
|
|
# Check the tax for changes
|
|
tax = self.env["account.tax"].browse(tax_id)
|
|
notes = self.diff_notes(template, tax)
|
|
|
|
if self.recreate_xml_ids and self.missing_xml_id(template, tax):
|
|
notes += (notes and "\n" or "") + _("Missing XML-ID.")
|
|
|
|
if notes:
|
|
# Tax to be updated
|
|
self.tax_ids.create(
|
|
{
|
|
"tax_id": template.id,
|
|
"update_chart_wizard_id": self.id,
|
|
"type": "updated",
|
|
"update_tax_id": tax_id,
|
|
"notes": notes,
|
|
}
|
|
)
|
|
# search for taxes not in the template and propose them for
|
|
# deactivation
|
|
taxes_to_deactivate = self.env["account.tax"].search(
|
|
self._domain_taxes_to_deactivate(found_taxes_ids)
|
|
)
|
|
for tax in taxes_to_deactivate:
|
|
self.tax_ids.create(
|
|
{
|
|
"update_chart_wizard_id": self.id,
|
|
"type": "deleted",
|
|
"update_tax_id": tax.id,
|
|
"notes": _("To deactivate: not in the template"),
|
|
}
|
|
)
|
|
|
|
def _find_accounts(self):
|
|
"""Load account templates to create/update."""
|
|
self.account_ids.unlink()
|
|
for template in self.chart_template_ids.mapped("account_ids"):
|
|
# Search for a real account that matches the template
|
|
account_id = self.find_account_by_templates(template)
|
|
if not account_id:
|
|
# Account to be created
|
|
self.account_ids.create(
|
|
{
|
|
"account_id": template.id,
|
|
"update_chart_wizard_id": self.id,
|
|
"type": "new",
|
|
"notes": _("No account found with this code."),
|
|
}
|
|
)
|
|
else:
|
|
# Check the account for changes
|
|
account = self.env["account.account"].browse(account_id)
|
|
notes = self.diff_notes(template, account)
|
|
|
|
if self.recreate_xml_ids and self.missing_xml_id(template, account):
|
|
notes += (notes and "\n" or "") + _("Missing XML-ID.")
|
|
|
|
if notes:
|
|
# Account to be updated
|
|
self.account_ids.create(
|
|
{
|
|
"account_id": template.id,
|
|
"update_chart_wizard_id": self.id,
|
|
"type": "updated",
|
|
"update_account_id": account_id,
|
|
"notes": notes,
|
|
}
|
|
)
|
|
|
|
def _find_fiscal_positions(self):
|
|
"""Load fiscal position templates to create/update."""
|
|
wiz_fp = self.env["wizard.update.charts.accounts.fiscal.position"]
|
|
self.fiscal_position_ids.unlink()
|
|
|
|
# Search for new / updated fiscal positions
|
|
templates = self.env["account.fiscal.position.template"].search(
|
|
[("chart_template_id", "in", self.chart_template_ids.ids)]
|
|
)
|
|
for template in templates:
|
|
# Search for a real fiscal position that matches the template
|
|
fp_id = self.find_fp_by_templates(template)
|
|
if not fp_id:
|
|
# Fiscal position to be created
|
|
wiz_fp.create(
|
|
{
|
|
"fiscal_position_id": template.id,
|
|
"update_chart_wizard_id": self.id,
|
|
"type": "new",
|
|
"notes": _("No fiscal position found with this name."),
|
|
}
|
|
)
|
|
else:
|
|
# Check the fiscal position for changes
|
|
fp = self.env["account.fiscal.position"].browse(fp_id)
|
|
notes = self.diff_notes(template, fp)
|
|
|
|
if self.recreate_xml_ids and self.missing_xml_id(template, fp):
|
|
notes += (notes and "\n" or "") + _("Missing XML-ID.")
|
|
|
|
if notes:
|
|
# Fiscal position template to be updated
|
|
wiz_fp.create(
|
|
{
|
|
"fiscal_position_id": template.id,
|
|
"update_chart_wizard_id": self.id,
|
|
"type": "updated",
|
|
"update_fiscal_position_id": fp_id,
|
|
"notes": notes,
|
|
}
|
|
)
|
|
|
|
def recreate_xml_id(self, template, real_obj):
|
|
ir_model_data = self.env["ir.model.data"]
|
|
template_xmlid = ir_model_data.search(
|
|
[("model", "=", template._name), ("res_id", "=", template.id)]
|
|
)
|
|
new_xml_id = "%d_%s" % (self.company_id.id, template_xmlid.name)
|
|
ir_model_data.search(
|
|
[("model", "=", real_obj._name), ("res_id", "=", real_obj.id)]
|
|
).unlink()
|
|
template_xmlid.copy(
|
|
{
|
|
"model": real_obj._name,
|
|
"res_id": real_obj.id,
|
|
"name": new_xml_id,
|
|
"noupdate": True,
|
|
}
|
|
)
|
|
|
|
def _update_taxes(self):
|
|
"""Process taxes to create/update/deactivate."""
|
|
# First create taxes in batch
|
|
taxes_to_create = self.tax_ids.filtered(lambda x: x.type == "new")
|
|
todo_dict = taxes_to_create.mapped("tax_id")._generate_tax(self.company_id)
|
|
template_to_tax_dict = {}
|
|
for key in todo_dict["tax_template_to_tax"].keys():
|
|
template_to_tax_dict[key.id] = todo_dict["tax_template_to_tax"][key].id
|
|
for wiz_tax in taxes_to_create:
|
|
new_tax = self.env["account.tax"].browse(
|
|
template_to_tax_dict[wiz_tax.tax_id.id]
|
|
)
|
|
_logger.info(
|
|
_("Created tax %s."), "'{}' (ID:{})".format(new_tax.name, new_tax.id)
|
|
)
|
|
for wiz_tax in self.tax_ids.filtered(lambda x: x.type != "new"):
|
|
template, tax = wiz_tax.tax_id, wiz_tax.update_tax_id
|
|
# Deactivate tax
|
|
if wiz_tax.type == "deleted":
|
|
tax.active = False
|
|
_logger.info(_("Deactivated tax %s."), "'%s'" % tax.name)
|
|
continue
|
|
else:
|
|
updated = False
|
|
for key, value in self.diff_fields(template, tax).items():
|
|
# We defer update because account might not be created yet
|
|
if key in [
|
|
"cash_basis_transition_account_id",
|
|
"invoice_repartition_line_ids",
|
|
"refund_repartition_line_ids",
|
|
]:
|
|
continue
|
|
tax[key] = value
|
|
updated = True
|
|
if updated:
|
|
_logger.info(_("Updated tax %s."), "'%s'" % template.name)
|
|
if self.recreate_xml_ids and self.missing_xml_id(template, tax):
|
|
self.recreate_xml_id(template, tax)
|
|
_logger.info(
|
|
_("Updated tax %s. (Recreated XML-IDs)"), "'%s'" % template.name
|
|
)
|
|
return todo_dict
|
|
|
|
def _update_accounts(self):
|
|
"""Process accounts to create/update."""
|
|
for wiz_account in self.account_ids:
|
|
account, template = (wiz_account.update_account_id, wiz_account.account_id)
|
|
if wiz_account.type == "new":
|
|
# Create the account
|
|
tax_template_ref = {
|
|
tax.id: self.find_tax_by_templates(tax) for tax in template.tax_ids
|
|
}
|
|
vals = self.chart_template_id._get_account_vals(
|
|
self.company_id,
|
|
template,
|
|
self.padded_code(template.code),
|
|
tax_template_ref,
|
|
)
|
|
try:
|
|
with self.env.cr.savepoint():
|
|
self.chart_template_id.create_record_with_xmlid(
|
|
self.company_id, template, "account.account", vals
|
|
)
|
|
_logger.info(
|
|
_("Created account %s."),
|
|
"'{} - {}'".format(vals["code"], vals["name"]),
|
|
)
|
|
except Exception:
|
|
self.rejected_new_account_number += 1
|
|
if config["test_enable"]:
|
|
_logger.info(EXCEPTION_TEXT)
|
|
else: # pragma: no cover
|
|
_logger.exception(
|
|
"ERROR: " + _("Exception creating account %s."),
|
|
"'{} - {}'".format(template.code, template.name),
|
|
)
|
|
if not self.continue_on_errors:
|
|
break
|
|
else:
|
|
# Update the account
|
|
try:
|
|
with self.env.cr.savepoint():
|
|
for key, value in iter(
|
|
self.diff_fields(template, account).items()
|
|
):
|
|
account[key] = value
|
|
_logger.info(
|
|
_("Updated account %s."),
|
|
"'{} - {}'".format(account.code, account.name),
|
|
)
|
|
if self.recreate_xml_ids and self.missing_xml_id(
|
|
template, account
|
|
):
|
|
self.recreate_xml_id(template, account)
|
|
_logger.info(
|
|
_("Updated account %s. (Recreated XML-ID)"),
|
|
"'{} - {}'".format(account.code, account.name),
|
|
)
|
|
|
|
except Exception:
|
|
self.rejected_updated_account_number += 1
|
|
if config["test_enable"]:
|
|
_logger.info(EXCEPTION_TEXT)
|
|
else: # pragma: no cover
|
|
_logger.exception(
|
|
"ERROR: " + _("Exception writing account %s."),
|
|
"'{} - {}'".format(account.code, account.name),
|
|
)
|
|
if not self.continue_on_errors:
|
|
break
|
|
|
|
def _update_taxes_pending_for_accounts(self, todo_dict):
|
|
"""Updates the taxes (created or updated on previous steps) to set
|
|
the references to the accounts (the taxes where created/updated first,
|
|
when the referenced accounts are still not available).
|
|
"""
|
|
done = self.env["account.tax"]
|
|
for k, v in todo_dict["account_dict"]["account.tax"].items():
|
|
vals = {}
|
|
for fld in [
|
|
"cash_basis_transition_account_id",
|
|
]:
|
|
if v[fld]:
|
|
acc_id = self.find_account_by_templates(
|
|
self.env["account.account.template"].browse(v[fld].id)
|
|
)
|
|
if acc_id:
|
|
vals[fld] = acc_id
|
|
else:
|
|
raise exceptions.UserError(
|
|
_("No real account found for template account with ID %s")
|
|
% v[fld].id
|
|
)
|
|
if vals:
|
|
tax = self.env["account.tax"].browse(k)
|
|
tax.write(vals)
|
|
done |= tax
|
|
|
|
for rep_line, v in todo_dict["account_dict"][
|
|
"account.tax.repartition.line"
|
|
].items():
|
|
if v["account_id"]:
|
|
acc_id = self.find_account_by_templates(
|
|
self.env["account.account.template"].browse(v["account_id"].id)
|
|
)
|
|
if acc_id:
|
|
rep_line.write({"account_id": acc_id})
|
|
done |= rep_line.invoice_tax_id or rep_line.refund_tax_id
|
|
else:
|
|
raise exceptions.UserError(
|
|
_("No real account found for template account with ID %s")
|
|
% v["account_id"].id
|
|
)
|
|
|
|
for wiz_tax in self.tax_ids.filtered(lambda r: r.type == "updated"):
|
|
template = wiz_tax.tax_id
|
|
tax = wiz_tax.update_tax_id
|
|
vals = {}
|
|
for key, value in self.diff_fields(template, tax).items():
|
|
if key in {
|
|
"cash_basis_transition_account_id",
|
|
"invoice_repartition_line_ids",
|
|
"refund_repartition_line_ids",
|
|
}:
|
|
vals[key] = value
|
|
if vals:
|
|
tax.write(vals)
|
|
done |= tax
|
|
|
|
if done:
|
|
_logger.info(
|
|
_("Post-updated account fields for taxes with IDs %s."), "%s" % done.ids
|
|
)
|
|
|
|
def _prepare_fp_vals(self, fp_template):
|
|
# Tax mappings
|
|
tax_mapping = []
|
|
for fp_tax in fp_template.tax_ids:
|
|
# Create the fp tax mapping
|
|
tax_mapping.append(
|
|
{
|
|
"tax_src_id": self.find_tax_by_templates(fp_tax.tax_src_id),
|
|
"tax_dest_id": self.find_tax_by_templates(fp_tax.tax_dest_id),
|
|
}
|
|
)
|
|
# Account mappings
|
|
account_mapping = []
|
|
for fp_account in fp_template.account_ids:
|
|
# Create the fp account mapping
|
|
account_mapping.append(
|
|
{
|
|
"account_src_id": (
|
|
self.find_account_by_templates(fp_account.account_src_id)
|
|
),
|
|
"account_dest_id": (
|
|
self.find_account_by_templates(fp_account.account_dest_id)
|
|
),
|
|
}
|
|
)
|
|
return {
|
|
"company_id": self.company_id.id,
|
|
"name": fp_template.name,
|
|
"tax_ids": [(0, 0, x) for x in tax_mapping],
|
|
"account_ids": [(0, 0, x) for x in account_mapping],
|
|
}
|
|
|
|
def _update_fiscal_positions(self):
|
|
"""Process fiscal position templates to create/update."""
|
|
for wiz_fp in self.fiscal_position_ids:
|
|
fp, template = (wiz_fp.update_fiscal_position_id, wiz_fp.fiscal_position_id)
|
|
if wiz_fp.type == "new":
|
|
# Create a new fiscal position
|
|
self.chart_template_id.create_record_with_xmlid(
|
|
self.company_id,
|
|
template,
|
|
"account.fiscal.position",
|
|
self._prepare_fp_vals(template),
|
|
)
|
|
_logger.info(_("Created fiscal position %s."), "'%s'" % template.name)
|
|
else:
|
|
for key, value in self.diff_fields(template, fp).items():
|
|
fp[key] = value
|
|
_logger.info(
|
|
_("Updated fiscal position %s."), "'%s'" % template.name
|
|
)
|
|
|
|
if self.recreate_xml_ids and self.missing_xml_id(template, fp):
|
|
self.recreate_xml_id(template, fp)
|
|
_logger.info(
|
|
_("Updated fiscal position %s. (Recreated XML-ID)"),
|
|
"'%s'" % template.name,
|
|
)
|
|
|
|
|
|
class WizardUpdateChartsAccountsTax(models.TransientModel):
|
|
_name = "wizard.update.charts.accounts.tax"
|
|
_description = "Tax that needs to be updated (new or updated in the " "template)."
|
|
|
|
tax_id = fields.Many2one(
|
|
comodel_name="account.tax.template", string="Tax template", ondelete="set null"
|
|
)
|
|
update_chart_wizard_id = fields.Many2one(
|
|
comodel_name="wizard.update.charts.accounts",
|
|
string="Update chart wizard",
|
|
required=True,
|
|
ondelete="cascade",
|
|
)
|
|
type = fields.Selection(
|
|
selection=[
|
|
("new", "New template"),
|
|
("updated", "Updated template"),
|
|
("deleted", "Tax to deactivate"),
|
|
],
|
|
readonly=False,
|
|
)
|
|
type_tax_use = fields.Selection(related="tax_id.type_tax_use", readonly=True)
|
|
update_tax_id = fields.Many2one(
|
|
comodel_name="account.tax",
|
|
string="Tax to update",
|
|
required=False,
|
|
ondelete="set null",
|
|
)
|
|
notes = fields.Text(readonly=True)
|
|
recreate_xml_ids = fields.Boolean(related="update_chart_wizard_id.recreate_xml_ids")
|
|
|
|
|
|
class WizardUpdateChartsAccountsAccount(models.TransientModel):
|
|
_name = "wizard.update.charts.accounts.account"
|
|
_description = (
|
|
"Account that needs to be updated (new or updated in the " "template)."
|
|
)
|
|
|
|
account_id = fields.Many2one(
|
|
comodel_name="account.account.template",
|
|
string="Account template",
|
|
required=True,
|
|
)
|
|
update_chart_wizard_id = fields.Many2one(
|
|
comodel_name="wizard.update.charts.accounts",
|
|
string="Update chart wizard",
|
|
required=True,
|
|
ondelete="cascade",
|
|
)
|
|
type = fields.Selection(
|
|
selection=[("new", "New template"), ("updated", "Updated template")],
|
|
readonly=False,
|
|
)
|
|
update_account_id = fields.Many2one(
|
|
comodel_name="account.account",
|
|
string="Account to update",
|
|
required=False,
|
|
ondelete="set null",
|
|
)
|
|
notes = fields.Text(readonly=True)
|
|
recreate_xml_ids = fields.Boolean(related="update_chart_wizard_id.recreate_xml_ids")
|
|
|
|
|
|
class WizardUpdateChartsAccountsFiscalPosition(models.TransientModel):
|
|
_name = "wizard.update.charts.accounts.fiscal.position"
|
|
_description = (
|
|
"Fiscal position that needs to be updated (new or updated " "in the template)."
|
|
)
|
|
|
|
fiscal_position_id = fields.Many2one(
|
|
comodel_name="account.fiscal.position.template",
|
|
string="Fiscal position template",
|
|
required=True,
|
|
)
|
|
update_chart_wizard_id = fields.Many2one(
|
|
comodel_name="wizard.update.charts.accounts",
|
|
string="Update chart wizard",
|
|
required=True,
|
|
ondelete="cascade",
|
|
)
|
|
type = fields.Selection(
|
|
selection=[("new", "New template"), ("updated", "Updated template")],
|
|
readonly=False,
|
|
)
|
|
update_fiscal_position_id = fields.Many2one(
|
|
comodel_name="account.fiscal.position",
|
|
required=False,
|
|
string="Fiscal position to update",
|
|
ondelete="set null",
|
|
)
|
|
notes = fields.Text(readonly=True)
|
|
recreate_xml_ids = fields.Boolean(
|
|
string="Recreate missing XML-IDs",
|
|
related="update_chart_wizard_id.recreate_xml_ids",
|
|
)
|
|
|
|
|
|
class WizardMatching(models.TransientModel):
|
|
_name = "wizard.matching"
|
|
_description = "Wizard Matching"
|
|
_order = "sequence"
|
|
|
|
update_chart_wizard_id = fields.Many2one(
|
|
comodel_name="wizard.update.charts.accounts",
|
|
string="Update chart wizard",
|
|
required=True,
|
|
ondelete="cascade",
|
|
)
|
|
sequence = fields.Integer(required=True, default=1)
|
|
matching_value = fields.Selection(selection="_get_matching_selection")
|
|
|
|
def _get_matching_selection(self):
|
|
return [("xml_id", "XML-ID")]
|
|
|
|
def _selection_from_files(self, model_name, field_opts):
|
|
result = []
|
|
for opt in field_opts:
|
|
model = self.env[model_name]
|
|
desc = model._fields[opt].get_description(self.env)["string"]
|
|
result.append((opt, "{} ({})".format(desc, opt)))
|
|
return result
|
|
|
|
|
|
class WizardTaxMatching(models.TransientModel):
|
|
_name = "wizard.tax.matching"
|
|
_description = "Wizard Tax Matching"
|
|
_inherit = "wizard.matching"
|
|
|
|
def _get_matching_selection(self):
|
|
vals = super(WizardTaxMatching, self)._get_matching_selection()
|
|
vals += self._selection_from_files(
|
|
"account.tax.template", ["description", "name"]
|
|
)
|
|
return vals
|
|
|
|
|
|
class WizardAccountMatching(models.TransientModel):
|
|
_name = "wizard.account.matching"
|
|
_description = "Wizard Account Matching"
|
|
_inherit = "wizard.matching"
|
|
|
|
def _get_matching_selection(self):
|
|
vals = super(WizardAccountMatching, self)._get_matching_selection()
|
|
vals += self._selection_from_files("account.account.template", ["code", "name"])
|
|
return vals
|
|
|
|
|
|
class WizardFpMatching(models.TransientModel):
|
|
_name = "wizard.fp.matching"
|
|
_description = "Wizard Fiscal Position Matching"
|
|
_inherit = "wizard.matching"
|
|
|
|
def _get_matching_selection(self):
|
|
vals = super(WizardFpMatching, self)._get_matching_selection()
|
|
vals += self._selection_from_files("account.fiscal.position.template", ["name"])
|
|
return vals
|