From ef73f5ad50f6ccf64a936f6d0de27c1a2fae06fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Wed, 24 Dec 2014 00:23:39 +0100 Subject: [PATCH] [FIX+IMP] account_chart_update: Several things * search on code first to match tax codes The tax code code should be more discriminant that the tax code name. * compare more fields in tax codes * deletion of tax codes removed from templates For tax codes, it make sense to propose deletion, since tax codes are normally defined by the tax authority and the user does not customize them. * update accounts on taxes even if an update of the account was not requested Useful when the chart of taxes changes but the chart of account need not be updated. * depth-first search of tax codes This helps creating parents before children. * compare more fields in taxes * add active field on tax code Deactivate tax codes which are not in the template. * deactivate taxes that are not in the template * multi-company criteria for taxes * avoid inserting duplicates in presence of inactive taxes or tax codes --- account_chart_update/__init__.py | 2 + account_chart_update/__openerp__.py | 2 +- account_chart_update/model/__init__.py | 1 + .../model/account_tax_code.py | 27 +++ .../wizard/wizard_chart_update.py | 208 +++++++++++++++--- .../wizard/wizard_chart_update_view.xml | 4 + 6 files changed, 214 insertions(+), 30 deletions(-) create mode 100644 account_chart_update/model/__init__.py create mode 100644 account_chart_update/model/account_tax_code.py diff --git a/account_chart_update/__init__.py b/account_chart_update/__init__.py index d04374b7..4319b421 100644 --- a/account_chart_update/__init__.py +++ b/account_chart_update/__init__.py @@ -23,4 +23,6 @@ """ Account Chart Update Wizard """ + +from . import model from . import wizard diff --git a/account_chart_update/__openerp__.py b/account_chart_update/__openerp__.py index e1f10c21..80afbf9f 100644 --- a/account_chart_update/__openerp__.py +++ b/account_chart_update/__openerp__.py @@ -27,7 +27,7 @@ { 'name': "Detect changes and update the Account Chart from a template", 'version': "1.2", - 'author': "Zikzakmedia SL", + 'author': "Zikzakmedia SL,Odoo Community Association (OCA)", 'website': "www.zikzakmedia.com", 'depends': ["account"], 'category': "Generic Modules/Accounting", diff --git a/account_chart_update/model/__init__.py b/account_chart_update/model/__init__.py new file mode 100644 index 00000000..a7362088 --- /dev/null +++ b/account_chart_update/model/__init__.py @@ -0,0 +1 @@ +from . import account_tax_code diff --git a/account_chart_update/model/account_tax_code.py b/account_chart_update/model/account_tax_code.py new file mode 100644 index 00000000..ae68fd81 --- /dev/null +++ b/account_chart_update/model/account_tax_code.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (c) 2014 ACSONE SA/NV (http://acsone.eu) +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp import models, fields + + +class AccountTaxCode(models.Model): + _inherit = 'account.tax.code' + + active = fields.Boolean('Active', default=True) diff --git a/account_chart_update/wizard/wizard_chart_update.py b/account_chart_update/wizard/wizard_chart_update.py index 891faf0d..14906044 100644 --- a/account_chart_update/wizard/wizard_chart_update.py +++ b/account_chart_update/wizard/wizard_chart_update.py @@ -194,6 +194,14 @@ class wizard_update_charts_accounts(orm.TransientModel): 'Updated fiscal positions', readonly=True ), + 'deleted_tax_codes': fields.integer( + 'Deactivated tax codes', + readonly=True + ), + 'deleted_taxes': fields.integer( + 'Deactivated taxes', + readonly=True + ), 'log': fields.text('Messages and Errors', readonly=True) } @@ -324,6 +332,8 @@ class wizard_update_charts_accounts(orm.TransientModel): """ Adds a tax template -> tax id to the mapping. """ + if context is None: + context = {} if not tax_templ: return False if tax_templ_mapping.get(tax_templ.id): @@ -337,7 +347,11 @@ class wizard_update_charts_accounts(orm.TransientModel): criteria = (['|', '|'] + criteria + [('description', '=', tax_templ.description), ('name', '=', tax_templ.description)]) - tax_ids = tax_obj.search(cr, uid, criteria, context=context) + criteria += [('company_id', '=', wizard.company_id.id)] + # search inactive taxes too, to avoid re-creating + # taxes that have been deactivated before + search_context = dict(context, active_test=False) + tax_ids = tax_obj.search(cr, uid, criteria, context=search_context) tax_templ_mapping[tax_templ.id] = tax_ids and tax_ids[0] or False return tax_templ_mapping[tax_templ.id] @@ -353,24 +367,25 @@ class wizard_update_charts_accounts(orm.TransientModel): return False if tax_code_templ_mapping.get(tax_code_template.id): return tax_code_templ_mapping[tax_code_template.id] - # In other case + # prepare a search context in order to + # search inactive tax codes too, to avoid re-creating + # tax codes that have been deactivated before + search_context = dict(context, active_test=False) tax_code_obj = self.pool['account.tax.code'] root_tax_code_id = wizard.chart_template_id.tax_code_root_id.id - tax_code_name = ((tax_code_template.id == root_tax_code_id) and - wizard.company_id.name or tax_code_template.name) - tax_code_ids = tax_code_obj.search(cr, uid, [ - ('name', '=', tax_code_name), - ('company_id', '=', wizard.company_id.id) - ], context=context) - if not tax_code_ids: - # if we could not match no tax code template name, - # try to match on tax code template code, if any - tax_code_code = tax_code_template.code - if tax_code_code: - tax_code_ids = tax_code_obj.search(cr, uid, [ - ('code', '=', tax_code_code), - ('company_id', '=', wizard.company_id.id) - ], context=context) + tax_code_code = tax_code_template.code + if tax_code_code: + tax_code_ids = tax_code_obj.search(cr, uid, [ + ('code', '=', tax_code_code), + ('company_id', '=', wizard.company_id.id) + ], context=search_context) + if not tax_code_code or not tax_code_ids: + tax_code_name = ((tax_code_template.id == root_tax_code_id) and + wizard.company_id.name or tax_code_template.name) + tax_code_ids = tax_code_obj.search(cr, uid, [ + ('name', '=', tax_code_name), + ('company_id', '=', wizard.company_id.id) + ], context=search_context) tax_code_templ_mapping[tax_code_template.id] = (tax_code_ids and tax_code_ids[0] or False) @@ -416,6 +431,21 @@ class wizard_update_charts_accounts(orm.TransientModel): fp_templ_mapping[fp_template.id] = fp_ids and fp_ids[0] or False return fp_templ_mapping[fp_template.id] + def _get_depth_first_tax_code_template_ids(self, cr, uid, root_tax_code_id, + context=None): + tax_code_templ_obj = self.pool['account.tax.code.template'] + + def get_children(tct): + for child in tct.child_ids: + res.append(child.id) + get_children(child) + + tct = tax_code_templ_obj.browse(cr, uid, root_tax_code_id, + context=context) + res = [tct.id] + get_children(tct) + return res + def _find_tax_codes(self, cr, uid, wizard, chart_template_ids, context=None): """ @@ -436,9 +466,9 @@ class wizard_update_charts_accounts(orm.TransientModel): wiz_tax_code_obj.unlink(cr, uid, wiz_tax_code_obj.search(cr, uid, [])) # Search for new / updated tax codes root_tax_code_id = wizard.chart_template_id.tax_code_root_id.id - children_tax_code_template = tax_code_templ_obj.search(cr, uid, [( - 'parent_id', 'child_of', [root_tax_code_id])], order='id', - context=context) + children_tax_code_template = \ + self._get_depth_first_tax_code_template_ids( + cr, uid, root_tax_code_id, context=context) for tax_code_template in tax_code_templ_obj.browse( cr, uid, children_tax_code_template, context=context): @@ -463,6 +493,9 @@ class wizard_update_charts_accounts(orm.TransientModel): notes = "" tax_code = tax_code_obj.browse( cr, uid, tax_code_id, context=context) + if tax_code.name != tax_code_template.name: + notes += _("The name field is different.\n") + modified = True if tax_code.code != tax_code_template.code: notes += _("The code field is different.\n") modified = True @@ -472,7 +505,18 @@ class wizard_update_charts_accounts(orm.TransientModel): if tax_code.sign != tax_code_template.sign: notes += _("The sign field is different.\n") modified = True - # TODO: We could check other account fields for changes... + if tax_code.notprintable != tax_code_template.notprintable: + notes += _("The notprintable field is different.\n") + modified = True + if tax_code.sequence != tax_code_template.sequence: + notes += _("The sequence field is different.\n") + modified = True + if tax_code.parent_id.id != self._map_tax_code_template( + cr, uid, wizard, + tax_code_template_mapping, + tax_code_template.parent_id, context=context): + notes += _("The parent field is different.\n") + modified = True if modified: # Tax code to update. updated_tax_codes += 1 @@ -483,6 +527,24 @@ class wizard_update_charts_accounts(orm.TransientModel): 'update_tax_code_id': tax_code_id, 'notes': notes, }, context) + # search for tax codes not in the template + # and propose them for deletion + tax_code_ids = tax_code_obj.\ + search(cr, uid, [('company_id', '=', wizard.company_id.id)], + context=context) + tax_code_ids = set(tax_code_ids) + template_tax_code_ids = set(tax_code_template_mapping.values()) + tax_code_ids_to_delete = tax_code_ids - template_tax_code_ids + for tax_code_id in tax_code_ids_to_delete: + updated_tax_codes += 1 + wiz_tax_code_obj.create(cr, uid, { + 'tax_code_id': False, + 'update_chart_wizard_id': wizard.id, + 'type': 'deleted', + 'update_tax_code_id': tax_code_id, + 'notes': "To deactivate: not in the template", + }, context) + return { 'new': new_tax_codes, 'updated': updated_tax_codes, @@ -499,6 +561,8 @@ class wizard_update_charts_accounts(orm.TransientModel): new_taxes = 0 updated_taxes = 0 tax_templ_mapping = {} + tax_code_template_mapping = {} + acc_templ_mapping = {} tax_obj = self.pool['account.tax'] tax_templ_obj = self.pool['account.tax.template'] wiz_taxes_obj = self.pool['wizard.update.charts.accounts.tax'] @@ -569,6 +633,46 @@ class wizard_update_charts_accounts(orm.TransientModel): if tax.type_tax_use != tax_templ.type_tax_use: notes += _("The type tax use field is different.\n") modified = True + # compare tax code fields + if tax.base_code_id.id != self._map_tax_code_template( + cr, uid, wizard, + tax_code_template_mapping, + tax_templ.base_code_id, context=context): + notes += _("The base_code_id field is different.\n") + modified = True + if tax.tax_code_id.id != self._map_tax_code_template( + cr, uid, wizard, + tax_code_template_mapping, + tax_templ.tax_code_id, context=context): + notes += _("The tax_code_id field is different.\n") + modified = True + if tax.ref_base_code_id.id != self._map_tax_code_template( + cr, uid, wizard, + tax_code_template_mapping, + tax_templ.ref_base_code_id, context=context): + notes += _("The ref_base_code_id field is different.\n") + modified = True + if tax.ref_tax_code_id.id != self._map_tax_code_template( + cr, uid, wizard, + tax_code_template_mapping, + tax_templ.ref_tax_code_id, context=context): + notes += _("The ref_tax_code_id field is different.\n") + modified = True + # compare tax account fields + if tax.account_paid_id.id != self._map_account_template( + cr, uid, wizard, + acc_templ_mapping, + tax_templ.account_paid_id, + context=context): + notes += _("The account_paid field is different.\n") + modified = True + if tax.account_collected_id.id != self._map_account_template( + cr, uid, wizard, + acc_templ_mapping, + tax_templ.account_collected_id, + context=context): + notes += _("The account_collected field is different.\n") + modified = True # TODO: We could check other tax fields for changes... if modified: # Tax code to update. @@ -582,6 +686,23 @@ class wizard_update_charts_accounts(orm.TransientModel): }, context) for delay_vals_wiz in delay_wiz_tax: wiz_taxes_obj.create(cr, uid, delay_vals_wiz, context) + # search for taxes not in the template + # and propose them for deletion + tax_ids = tax_obj.\ + search(cr, uid, [('company_id', '=', wizard.company_id.id)], + context=context) + tax_ids = set(tax_ids) + template_tax_ids = set(tax_templ_mapping.values()) + tax_ids_to_delete = tax_ids - template_tax_ids + for tax_id in tax_ids_to_delete: + updated_taxes += 1 + wiz_taxes_obj.create(cr, uid, { + 'tax_id': False, + 'update_chart_wizard_id': wizard.id, + 'type': 'deleted', + 'update_tax_id': tax_id, + 'notes': "To deactivate: not in the template", + }, context) return {'new': new_taxes, 'updated': updated_taxes, @@ -848,7 +969,10 @@ class wizard_update_charts_accounts(orm.TransientModel): new_tax_codes = 0 updated_tax_codes = 0 tax_code_template_mapping = {} + # process new/updated for wiz_tax_code in wizard.tax_code_ids: + if wiz_tax_code.type == 'deleted': + continue tax_code_template = wiz_tax_code.tax_code_id tax_code_name = ((root_tax_code_id == tax_code_template.id) and wizard.company_id.name or tax_code_template.name) @@ -867,6 +991,8 @@ class wizard_update_charts_accounts(orm.TransientModel): tax_code_template_mapping.get(p_id)), 'company_id': wizard.company_id.id, 'sign': tax_code_template.sign, + 'notprintable': tax_code_template.notprintable, + 'sequence': tax_code_template.sequence, } tax_code_id = None modified = False @@ -900,9 +1026,19 @@ class wizard_update_charts_accounts(orm.TransientModel): tax_code_name, tax_code_template.parent_id.name), True ) + # process deleted + tax_code_ids_to_delete = [wtc.update_tax_code_id.id + for wtc in wizard.tax_code_ids + if wtc.type == 'deleted'] + taxcodes.write(cr, uid, tax_code_ids_to_delete, + {'active': False}, + context=context) + log.add(_("Deactivated %d tax codes\n" % len(tax_code_ids_to_delete))) + deleted_tax_codes = len(tax_code_ids_to_delete) return { 'new': new_tax_codes, 'updated': updated_tax_codes, + 'deleted': deleted_tax_codes, 'mapping': tax_code_template_mapping } @@ -917,6 +1053,8 @@ class wizard_update_charts_accounts(orm.TransientModel): tax_template_mapping = {} taxes_pending_for_accounts = {} for wiz_tax in wizard.tax_ids: + if wiz_tax.type == 'deleted': + continue tax_template = wiz_tax.tax_id # Ensure the parent tax template is on the map. self._map_tax_template(cr, uid, wizard, tax_template_mapping, @@ -1058,9 +1196,19 @@ class wizard_update_charts_accounts(orm.TransientModel): ), True ) + # process deleted + tax_ids_to_delete = [wtc.update_tax_id.id + for wtc in wizard.tax_ids + if wtc.type == 'deleted'] + taxes.write(cr, uid, tax_ids_to_delete, + {'active': False}, + context=context) + log.add(_("Deactivated %d taxes\n" % len(tax_ids_to_delete))) + deleted_taxes = len(tax_ids_to_delete) return { 'new': new_taxes, 'updated': updated_taxes, + 'deleted': deleted_taxes, 'mapping': tax_template_mapping, 'pending': taxes_pending_for_accounts } @@ -1157,9 +1305,9 @@ class wizard_update_charts_accounts(orm.TransientModel): 'shortcut': account_template.shortcut, 'note': account_template.note, 'parent_id': ( - account_template.parent_id - and account_template_mapping.get(p_id) or - False + account_template_mapping.get(p_id) + if account_template.parent_id + else False ), 'tax_ids': [(6, 0, tax_ids)], 'company_id': wizard.company_id.id, @@ -1436,7 +1584,7 @@ class wizard_update_charts_accounts(orm.TransientModel): accounts_res = self._update_accounts(cr, uid, wizard, log, taxes_res['mapping'], context=context) - if wizard.update_tax and wizard.update_account: + if wizard.update_tax: self._update_taxes_pending_for_accounts(cr, uid, wizard, log, taxes_res['pending'], accounts_res['mapping'], @@ -1461,6 +1609,8 @@ class wizard_update_charts_accounts(orm.TransientModel): 'updated_taxes': taxes_res.get('updated', 0), 'updated_accounts': accounts_res.get('updated', 0), 'updated_fps': fps_res.get('updated', 0), + 'deleted_tax_codes': tax_codes_res.get('deleted', 0), + 'deleted_taxes': taxes_res.get('deleted', 0), 'log': log(), }, context=context) return _reopen(self, wizard.id, 'wizard.update.chart.accounts') @@ -1477,7 +1627,6 @@ class wizard_update_charts_accounts_tax_code(orm.TransientModel): 'tax_code_id': fields.many2one( 'account.tax.code.template', 'Tax code template', - required=True, ondelete='set null' ), 'update_chart_wizard_id': fields.many2one( @@ -1487,8 +1636,9 @@ class wizard_update_charts_accounts_tax_code(orm.TransientModel): ondelete='cascade' ), 'type': fields.selection([ - ('new', 'New template'), - ('updated', 'Updated template'), + ('new', 'New tax code'), + ('updated', 'Updated tax code'), + ('deleted', 'Tax code to deactivate'), ], 'Type'), 'update_tax_code_id': fields.many2one( 'account.tax.code', @@ -1513,7 +1663,6 @@ class wizard_update_charts_accounts_tax(orm.TransientModel): 'tax_id': fields.many2one( 'account.tax.template', 'Tax template', - required=True, ondelete='set null' ), 'update_chart_wizard_id': fields.many2one( @@ -1525,6 +1674,7 @@ class wizard_update_charts_accounts_tax(orm.TransientModel): 'type': fields.selection([ ('new', 'New template'), ('updated', 'Updated template'), + ('deleted', 'Tax to deactivate'), ], 'Type'), 'update_tax_id': fields.many2one( 'account.tax', diff --git a/account_chart_update/wizard/wizard_chart_update_view.xml b/account_chart_update/wizard/wizard_chart_update_view.xml index 91c7927d..f4f1586e 100644 --- a/account_chart_update/wizard/wizard_chart_update_view.xml +++ b/account_chart_update/wizard/wizard_chart_update_view.xml @@ -113,6 +113,10 @@ + + + +