9e63f90527
With this option, you can select which fields do you want to compare for updating. Use case: you have specific accounts in taxes, and you don't want to lose them, but you want to update their groups through the wizard. Before this, the update was all or nothing. Now, you have total control!
853 lines
34 KiB
Python
853 lines
34 KiB
Python
# © 2010 Jordi Esteve, Zikzakmedia S.L. (http://www.zikzakmedia.com)
|
|
# © 2010 Pexego Sistemas Informáticos S.L.(http://www.pexego.es)
|
|
# Borja López Soilán
|
|
# © 2013 Joaquin Gutierrez (http://www.gutierrezweb.es)
|
|
# © 2015 Antonio Espinosa <antonioea@tecnativa.com>
|
|
# © 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
|
# © 2016 Jacques-Etienne Baudoux <je@bcim.be>
|
|
# Copyright 2018 Tecnativa - Pedro M. Baeza
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
|
|
from odoo import _, api, exceptions, fields, models, tools
|
|
from odoo.tools import config
|
|
from contextlib import closing
|
|
from io import StringIO
|
|
import logging
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
EXCEPTION_TEXT = "Traceback (most recent call last)"
|
|
|
|
|
|
class WizardUpdateChartsAccounts(models.TransientModel):
|
|
_name = '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,
|
|
ondelete='set null', 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(
|
|
string='# of digits', required=True,
|
|
help="No. of digits to use for account code. "
|
|
"Make sure it is the same number as existing accounts.")
|
|
lang = fields.Selection(
|
|
lambda self: self._get_lang_selection_options(), 'Language', size=5,
|
|
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_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(
|
|
string="Continue on errors", default=False,
|
|
help="If set, the wizard will continue to the next step even if "
|
|
"there are minor errors.")
|
|
tax_ids = fields.One2many(
|
|
comodel_name='wizard.update.charts.accounts.tax', ondelete='cascade',
|
|
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',
|
|
ondelete='cascade')
|
|
fiscal_position_ids = fields.One2many(
|
|
comodel_name='wizard.update.charts.accounts.fiscal.position',
|
|
inverse_name='update_chart_wizard_id', string='Fiscal positions',
|
|
ondelete='cascade')
|
|
new_taxes = fields.Integer(
|
|
string='New taxes', compute="_compute_new_taxes_count")
|
|
new_accounts = fields.Integer(
|
|
string='New accounts',
|
|
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(
|
|
string='Updated taxes',
|
|
compute="_compute_updated_taxes_count")
|
|
rejected_updated_account_number = fields.Integer()
|
|
updated_accounts = fields.Integer(
|
|
string='Updated accounts',
|
|
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(),
|
|
)
|
|
|
|
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()
|
|
)]
|
|
|
|
@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.multi
|
|
@api.depends("chart_template_id")
|
|
def _compute_chart_template_ids(self):
|
|
self.chart_template_ids = (
|
|
self.env['wizard.multi.charts.accounts']
|
|
._get_chart_parent_ids(self.chart_template_id))
|
|
|
|
@api.multi
|
|
@api.depends('tax_ids')
|
|
def _compute_new_taxes_count(self):
|
|
self.new_taxes = len(self.tax_ids.filtered(lambda x: x.type == 'new'))
|
|
|
|
@api.multi
|
|
@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.multi
|
|
@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.multi
|
|
@api.depends('tax_ids')
|
|
def _compute_updated_taxes_count(self):
|
|
self.updated_taxes = len(
|
|
self.tax_ids.filtered(lambda x: x.type == 'updated'))
|
|
|
|
@api.multi
|
|
@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.multi
|
|
@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.multi
|
|
@api.depends('tax_ids')
|
|
def _compute_deleted_taxes_count(self):
|
|
self.deleted_taxes = len(
|
|
self.tax_ids.filtered(lambda x: x.type == 'deleted'))
|
|
|
|
@api.multi
|
|
@api.onchange("company_id")
|
|
def _onchage_company_update_chart_template(self):
|
|
self.chart_template_id = self.company_id.chart_template_id
|
|
|
|
@api.model
|
|
def _get_code_digits(self, company=None):
|
|
"""Returns the number of digits for the accounts, fetched from
|
|
the company.
|
|
"""
|
|
if company is None:
|
|
company = self.env.user.company_id
|
|
return company.accounts_code_digits or 6
|
|
|
|
@api.onchange('company_id')
|
|
def onchange_company_id(self):
|
|
"""Update the code digits when the company changes"""
|
|
self.code_digits = self._get_code_digits(company=self.company_id)
|
|
|
|
@api.multi
|
|
def _reopen(self):
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'view_mode': 'form',
|
|
'view_type': '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,
|
|
},
|
|
}
|
|
|
|
@api.multi
|
|
def action_init(self):
|
|
"""Initial action that sets the initial state."""
|
|
self.state = 'init'
|
|
return self._reopen()
|
|
|
|
@api.multi
|
|
def action_find_records(self):
|
|
"""Searchs for records to update/create and shows them."""
|
|
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()
|
|
|
|
@api.multi
|
|
def action_update_records(self):
|
|
"""Action that creates/updates/deletes the selected elements."""
|
|
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:
|
|
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()
|
|
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.Warning(
|
|
_("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()
|
|
|
|
@api.multi
|
|
@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 = self.env['account.tax'].with_context(active_test=False)
|
|
result = Tax
|
|
for template in templates:
|
|
single = Tax
|
|
criteria = (
|
|
("name", "=", template.name),
|
|
("description", "=", template.name),
|
|
("name", "=", template.description),
|
|
("description", "=", template.description),
|
|
)
|
|
for domain in criteria:
|
|
if single:
|
|
break
|
|
if domain[2]:
|
|
single = Tax.search(
|
|
[domain,
|
|
("company_id", "=", self.company_id.id),
|
|
("type_tax_use", "=", template.type_tax_use)],
|
|
limit=1)
|
|
result |= single
|
|
return result[:1].id
|
|
|
|
@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')
|
|
|
|
@api.multi
|
|
@tools.ormcache("templates")
|
|
def find_account_by_templates(self, templates):
|
|
"""Find an account that matches the template."""
|
|
return self.env['account.account'].search(
|
|
[('code', 'in',
|
|
list(map(self.padded_code, templates.mapped("code")))),
|
|
('company_id', '=', self.company_id.id)],
|
|
).id
|
|
|
|
@api.multi
|
|
@tools.ormcache("templates")
|
|
def find_fp_by_templates(self, templates):
|
|
"""Find a real fiscal position from a template."""
|
|
return self.env['account.fiscal.position'].search(
|
|
[('name', 'in', templates.mapped("name")),
|
|
('company_id', '=', self.company_id.id)], limit=1).id
|
|
|
|
@api.multi
|
|
@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
|
|
|
|
@api.multi
|
|
@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": {
|
|
"chart_template_id",
|
|
"code",
|
|
},
|
|
"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):
|
|
"""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)
|
|
to_include = []
|
|
if template._name == 'account.tax.template':
|
|
to_include = self.tax_field_ids.mapped('name')
|
|
elif template._name == 'account.account.template':
|
|
to_include = self.account_field_ids.mapped('name')
|
|
elif template._name == 'account.fiscal.position.template':
|
|
to_include = self.fp_field_ids.mapped('name')
|
|
for key, field in template._fields.items():
|
|
if key in ignore or key not in to_include:
|
|
continue
|
|
expected = t = None
|
|
# Translate template records to reals for comparison
|
|
relation = field.get_description(self.env).get("relation", "")
|
|
if relation:
|
|
if ".tax.template" in relation:
|
|
t = "tax"
|
|
elif ".account.template" in relation:
|
|
t = "account"
|
|
if t:
|
|
find = getattr(
|
|
self,
|
|
"find_%s%s_by_templates" % (
|
|
"fp_" if ".fiscal.position" in relation
|
|
else "",
|
|
t))
|
|
if ".fiscal.position" in relation:
|
|
# Special case: if something is returned, then
|
|
# there's any difference, so it will get non equal
|
|
# when comparing, although we get the warning
|
|
# "Comparing apples with oranges"
|
|
expected = find(template[key], real[key])
|
|
else:
|
|
exp_id = find(template[key])
|
|
expected = self.env[relation[:-9]].browse(exp_id)
|
|
# Register detected differences
|
|
try:
|
|
if expected is not None:
|
|
if expected != [] and expected != real[key]:
|
|
result[key] = expected
|
|
elif template[key] != real[key]:
|
|
result[key] = template[key]
|
|
# Avoid to cache recordset references
|
|
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
|
|
except KeyError:
|
|
pass
|
|
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)
|
|
|
|
@api.multi
|
|
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 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(
|
|
[('company_id', '=', self.company_id.id),
|
|
("id", "not in", found_taxes_ids),
|
|
("active", "=", True)])
|
|
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"),
|
|
})
|
|
|
|
@api.multi
|
|
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 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,
|
|
})
|
|
|
|
@api.multi
|
|
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 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,
|
|
})
|
|
|
|
@api.multi
|
|
def _update_taxes(self):
|
|
"""Process taxes to create/update/deactivate."""
|
|
for wiz_tax in self.tax_ids:
|
|
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
|
|
# Create tax
|
|
if wiz_tax.type == 'new':
|
|
template._generate_tax(self.company_id)
|
|
_logger.info(_("Created tax %s."), "'%s'" % template.name)
|
|
# Update tax
|
|
else:
|
|
for key, value in self.diff_fields(template, tax).items():
|
|
# We defer update because account might not be created yet
|
|
if key in {'account_id', 'refund_account_id'}:
|
|
continue
|
|
tax[key] = value
|
|
_logger.info(_("Updated tax %s."), "'%s'" % template.name)
|
|
|
|
@api.multi
|
|
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."),
|
|
"'%s - %s'" % (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."),
|
|
"'%s - %s'" % (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."),
|
|
"'%s - %s'" % (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."),
|
|
"'%s - %s'" % (account.code, account.name),
|
|
)
|
|
if not self.continue_on_errors:
|
|
break
|
|
|
|
@api.multi
|
|
def _update_taxes_pending_for_accounts(self):
|
|
"""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).
|
|
"""
|
|
for wiz_tax in self.tax_ids:
|
|
if wiz_tax.type == "deleted" or not wiz_tax.update_tax_id:
|
|
continue
|
|
template = wiz_tax.tax_id
|
|
tax = wiz_tax.update_tax_id
|
|
done = False
|
|
for key, value in self.diff_fields(template, tax).items():
|
|
if key in {'account_id', 'refund_account_id'}:
|
|
tax[key] = value
|
|
done = True
|
|
if done:
|
|
_logger.info(_("Post-updated tax %s."), "'%s'" % tax.name)
|
|
|
|
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],
|
|
}
|
|
|
|
@api.multi
|
|
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),
|
|
)
|
|
else:
|
|
# Update the given fiscal position
|
|
for key, value in self.diff_fields(template, fp).items():
|
|
fp[key] = value
|
|
_logger.info(
|
|
_("Created or updated fiscal position %s."),
|
|
"'%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')],
|
|
string='Type',
|
|
readonly=True)
|
|
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('Notes', readonly=True)
|
|
|
|
|
|
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, 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')],
|
|
string='Type',
|
|
readonly=True)
|
|
update_account_id = fields.Many2one(
|
|
comodel_name='account.account', string='Account to update',
|
|
required=False, ondelete='set null')
|
|
notes = fields.Text('Notes', readonly=True)
|
|
|
|
|
|
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, 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')],
|
|
string='Type', readonly=True, required=True,
|
|
)
|
|
update_fiscal_position_id = fields.Many2one(
|
|
comodel_name='account.fiscal.position', required=False,
|
|
string='Fiscal position to update', ondelete='set null')
|
|
notes = fields.Text('Notes', readonly=True)
|