Files
account-financial-tools/account_tax_update/model/update_tax_config.py

522 lines
21 KiB
Python

# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2012 Therp BV (<http://therp.nl>).
# This module copyright (C) 2013 Camptocamp (<http://www.camptocamp.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from datetime import datetime
import pickle
from openerp.osv import orm, fields
from openerp.tools.translate import _
class UpdateTaxConfig(orm.Model):
"""
A configuration model to collect taxes to be replaced with
duplicates, but with a different amount. Once the taxes are
collected, the following operations can be carried out by
the user.
1) generate the target taxes
2) Update defaults for sales taxes
3) Update defaults for purchase taxes
4) Set old taxes inactive
"""
_name = 'account.update.tax.config'
_description = 'Update taxes'
_columns = {
'name': fields.char(
'Legacy taxes prefix', size=64, required=True,
help="The processed taxes will be marked with this name"),
'log': fields.text(
'Log', readonly="1"),
'purchase_line_ids': fields.one2many(
'account.update.tax.config.line',
'purchase_config_id',
'Purchase taxes'),
'sale_line_ids': fields.one2many(
'account.update.tax.config.line',
'sale_config_id',
'Sales taxes'),
'state': fields.selection(
[('draft', 'Draft'),
('confirm', 'Confirm'),
('update_sales', 'Sales updated'),
('update_purchase', 'Purchase updated'),
('done', 'Done'),
], 'State', readonly=True),
'default_amount': fields.float(
'Default new amount', digits=(14, 4),
help=("Although it is possible to specify a distinct new amount "
"per tax, you can set the default value here.")),
'sale_set_defaults': fields.boolean(
'Sales tax defaults have been set',
readonly=True),
'purchase_set_defaults': fields.boolean(
'Purchase tax defaults have been set',
readonly=True),
'sale_set_inactive': fields.boolean(
'Sales taxes have been set to inactive',
readonly=True),
'purchase_set_inactive': fields.boolean(
'Purchase taxes have been set to inactive',
readonly=True),
'duplicate_tax_code': fields.boolean(
'Duplicate Tax code linked'),
}
_defaults = {
'state': 'draft',
}
_sql_constraints = [
('name_uniq', 'unique(name)', 'Name must be unique.'),
]
def add_lines(self, cr, uid, ids, context=None):
"""
Call the wizard that adds configuration lines
"""
if not ids:
return
if isinstance(ids, (int, float)):
ids = [ids]
wizard_obj = self.pool.get('account.update.tax.select_taxes')
config = self.browse(cr, uid, ids[0], context=context)
if not context or not context.get('type_tax_use'):
raise orm.except_orm(
_("Error"),
_("Can not detect tax use type"))
covered_tax_ids = [
x.source_tax_id.id
for x in config['purchase_line_ids'] + config['sale_line_ids']
]
res_id = wizard_obj.create(
cr, uid, {
'config_id': ids[0],
'type_tax_use': context['type_tax_use'],
'covered_tax_ids': [(6, 0, covered_tax_ids)],
},
context=context)
local_context = context.copy()
local_context['active_id'] = res_id
return {
'name': wizard_obj._description,
'view_type': 'form',
'view_mode': 'form',
'res_model': wizard_obj._name,
'domain': [],
'context': context,
'type': 'ir.actions.act_window',
'target': 'new',
'res_id': res_id,
'nodestroy': True,
}
def confirm(self, cr, uid, ids, context=None):
"""
Set the configuration to confirmed, so that no new
taxes can be added. Create the duplicate taxes,
rename the legacy taxes and recreate the hierarchical
structure. Construct the fiscal position tax mappings.
"""
config = self.browse(cr, uid, ids[0], context=None)
tax_pool = self.pool.get('account.tax')
tax_code_pool = self.pool.get('account.tax.code')
line_pool = self.pool.get('account.update.tax.config.line')
tax_map = {}
log = (config.log or '') + (
"\n*** %s: Confirmed with the following taxes:\n" %
datetime.now().ctime())
for line in config.sale_line_ids + config.purchase_line_ids:
log += " - %s (%s)\n" % (
line.source_tax_id.name,
line.source_tax_id.description
)
# Switch names around, not violating the uniqueness constraint
tax_old_name = line.source_tax_id.name
tax_pool.write(
cr, uid, line.source_tax_id.id,
{'name': '[%s] %s' % (config.name, tax_old_name)},
context=context)
if line.source_tax_id.amount in [1.0, -1.0, 0]:
amount_new = line.source_tax_id.amount
else:
amount_new = config.default_amount or line.source_tax_id.amount
# 6.0 messes up the name change with copy + write, while
# 6.1 throws name uniqueness constraint violation
# So jumping some hoops with rewriting the new name
# We will check if we need to dupliace
cp_base_code_id = False
cp_ref_base_code_id = False
cp_tax_code_id = False
cp_ref_tax_code_id = False
if config.duplicate_tax_code:
if line.source_tax_id.base_code_id:
cp_base_code_id = tax_code_pool.copy(
cr, uid,
line.source_tax_id.base_code_id.id
)
rename_old = '[%s] %s' % (
config.name,
line.source_tax_id.base_code_id.name
)
tax_code_pool.write(cr, uid,
line.source_tax_id.base_code_id.id,
{'name': rename_old})
if line.source_tax_id.tax_code_id:
cp_tax_code_id = tax_code_pool.copy(
cr, uid,
line.source_tax_id.tax_code_id.id
)
rename_old = '[%s] %s' % (
config.name,
line.source_tax_id.tax_code_id.name
)
tax_code_pool.write(cr, uid,
line.source_tax_id.tax_code_id.id,
{'name': rename_old})
if line.source_tax_id.ref_base_code_id:
# Check if with have the same tax code for base_code_id
if (line.source_tax_id.ref_base_code_id.id ==
line.source_tax_id.base_code_id.id):
cp_ref_base_code_id = cp_base_code_id
else:
cp_ref_base_code_id = tax_code_pool.copy(
cr, uid,
line.source_tax_id.ref_base_code_id.id
)
rename_old = '[%s] %s' % (
config.name,
line.source_tax_id.ref_base_code_id.name
)
tax_code_pool.write(
cr, uid,
line.source_tax_id.ref_base_code_id.id,
{'name': rename_old}
)
if line.source_tax_id.ref_tax_code_id:
if (line.source_tax_id.ref_tax_code_id.id ==
line.source_tax_id.tax_code_id.id):
cp_ref_tax_code_id = cp_tax_code_id
else:
cp_ref_tax_code_id = tax_code_pool.copy(
cr, uid,
line.source_tax_id.ref_tax_code_id.id
)
rename_old = '[%s] %s' % (
config.name,
line.source_tax_id.ref_tax_code_id.name
)
tax_code_pool.write(
cr, uid,
line.source_tax_id.ref_tax_code_id.id,
{'name': rename_old}
)
else:
cp_base_code_id = (line.source_tax_id.base_code_id and
line.source_tax_id.base_code_id.id or
False)
cp_ref_base_code_id = (
line.source_tax_id.ref_base_code_id and
line.source_tax_id.ref_base_code_id.id or
False
)
cp_tax_code_id = (line.source_tax_id.tax_code_id and
line.source_tax_id.tax_code_id.id or
False)
cp_ref_tax_code_id = (line.source_tax_id.ref_tax_code_id and
line.source_tax_id.ref_tax_code_id.id or
False)
target_tax_id = tax_pool.copy(
cr, uid, line.source_tax_id.id,
{
'name': '[update, %s] %s' % (config.name, tax_old_name),
'amount': amount_new,
'parent_id': False,
'child_ids': [(6, 0, [])],
},
context=context
)
tax_pool.write(
cr, uid, target_tax_id,
{'name': tax_old_name,
'base_code_id': cp_base_code_id,
'ref_base_code_id': cp_ref_base_code_id,
'tax_code_id': cp_tax_code_id,
'ref_tax_code_id': cp_ref_tax_code_id},
context=context
)
tax_map[line.source_tax_id.id] = target_tax_id
line_pool.write(
cr, uid, line.id,
{'target_tax_id': target_tax_id}, context=context)
# Map the parent_id
# First, rebrowse the config
# (as browse_record.refresh() is not available in 6.0)
config = self.browse(cr, uid, ids[0], context=None)
for line in config.sale_line_ids + config.purchase_line_ids:
if line.source_tax_id.parent_id:
tax_pool.write(
cr, uid, line.target_tax_id.id,
{'parent_id': tax_map[line.source_tax_id.parent_id.id]},
context=context)
# Map fiscal positions
fp_tax_pool = self.pool.get('account.fiscal.position.tax')
fp_tax_ids = fp_tax_pool.search(
cr, uid, [('tax_src_id', 'in', tax_map.keys())], context=context)
fp_taxes = fp_tax_pool.browse(cr, uid, fp_tax_ids, context=context)
for fp_tax in fp_taxes:
new_fp_tax_id = fp_tax_pool.copy(
cr, uid, fp_tax.id,
{'tax_src_id': tax_map[fp_tax.tax_src_id.id],
'tax_dest_id': tax_map.get(
fp_tax.tax_dest_id.id, fp_tax.tax_dest_id.id)},
context=context)
new_fp_tax = fp_tax_pool.browse(
cr, uid, new_fp_tax_id, context=context)
log += ("\nCreate new tax mapping on position %s:\n"
"%s (%s)\n"
"=> %s (%s)\n" % (
new_fp_tax.position_id.name,
new_fp_tax.tax_src_id.name,
new_fp_tax.tax_src_id.description,
new_fp_tax.tax_dest_id.name,
new_fp_tax.tax_dest_id.description,
))
self.write(
cr, uid, ids[0],
{'state': 'confirm', 'log': log}, context=context)
return {
'name': self._description,
'view_type': 'form',
'view_mode': 'form',
'res_model': self._name,
'domain': [],
'context': context,
'type': 'ir.actions.act_window',
'res_id': ids[0],
'nodestroy': True,
}
def set_defaults(self, cr, uid, ids, context=None):
if not context or not context.get('type_tax_use'):
raise orm.except_orm(
_("Error"),
_("Can not detect tax use type"))
local_context = context.copy()
local_context['active_test'] = False
config = self.browse(cr, uid, ids[0], context=None)
tax_lines = config['%s_line_ids' % context['type_tax_use']]
tax_map = dict([(x.source_tax_id.id, x.target_tax_id.id)
for x in tax_lines])
ir_values_pool = self.pool.get('ir.values')
log = (config.log or '') + (
"\n*** %s: Writing default %s taxes:\n" % (
datetime.now().ctime(),
context['type_tax_use']))
def update_defaults(model_name, field_name, column):
log = ''
if column._obj == 'account.tax':
values_ids = ir_values_pool.search(
cr, uid,
[('key', '=', 'default'),
('model', '=', model_name),
('name', '=', field_name)],
context=local_context)
for value in ir_values_pool.browse(
cr, uid, values_ids, context=context):
val = False
write = False
try:
# Currently, value_unpickle from ir_values
# fails as it feeds unicode to pickle.loads()
val = pickle.loads(str(value.value))
except:
continue
if isinstance(val, (int, long)) and val in tax_map:
write = True
new_val = tax_map[val]
elif isinstance(val, list) and val:
new_val = []
for i in val:
if i in tax_map:
write = True
new_val.append(tax_map.get(i, i))
if write:
log += "Default (%s => %s) for %s,%s\n" % (
val, new_val, model_name, field_name)
ir_values_pool.write(
cr, uid, value.id,
{'value_unpickle': new_val}, context=context)
return log
model_pool = self.pool.get('ir.model')
model_ids = model_pool.search(cr, uid, [], context=context)
models = model_pool.read(
cr, uid, model_ids, ['model'], context=context)
pool_models_items = [(x['model'], self.pool.get(
x['model'])) for x in models]
# 6.1: self.pool.models.items():
for model_name, model in pool_models_items:
if model:
for field_name, column in model._columns.items():
log += update_defaults(model_name, field_name, column)
for field_name, field_tuple in \
model._inherit_fields.iteritems():
if len(field_tuple) >= 3:
column = field_tuple[2]
log += update_defaults(model_name, field_name, column)
log += "\nReplacing %s taxes on accounts and products\n" % (
context['type_tax_use'])
for (model, field) in [
# make this a configurable list of ir_model_fields one day?
('account.account', 'tax_ids'),
('product.product', 'supplier_taxes_id'),
('product.product', 'taxes_id'),
('product.template', 'supplier_taxes_id'),
('product.template', 'taxes_id')]:
pool = self.pool.get(model)
obj_ids = pool.search(
cr, uid, [(field, 'in', tax_map.keys())],
context=local_context)
for obj in pool.read(
cr, uid, obj_ids, [field], context=context):
new_val = []
write = False
for i in obj[field]:
if i in tax_map:
write = True
new_val.append(tax_map.get(i, i))
if write:
pool.write(
cr, uid, obj['id'],
{field: [(6, 0, new_val)]},
context=context)
log += "Value (%s => %s) for %s,%s,%s\n" % (
obj[field], new_val, model, field, obj['id'])
self.write(
cr, uid, ids[0],
{'log': log, '%s_set_defaults' % context['type_tax_use']: True},
context=context)
return {
'name': self._description,
'view_type': 'form',
'view_mode': 'form',
'res_model': self._name,
'domain': [],
'context': context,
'type': 'ir.actions.act_window',
'res_id': ids[0],
'nodestroy': True,
}
def set_inactive(self, cr, uid, ids, context=None):
if not context or not context.get('type_tax_use'):
raise orm.except_orm(
_("Error"),
_("Can not detect tax use type"))
config = self.browse(cr, uid, ids[0], context=None)
tax_lines = config['%s_line_ids' % context['type_tax_use']]
tax_pool = self.pool.get('account.tax')
tax_ids = tax_pool.search(
cr, uid,
[('id', 'in',
[x.source_tax_id.id for x in tax_lines])],
context=context)
tax_pool.write(
cr, uid, tax_ids, {'active': False}, context=context)
log = (config.log or '') + (
"\n*** %s: Setting %s %s taxes inactive\n" % (
datetime.now().ctime(),
len(tax_ids),
context['type_tax_use']))
self.write(
cr, uid, ids[0],
{'log': log, '%s_set_inactive' % context['type_tax_use']: True},
context=context)
return {
'name': self._description,
'view_type': 'form',
'view_mode': 'form',
'res_model': self._name,
'domain': [],
'context': context,
'type': 'ir.actions.act_window',
'res_id': ids[0],
'nodestroy': True,
}
class UpdateTaxConfigLine(orm.Model):
_name = 'account.update.tax.config.line'
_description = "Tax update configuration lines"
_rec_name = 'source_tax_id' # Wha'evuh
def _get_config_field(
self, cr, uid, ids, field, args, context=None):
# Retrieve values of the associated config_id
# either sale or purchase
result = dict([(x, False) for x in ids or []])
for x in self.browse(cr, uid, ids, context=context):
config = x['sale_config_id'] or x['purchase_config_id']
if config:
result[x.id] = config[field]
return result
_columns = {
'purchase_config_id': fields.many2one(
'account.update.tax.config',
'Configuration'),
'sale_config_id': fields.many2one(
'account.update.tax.config',
'Configuration'),
'source_tax_id': fields.many2one(
'account.tax', 'Source tax',
required=True),
'source_tax_description': fields.related(
'source_tax_id', 'description',
type='char', size=32,
string="Old tax code"),
'target_tax_id': fields.many2one(
'account.tax', 'Target tax'),
'target_tax_description': fields.related(
'target_tax_id', 'description',
type='char', size=32,
string="New tax code"),
'amount_old': fields.related(
'source_tax_id', 'amount',
type='float', digits=(14, 4),
string='Old amount', readonly=True),
'amount_new': fields.related(
'target_tax_id', 'amount',
type='float', digits=(14, 4),
string='New amount'),
'state': fields.function(
_get_config_field, 'state', method=True,
type='char', size=16, string='State'),
}