mirror of
https://github.com/OCA/account-financial-tools.git
synced 2025-02-02 12:47:26 +02:00
522 lines
21 KiB
Python
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'),
|
|
}
|