Files
tryton/tax.py
T
Korbinian Preisler fd41dd2442 fix update functions for issue 1599
(transplanted from 2dab758cee6539fbc2755dfdb9b46bcb797d3c66)
2010-06-26 09:24:07 +02:00

1507 lines
60 KiB
Python

#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
"Tax"
from trytond.model import ModelView, ModelSQL, fields
from trytond.wizard import Wizard
from decimal import Decimal
from trytond.tools import Cache
from trytond.backend import TableHandler
from trytond.pyson import Eval, Not, Equal, If, In, Bool, Get, Or, And, \
PYSONEncoder
class Group(ModelSQL, ModelView):
'Tax Group'
_name = 'account.tax.group'
_description = __doc__
name = fields.Char('Name', size=None, required=True, translate=True)
code = fields.Char('Code', size=None, required=True)
def init(self, cursor, module_name):
super(Group, self).init(cursor, module_name)
table = TableHandler(cursor, self, module_name)
# Migration from 1.4 drop code_uniq constraint
table.drop_constraint('code_uniq')
Group()
class CodeTemplate(ModelSQL, ModelView):
'Tax Code Template'
_name = 'account.tax.code.template'
_description = __doc__
name = fields.Char('Name', required=True, translate=True)
code = fields.Char('Code')
parent = fields.Many2One('account.tax.code.template', 'Parent')
childs = fields.One2Many('account.tax.code.template', 'parent', 'Children')
account = fields.Many2One('account.account.template', 'Account Template',
domain=[('parent', '=', False)], required=True)
description = fields.Text('Description', translate=True)
def __init__(self):
super(CodeTemplate, self).__init__()
self._constraints += [
('check_recursion', 'recursive_tax_code'),
]
self._error_messages.update({
'recursive_tax_code': 'You can not create recursive tax codes!',
})
self._order.insert(0, ('code', 'ASC'))
self._order.insert(0, ('account', 'ASC'))
def _get_tax_code_value(self, cursor, user, template, context=None,
code=None):
'''
Set values for tax code creation.
:param cursor: the database cursor
:param user: the user id
:param template: the BrowseRecord of the template
:param context: the context
:param code: the BrowseRecord of the code to update
:return: a dictionary with tax code fields as key and values as value
'''
res = {}
if not code or code.name != template.name:
res['name'] = template.name
if not code or code.code != template.code:
res['code'] = template.code
if not code or code.description != template.description:
res['description'] = template.description
if not code or code.template.id != template.id:
res['template'] = template.id
return res
def create_tax_code(self, cursor, user, template, company_id, context=None,
template2tax_code=None, parent_id=False):
'''
Create recursively tax codes based on template.
:param cursor: the database cursor
:param user: the user id
:param template: the template id or the BrowseRecord of template
used for tax code creation
:param company_id: the id of the company for which tax codes are
created
:param context: the context
:param template2tax_code: a dictionary with tax code template id as key
and tax code id as value, used to convert template id into
tax code. The dictionary is filled with new tax codes
:param parent_id: the tax code id of the parent of the tax codes that
must be created
:return: id of the tax code created
'''
tax_code_obj = self.pool.get('account.tax.code')
lang_obj = self.pool.get('ir.lang')
if template2tax_code is None:
template2tax_code = {}
if isinstance(template, (int, long)):
template = self.browse(cursor, user, template, context=context)
if template.id not in template2tax_code:
vals = self._get_tax_code_value(cursor, user, template,
context=context)
vals['company'] = company_id
vals['parent'] = parent_id
new_id = tax_code_obj.create(cursor, user, vals, context=context)
prev_lang = template._context.get('language') or 'en_US'
prev_data = {}
for field_name, field in template._columns.iteritems():
if getattr(field, 'translate', False):
prev_data[field_name] = template[field_name]
ctx = context.copy()
for lang in lang_obj.get_translatable_languages(cursor, user,
context=context):
if lang == prev_lang:
continue
ctx['language'] = lang
template.setLang(lang)
data = {}
for field_name, field in template._columns.iteritems():
if getattr(field, 'translate', False) \
and template[field_name] != prev_data[field_name]:
data[field_name] = template[field_name]
if data:
tax_code_obj.write(cursor, user, new_id, data, context=ctx)
template.setLang(prev_lang)
template2tax_code[template.id] = new_id
else:
new_id = template2tax_code[template.id]
new_childs = []
for child in template.childs:
new_childs.append(self.create_tax_code(cursor, user, child,
company_id, context=context,
template2tax_code=template2tax_code, parent_id=new_id))
return new_id
CodeTemplate()
class Code(ModelSQL, ModelView):
'Tax Code'
_name = 'account.tax.code'
_description = __doc__
name = fields.Char('Name', size=None, required=True, select=1,
translate=True)
code = fields.Char('Code', size=None, select=1)
active = fields.Boolean('Active', select=2)
company = fields.Many2One('company.company', 'Company', required=True)
parent = fields.Many2One('account.tax.code', 'Parent', select=1,
domain=[('company', '=', Eval('company', 0))], depends=['company'])
childs = fields.One2Many('account.tax.code', 'parent', 'Children',
domain=[('company', '=', Eval('company', 0))], depends=['company'])
currency_digits = fields.Function(fields.Integer('Currency Digits',
on_change_with=['company']), 'get_currency_digits')
sum = fields.Function(fields.Numeric('Sum',
digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']),
'get_sum')
template = fields.Many2One('account.tax.code.template', 'Template')
description = fields.Text('Description', translate=True)
def __init__(self):
super(Code, self).__init__()
self._constraints += [
('check_recursion', 'recursive_tax_code'),
]
self._error_messages.update({
'recursive_tax_code': 'You can not create recursive tax codes!',
})
self._order.insert(0, ('code', 'ASC'))
def default_active(self, cursor, user, context=None):
return True
def default_company(self, cursor, user, context=None):
if context is None:
context = {}
if context.get('company'):
return context['company']
return False
def on_change_with_currency_digits(self, cursor, user, vals, context=None):
company_obj = self.pool.get('company.company')
if vals.get('company'):
company = company_obj.browse(cursor, user, vals['company'],
context=context)
return company.currency.digits
return 2
def get_currency_digits(self, cursor, user, ids, name, context=None):
res = {}
for code in self.browse(cursor, user, ids, context=context):
res[code.id] = code.company.currency.digits
return res
def get_sum(self, cursor, user, ids, name, context=None):
res = {}
move_line_obj = self.pool.get('account.move.line')
currency_obj = self.pool.get('currency.currency')
child_ids = self.search(cursor, user, [('parent', 'child_of', ids)],
context=context)
all_ids = {}.fromkeys(ids + child_ids).keys()
line_query, _ = move_line_obj.query_get(cursor, user, context=context)
cursor.execute('SELECT c.id, ' \
'SUM(tl.amount) ' \
'FROM account_tax_code c, ' \
'account_tax_line tl, ' \
'account_move_line l ' \
'WHERE c.id = tl.code ' \
'AND tl.move_line = l.id ' \
'AND c.id IN (' + \
','.join(('%s',) * len(all_ids))+ ') ' \
'AND ' + line_query + ' ' \
'AND c.active ' \
'GROUP BY c.id', all_ids)
code_sum = {}
for code_id, sum in cursor.fetchall():
# SQLite uses float for SUM
if not isinstance(sum, Decimal):
sum = Decimal(str(sum))
code_sum[code_id] = sum
for code in self.browse(cursor, user, ids, context=context):
res.setdefault(code.id, Decimal('0.0'))
child_ids = self.search(cursor, user, [
('parent', 'child_of', [code.id]),
], context=context)
for child_id in child_ids:
res[code.id] += currency_obj.round(cursor, user,
code.company.currency,
code_sum.get(child_id, Decimal('0.0')))
res[code.id] = currency_obj.round(cursor, user,
code.company.currency, res[code.id])
return res
def get_rec_name(self, cursor, user, ids, name, context=None):
if not ids:
return {}
res = {}
for code in self.browse(cursor, user, ids, context=context):
if code.code:
res[code.id] = code.code + ' - ' + code.name
else:
res[code.id] = code.name
return res
def search_rec_name(self, cursor, user, name, clause, context=None):
ids = self.search(cursor, user, [('code',) + clause[1:]], limit=1,
order=[], context=context)
if ids:
return [('code',) + clause[1:]]
return [('name',) + clause[1:]]
def delete(self, cursor, user, ids, context=None):
if isinstance(ids, (int, long)):
ids = [ids]
code_ids = self.search(cursor, user, [
('parent', 'child_of', ids),
], context=context)
return super(Code, self).delete(cursor, user, code_ids,
context=context)
def update_tax_code(self, cursor, user, code, context=None,
template2tax_code=None):
'''
Update recursively tax code based on template.
:param cursor: the database cursor
:param user: the user id
:param code: a code id or the BrowseRecord of the code
:param context: the context
:param template2tax_code: a dictionary with tax code template id as key
and tax code id as value, used to convert template id into
tax code. The dictionary is filled with new tax codes
'''
template_obj = self.pool.get('account.tax.code.template')
lang_obj = self.pool.get('ir.lang')
if template2tax_code is None:
template2tax_code = {}
if isinstance(code, (int, long)):
code = self.browse(cursor, user, code, context=context)
if code.template:
vals = template_obj._get_tax_code_value(cursor, user,
code.template, context=context, code=code)
if vals:
self.write(cursor, user, code.id, vals, context=context)
prev_lang = code._context.get('language') or 'en_US'
prev_data = {}
for field_name, field in code.template._columns.iteritems():
if getattr(field, 'translate', False):
prev_data[field_name] = code.template[field_name]
ctx = context.copy()
for lang in lang_obj.get_translatable_languages(cursor, user,
context=context):
if lang == prev_lang:
continue
ctx['language'] = lang
code.setLang(lang)
data = {}
for field_name, field in code.template._columns.iteritems():
if (getattr(field, 'translate', False)
and code.template[field_name] !=
prev_data[field_name]):
data[field_name] = code.template[field_name]
if data:
self.write(cursor, user, code.id, data, context=ctx)
code.setLang(prev_lang)
template2tax_code[code.template.id] = code.id
for child in code.childs:
self.update_tax_code(cursor, user, child, context=context,
template2tax_code=template2tax_code)
Code()
class OpenChartCodeInit(ModelView):
'Open Chart Code Init'
_name = 'account.tax.open_chart_code.init'
_description = __doc__
method = fields.Selection([
('fiscalyear', 'By Fiscal Year'),
('periods', 'By Periods'),
], 'Method', required=True)
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
help='Leave empty for all open fiscal year',
states={
'invisible': Not(Equal(Eval('method'), 'fiscalyear')),
}, depends=['method'])
periods = fields.Many2Many('account.period', None, None, 'Periods',
help='Leave empty for all periods of all open fiscal year',
states={
'invisible': Not(Equal(Eval('method'), 'periods')),
}, depends=['method'])
def default_method(self, cursor, user, context=None):
return 'periods'
OpenChartCodeInit()
class OpenChartCode(Wizard):
'Open Chart Of Tax Code by Fiscal Year'
_name = 'account.tax.open_chart_code'
states = {
'init': {
'result': {
'type': 'form',
'object': 'account.tax.open_chart_code.init',
'state': [
('end', 'Cancel', 'tryton-cancel'),
('open', 'Open', 'tryton-ok', True),
],
},
},
'open': {
'result': {
'type': 'action',
'action': '_action_open_chart',
'state': 'end',
},
},
}
def _action_open_chart(self, cursor, user, data, context=None):
model_data_obj = self.pool.get('ir.model.data')
act_window_obj = self.pool.get('ir.action.act_window')
act_window_id = model_data_obj.get_id(cursor, user, 'account',
'act_tax_code_tree2', context=context)
res = act_window_obj.read(cursor, user, act_window_id, context=context)
if data['form']['method'] == 'fiscalyear':
res['pyson_context'] = PYSONEncoder().encode({
'fiscalyear': data['form']['fiscalyear'],
})
else:
res['pyson_context'] = PYSONEncoder().encode({
'periods': data['form']['periods'][0][1],
})
return res
OpenChartCode()
class TaxTemplate(ModelSQL, ModelView):
'Account Tax Template'
_name = 'account.tax.template'
_description = __doc__
name = fields.Char('Name', required=True, translate=True)
description = fields.Char('Description', required=True, translate=True)
group = fields.Many2One('account.tax.group', 'Group')
sequence = fields.Integer('Sequence')
amount = fields.Numeric('Amount', digits=(16, 8))
percentage = fields.Numeric('Percentage', digits=(16, 8))
type = fields.Selection([
('percentage', 'Percentage'),
('fixed', 'Fixed'),
('none', 'None'),
], 'Type', required=True)
parent = fields.Many2One('account.tax.template', 'Parent')
childs = fields.One2Many('account.tax.template', 'parent', 'Children')
invoice_account = fields.Many2One('account.account.template',
'Invoice Account')
credit_note_account = fields.Many2One('account.account.template',
'Credit Note Account')
invoice_base_code = fields.Many2One('account.tax.code.template',
'Invoice Base Code')
invoice_base_sign = fields.Numeric('Invoice Base Sign', digits=(2, 0))
invoice_tax_code = fields.Many2One('account.tax.code.template',
'Invoice Tax Code')
invoice_tax_sign = fields.Numeric('Invoice Tax Sign', digits=(2, 0))
credit_note_base_code = fields.Many2One('account.tax.code.template',
'Credit Note Base Code')
credit_note_base_sign = fields.Numeric('Credit Note Base Sign', digits=(2, 0))
credit_note_tax_code = fields.Many2One('account.tax.code.template',
'Credit Note Tax Code')
credit_note_tax_sign = fields.Numeric('Credit Note Tax Sign', digits=(2, 0))
account = fields.Many2One('account.account.template', 'Account Template',
domain=[('parent', '=', False)], required=True)
def __init__(self):
super(TaxTemplate, self).__init__()
self._order.insert(0, ('sequence', 'ASC'))
self._order.insert(0, ('account', 'ASC'))
def init(self, cursor, module_name):
super(TaxTemplate, self).init(cursor, module_name)
table = TableHandler(cursor, self, module_name)
# Migration from 1.0 group is no more required
table.not_null_action('group', action='remove')
def default_type(self, cursor, user, context=None):
return 'percentage'
def default_include_base_amount(self, cursor, user, context=None):
return False
def default_invoice_base_sign(self, cursor, user, context=None):
return Decimal('1')
def default_invoice_tax_sign(self, cursor, user, context=None):
return Decimal('1')
def default_credit_note_base_sign(self, cursor, user, context=None):
return Decimal('1')
def default_credit_note_tax_sign(self, cursor, user, context=None):
return Decimal('1')
def _get_tax_value(self, cursor, user, template, context=None,
tax=None):
'''
Set values for tax creation.
:param cursor: the database cursor
:param user: the user id
:param template: the BrowseRecord of the template
:param context: the context
:param tax: the BrowseRecord of the tax to update
:return: a dictionary with account fields as key and values as value
'''
res = {}
for field in ('name', 'description', 'sequence', 'amount',
'percentage', 'type', 'invoice_base_sign', 'invoice_tax_sign',
'credit_note_base_sign', 'credit_note_tax_sign'):
if not tax or tax[field] != template[field]:
res[field] = template[field]
for field in ('group',):
if not tax or tax[field].id != template[field].id:
res[field] = template[field].id
if not tax or tax.template.id != template.id:
res['template'] = template.id
return res
def create_tax(self, cursor, user, template, company_id,
template2tax_code, template2account, context=None,
template2tax=None, parent_id=False):
'''
Create recursively taxes based on template.
:param cursor: the database cursor
:param user: the user id
:param template: the template id or the BrowseRecord of template
used for tax creation
:param company_id: the id of the company for which taxes are created
:param template2tax_code: a dictionary with tax code template id as key
and tax code id as value, used to convert tax code template into
tax code
:param template2account: a dictionary with account template id as key
and account id as value, used to convert account template into
account code
:param context: the context
:param template2tax: a dictionary with tax template id as key and
tax id as value, used to convert template id into tax.
The dictionary is filled with new taxes
:param parent_id: the tax id of the parent of the tax that must be
created
:return: id of the tax created
'''
tax_obj = self.pool.get('account.tax')
lang_obj = self.pool.get('ir.lang')
if template2tax is None:
template2tax = {}
if isinstance(template, (int, long)):
template = self.browse(cursor, user, template, context=context)
if template.id not in template2tax:
vals = self._get_tax_value(cursor, user, template, context=context)
vals['company'] = company_id
vals['parent'] = parent_id
if template.invoice_account:
vals['invoice_account'] = \
template2account[template.invoice_account.id]
else:
vals['invoice_account'] = False
if template.credit_note_account:
vals['credit_note_account'] = \
template2account[template.credit_note_account.id]
else:
vals['credit_note_account'] = False
if template.invoice_base_code:
vals['invoice_base_code'] = \
template2tax_code[template.invoice_base_code.id]
else:
vals['invoice_base_code'] = False
if template.invoice_tax_code:
vals['invoice_tax_code'] = \
template2tax_code[template.invoice_tax_code.id]
else:
vals['invoice_tax_code'] = False
if template.credit_note_base_code:
vals['credit_note_base_code'] = \
template2tax_code[template.credit_note_base_code.id]
else:
vals['credit_note_base_code'] = False
if template.credit_note_tax_code:
vals['credit_note_tax_code'] = \
template2tax_code[template.credit_note_tax_code.id]
else:
vals['credit_note_tax_code'] = False
new_id = tax_obj.create(cursor, user, vals, context=context)
prev_lang = template._context.get('language') or 'en_US'
prev_data = {}
for field_name, field in template._columns.iteritems():
if getattr(field, 'translate', False):
prev_data[field_name] = template[field_name]
ctx = context.copy()
for lang in lang_obj.get_translatable_languages(cursor, user,
context=context):
if lang == prev_lang:
continue
ctx['language'] = lang
template.setLang(lang)
data = {}
for field_name, field in template._columns.iteritems():
if getattr(field, 'translate', False) \
and template[field_name] != prev_data[field_name]:
data[field_name] = template[field_name]
if data:
tax_obj.write(cursor, user, new_id, data, context=ctx)
template.setLang(prev_lang)
template2tax[template.id] = new_id
else:
new_id = template2tax[template.id]
new_childs = []
for child in template.childs:
new_childs.append(self.create_tax(cursor, user, child,
company_id, template2tax_code, template2account,
context=context, template2tax=template2tax, parent_id=new_id))
return new_id
TaxTemplate()
class Tax(ModelSQL, ModelView):
'''
Account Tax
Type:
percentage: tax = price * amount
fixed: tax = amount
none: tax = none
'''
_name = 'account.tax'
_description = 'Account Tax'
name = fields.Char('Name', required=True, translate=True)
description = fields.Char('Description', required=True, translate=True,
help="The name that will be used in reports")
group = fields.Many2One('account.tax.group', 'Group',
states={
'invisible': Bool(Eval('parent')),
}, depends=['parent'])
active = fields.Boolean('Active')
sequence = fields.Integer('Sequence',
help='Use to order the taxes')
currency_digits = fields.Function(fields.Integer('Currency Digits',
on_change_with=['company']), 'get_currency_digits')
amount = fields.Numeric('Amount', digits=(16, Eval('currency_digits', 2)),
states={
'invisible': Not(Equal(Eval('type'), 'fixed')),
}, help='In company\'s currency', depends=['type', 'currency_digits'])
percentage = fields.Numeric('Percentage', digits=(16, 8),
states={
'invisible': Not(Equal(Eval('type'), 'percentage')),
}, help='In %', depends=['type'])
type = fields.Selection([
('percentage', 'Percentage'),
('fixed', 'Fixed'),
('none', 'None'),
], 'Type', required=True)
parent = fields.Many2One('account.tax', 'Parent', ondelete='CASCADE')
childs = fields.One2Many('account.tax', 'parent', 'Children')
company = fields.Many2One('company.company', 'Company', required=True,
domain=[
('id', If(In('company', Eval('context', {})), '=', '!='),
Get(Eval('context', {}), 'company', 0)),
])
invoice_account = fields.Many2One('account.account', 'Invoice Account',
domain=[
('company', '=', Eval('company')),
('kind', '!=', 'view'),
],
states={
'readonly': Or(Equal(Eval('type'), 'none'),
Not(Bool(Eval('company')))),
'required': And(Not(Equal(Eval('type'), 'none')),
Bool(Eval('company'))),
}, depends=['company'])
credit_note_account = fields.Many2One('account.account', 'Credit Note Account',
domain=[
('company', '=', Eval('company')),
('kind', '!=', 'view'),
],
states={
'readonly': Or(Equal(Eval('type'), 'none'),
Not(Bool(Eval('company')))),
'required': And(Not(Equal(Eval('type'), 'none')),
Bool(Eval('company'))),
}, depends=['company', 'type'])
invoice_base_code = fields.Many2One('account.tax.code',
'Invoice Base Code',
states={
'readonly': Equal(Eval('type'), 'none'),
}, depends=['type'])
invoice_base_sign = fields.Numeric('Invoice Base Sign', digits=(2, 0),
help='Usualy 1 or -1',
states={
'readonly': Equal(Eval('type'), 'none'),
}, depends=['type'])
invoice_tax_code = fields.Many2One('account.tax.code',
'Invoice Tax Code',
states={
'readonly': Equal(Eval('type'), 'none'),
}, depends=['type'])
invoice_tax_sign = fields.Numeric('Invoice Tax Sign', digits=(2, 0),
help='Usualy 1 or -1',
states={
'readonly': Equal(Eval('type'), 'none'),
}, depends=['type'])
credit_note_base_code = fields.Many2One('account.tax.code',
'Credit Note Base Code',
states={
'readonly': Equal(Eval('type'), 'none'),
}, depends=['type'])
credit_note_base_sign = fields.Numeric('Credit Note Base Sign', digits=(2, 0),
help='Usualy 1 or -1',
states={
'readonly': Equal(Eval('type'), 'none'),
}, depends=['type'])
credit_note_tax_code = fields.Many2One('account.tax.code',
'Credit Note Tax Code',
states={
'readonly': Equal(Eval('type'), 'none'),
}, depends=['type'])
credit_note_tax_sign = fields.Numeric('Credit Note Tax Sign', digits=(2, 0),
help='Usualy 1 or -1',
states={
'readonly': Equal(Eval('type'), 'none'),
}, depends=['type'])
template = fields.Many2One('account.tax.template', 'Template')
def __init__(self):
super(Tax, self).__init__()
self._order.insert(0, ('sequence', 'ASC'))
def init(self, cursor, module_name):
super(Tax, self).init(cursor, module_name)
table = TableHandler(cursor, self, module_name)
# Migration from 1.0 group is no more required
table.not_null_action('group', action='remove')
def default_active(self, cursor, user, context=None):
return True
def default_type(self, cursor, user, context=None):
return 'percentage'
def default_include_base_amount(self, cursor, user, context=None):
return False
def default_invoice_base_sign(self, cursor, user, context=None):
return Decimal('1')
def default_invoice_tax_sign(self, cursor, user, context=None):
return Decimal('1')
def default_credit_note_base_sign(self, cursor, user, context=None):
return Decimal('1')
def default_credit_note_tax_sign(self, cursor, user, context=None):
return Decimal('1')
def default_company(self, cursor, user, context=None):
if context is None:
context = {}
if context.get('company'):
return context['company']
return False
def on_change_with_currency_digits(self, cursor, user, vals, context=None):
company_obj = self.pool.get('company.company')
if vals.get('company'):
company = company_obj.browse(cursor, user, vals['company'],
context=context)
return company.currency.digits
return 2
def get_currency_digits(self, cursor, user, ids, name, context=None):
res = {}
for tax in self.browse(cursor, user, ids, context=context):
res[tax.id] = tax.company.currency.digits
return res
def _process_tax(self, cursor, user, tax, price_unit, context=None):
if tax.type == 'percentage':
amount = price_unit * tax.percentage / Decimal('100')
return {
'base': price_unit,
'amount': amount,
'tax': tax,
}
if tax.type == 'fixed':
amount = tax.amount
return {
'base': price_unit,
'amount': amount,
'tax': tax,
}
def _unit_compute(self, cursor, user, taxes, price_unit, context=None):
res = []
for tax in taxes:
if tax.type != 'none':
res.append(self._process_tax(cursor, user, tax, price_unit,
context=context))
if len(tax.childs):
res.extend(self._unit_compute(cursor, user, tax.childs,
price_unit, context=context))
return res
def delete(self, cursor, user, ids, context=None):
# Restart the cache
self.sort_taxes(cursor.dbname)
return super(Tax, self).delete(cursor, user, ids, context=context)
def create(self, cursor, user, vals, context=None):
# Restart the cache
self.sort_taxes(cursor.dbname)
return super(Tax, self).create(cursor, user, vals, context=context)
def write(self, cursor, user, ids, vals, context=None):
# Restart the cache
self.sort_taxes(cursor.dbname)
return super(Tax, self).write(cursor, user, ids, vals, context=context)
@Cache('account_tax.sort_taxes')
def sort_taxes(self, cursor, user, ids, context=None):
'''
Return a list of taxe ids sorted
:param cursor: the database cursor
:param user: the user id
:param ids: a list of tax ids
:param context: the context
:return: a list of tax ids sorted
'''
return self.search(cursor, user, [
('id', 'in', ids),
], order=[('sequence', 'ASC'), ('id', 'ASC')], context=context)
def compute(self, cursor, user, ids, price_unit, quantity, context=None):
'''
Compute taxes for price_unit and quantity.
Return list of dict for each taxes and their childs with:
base
amount
tax
'''
ids = self.sort_taxes(cursor, user, ids, context=context)
taxes = self.browse(cursor, user, ids, context=context)
res = self._unit_compute(cursor, user, taxes, price_unit,
context=context)
quantity = Decimal(str(quantity or 0.0))
for row in res:
row['base'] *= quantity
row['amount'] *= quantity
return res
def update_tax(self, cursor, user, tax, template2tax_code,
template2account, context=None, template2tax=None):
'''
Update recursively taxes based on template.
:param cursor: the database cursor
:param user: the user id
:param tax: a tax id or the BrowseRecord of the tax
:param template2tax_code: a dictionary with tax code template id as key
and tax code id as value, used to convert tax code template into
tax code
:param template2account: a dictionary with account template id as key
and account id as value, used to convert account template into
account code
:param context: the context
:param template2tax: a dictionary with tax template id as key and
tax id as value, used to convert template id into tax.
The dictionary is filled with new taxes
'''
template_obj = self.pool.get('account.tax.template')
lang_obj = self.pool.get('ir.lang')
if template2tax is None:
template2tax = {}
if isinstance(tax, (int, long)):
tax = self.browse(cursor, user, tax, context=context)
if tax.template:
vals = template_obj._get_tax_value(cursor, user, tax.template,
context=context, tax=tax)
if tax.template.invoice_account \
and tax.invoice_account.id != \
template2account.get(tax.template.invoice_account.id,
False):
vals['invoice_account'] = \
template2account.get(tax.template.invoice_account.id,
False)
elif not tax.template.invoice_account \
and tax.invoice_account:
vals['invoice_account'] = False
if tax.template.credit_note_account \
and tax.credit_note_account.id != \
template2account.get(tax.template.credit_note_account.id,
False):
vals['credit_note_account'] = \
template2account.get(tax.template.credit_note_account.id,
False)
elif not tax.template.credit_note_account \
and tax.credit_note_account:
vals['credit_note_account'] = False
if tax.template.invoice_base_code \
and tax.invoice_base_code.id != \
template2tax_code.get(tax.template.invoice_base_code.id,
False):
vals['invoice_base_code'] = \
template2tax_code.get(tax.template.invoice_base_code.id,
False)
elif not tax.template.invoice_base_code \
and tax.invoice_base_code:
vals['invoice_base_code'] = False
if tax.template.invoice_tax_code \
and tax.invoice_tax_code.id != \
template2tax_code.get(tax.template.invoice_tax_code.id,
False):
vals['invoice_tax_code'] = \
template2tax_code.get(tax.template.invoice_tax_code.id,
False)
elif not tax.template.invoice_tax_code \
and tax.invoice_tax_code:
vals['invoice_tax_code'] = False
if tax.template.credit_note_base_code \
and tax.credit_note_base_code.id != \
template2tax_code.get(tax.template.credit_note_base_code.id,
False):
vals['credit_note_base_code'] = \
template2tax_code.get(tax.template.credit_note_base_code.id,
False)
elif not tax.template.credit_note_base_code \
and tax.credit_note_base_code:
vals['credit_note_base_code'] = False
if tax.template.credit_note_tax_code \
and tax.credit_note_tax_code.id != \
template2tax_code.get(tax.template.credit_note_tax_code.id,
False):
vals['credit_note_tax_code'] = \
template2tax_code.get(tax.template.credit_note_tax_code.id,
False)
elif not tax.template.credit_note_tax_code \
and tax.credit_note_tax_code:
vals['credit_note_tax_code'] = False
if vals:
self.write(cursor, user, tax.id, vals, context=context)
prev_lang = tax._context.get('language') or 'en_US'
prev_data = {}
for field_name, field in tax.template._columns.iteritems():
if getattr(field, 'translate', False):
prev_data[field_name] = tax.template[field_name]
ctx = context.copy()
for lang in lang_obj.get_translatable_languages(cursor, user,
context=context):
if lang == prev_lang:
continue
ctx['language'] = lang
tax.setLang(lang)
data = {}
for field_name, field in tax.template._columns.iteritems():
if (getattr(field, 'translate', False)
and tax.template[field_name] !=
prev_data[field_name]):
data[field_name] = tax.template[field_name]
if data:
self.write(cursor, user, tax.id, data, context=ctx)
tax.setLang(prev_lang)
template2tax[tax.template.id] = tax.id
for child in tax.childs:
self.update_tax(cursor, user, child, template2tax_code,
template2account, context=context, template2tax=template2tax)
Tax()
class Line(ModelSQL, ModelView):
'Tax Line'
_name = 'account.tax.line'
_description = __doc__
_rec_name = 'amount'
currency_digits = fields.Function(fields.Integer('Currency Digits',
on_change_with=['move_line']), 'get_currency_digits')
amount = fields.Numeric('Amount', digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits'])
code = fields.Many2One('account.tax.code', 'Code', select=1, required=True)
tax = fields.Many2One('account.tax', 'Tax', select=1, ondelete='RESTRICT',
on_change=['tax'])
move_line = fields.Many2One('account.move.line', 'Move Line',
required=True, select=1, ondelete='CASCADE')
def on_change_with_currency_digits(self, cursor, user, vals, context=None):
move_line_obj = self.pool.get('account.move.line')
if vals.get('move_line'):
move_line = move_line_obj.browse(cursor, user, vals['move_line'],
context=context)
return move_line.currency_digits
return 2
def get_currency_digits(self, cursor, user, ids, name, context=None):
res = {}
for line in self.browse(cursor, user, ids, context=context):
res[line.id] = line.move_line.currency_digits
return res
def on_change_tax(self, cursor, user, vals, context=None):
res = {
'code': False,
}
return res
Line()
class RuleTemplate(ModelSQL, ModelView):
'Tax Rule Template'
_name = 'account.tax.rule.template'
_description = __doc__
name = fields.Char('Name', required=True, translate=True)
lines = fields.One2Many('account.tax.rule.line.template', 'rule', 'Lines')
account = fields.Many2One('account.account.template', 'Account Template',
domain=[('parent', '=', False)], required=True)
def _get_tax_rule_value(self, cursor, user, template, context=None,
rule=None):
'''
Set values for tax rule creation.
:param cursor: the database cursor
:param user: the user id
:param template: the BrowseRecord of the template
:param context: the context
:param rule: the BrowseRecord of the rule to update
:return: a dictionary with rule fields as key and values as value
'''
res = {}
if not rule or rule.name != template.name:
res['name'] = template.name
if not rule or rule.template.id != template.id:
res['template'] = template.id
return res
def create_rule(self, cursor, user, template, company_id, context=None,
template2rule=None):
'''
Create tax rule based on template.
:param cursor: the database cursor
:param user: the user id
:param template: the template id or the BrowseRecord of template
used for tax rule creation
:param company_id: the id of the company for which tax rules are
created
:param context: the context
:param template2rule: a dictionary with tax rule template id as key
and tax rule id as value, used to convert template id into
tax rule. The dictionary is filled with new tax rules
:return: id of the tax rule created
'''
rule_obj = self.pool.get('account.tax.rule')
lang_obj = self.pool.get('ir.lang')
if template2rule is None:
template2rule = {}
if isinstance(template, (int, long)):
template = self.browse(cursor, user, template, context=context)
if template.id not in template2rule:
vals = self._get_tax_rule_value(cursor, user, template,
context=context)
vals['company'] = company_id
new_id = rule_obj.create(cursor, user, vals, context=context)
prev_lang = template._context.get('language') or 'en_US'
prev_data = {}
for field_name, field in template._columns.iteritems():
if getattr(field, 'translate', False):
prev_data[field_name] = template[field_name]
ctx = context.copy()
for lang in lang_obj.get_translatable_languages(cursor, user,
context=context):
if lang == prev_lang:
continue
ctx['language'] = lang
template.setLang(lang)
data = {}
for field_name, field in template._columns.iteritems():
if getattr(field, 'translate', False) \
and template[field_name] != prev_data[field_name]:
data[field_name] = template[field_name]
if data:
rule_obj.write(cursor, user, new_id, data, context=ctx)
template.setLang(prev_lang)
template2rule[template.id] = new_id
else:
new_id = template2rule[template.id]
return new_id
RuleTemplate()
class Rule(ModelSQL, ModelView):
'Tax Rule'
_name = 'account.tax.rule'
_description = __doc__
name = fields.Char('Name', required=True, translate=True)
company = fields.Many2One('company.company', 'Company', required=True,
select=1, domain=[
('id', If(In('company', Eval('context', {})), '=', '!='),
Get(Eval('context', {}), 'company', 0)),
])
lines = fields.One2Many('account.tax.rule.line', 'rule', 'Lines')
template = fields.Many2One('account.tax.rule.template', 'Template')
def apply(self, cursor, user, rule, tax, pattern, context=None):
'''
Apply rule on tax
:param cursor: the database cursor
:param user: the user id
:param rule: a rule id or the BrowseRecord of the rule
:param tax: a tax id or the BrowseRecord of the tax
:param pattern: a dictonary with rule line field as key
and match value as value
:param context: the context
:return: a list of the tax id to use or False
'''
tax_obj = self.pool.get('account.tax')
rule_line_obj = self.pool.get('account.tax.rule.line')
if isinstance(rule, (int, long)) and rule:
rule = self.browse(cursor, user, rule, context=context)
if isinstance(tax, (int, long)) and tax:
tax = tax_obj.browse(cursor, user, tax, context=context)
pattern = pattern.copy()
pattern['group'] = tax and tax.group.id or False
pattern['origin_tax'] = tax and tax.id or False
for line in rule.lines:
if rule_line_obj.match(cursor, user, line, pattern,
context=context):
return rule_line_obj.get_taxes(cursor, user, line,
context=context)
return tax and [tax.id] or False
def update_rule(self, cursor, user, rule, context=None, template2rule=None):
'''
Update tax rule based on template.
:param cursor: the database cursor
:param user: the user id
:param rule: a rule id or the BrowseRecord of the rule
:param context: the context
:param template2rule: a dictionary with tax rule template id as key
and tax rule id as value, used to convert template id into
tax rule. The dictionary is filled with new tax rules
'''
template_obj = self.pool.get('account.tax.rule.template')
lang_obj = self.pool.get('ir.lang')
if template2rule is None:
template2rule = {}
if isinstance(rule, (int, long)):
rule = self.browse(cursor, user, rule, context=context)
if rule.template:
vals = template_obj._get_tax_rule_value(cursor, user,
rule.template, context=context, rule=rule)
if vals:
self.write(cursor, user, rule.id, vals, context=context)
prev_lang = rule._context.get('language') or 'en_US'
prev_data = {}
for field_name, field in rule.template._columns.iteritems():
if getattr(field, 'translate', False):
prev_data[field_name] = rule.template[field_name]
ctx = context.copy()
for lang in lang_obj.get_translatable_languages(cursor, user,
context=context):
if lang == prev_lang:
continue
ctx['language'] = lang
rule.setLang(lang)
data = {}
for field_name, field in rule.template._columns.iteritems():
if (getattr(field, 'translate', False)
and rule.template[field_name] !=
prev_data[field_name]):
data[field_name] = rule.template[field_name]
if data:
self.write(cursor, user, rule.id, data, context=ctx)
rule.setLang(prev_lang)
template2rule[rule.template.id] = rule.id
Rule()
class RuleLineTemplate(ModelSQL, ModelView):
'Tax Rule Line Template'
_name = 'account.tax.rule.line.template'
_description = __doc__
rule = fields.Many2One('account.tax.rule.template', 'Rule', required=True,
ondelete='CASCADE')
group = fields.Many2One('account.tax.group', 'Tax Group')
origin_tax = fields.Many2One('account.tax.template', 'Original Tax',
domain=[
('account', '=', Get(Eval('_parent_rule', {}), 'account')),
('group', '=', Eval('group'))
],
help='If the original tax template is filled, the rule will be ' \
'applied only for this tax template.')
tax = fields.Many2One('account.tax.template', 'Substitution Tax',
domain=[
('account', '=', Get(Eval('_parent_rule', {}), 'account')),
('group', '=', Eval('group')),
])
sequence = fields.Integer('Sequence')
def __init__(self):
super(RuleLineTemplate, self).__init__()
self._order.insert(0, ('rule', 'ASC'))
self._order.insert(0, ('sequence', 'ASC'))
def _get_tax_rule_line_value(self, cursor, user, template, context=None,
rule_line=None):
'''
Set values for tax rule line creation.
:param cursor: the database cursor
:param user: the user id
:param template: the BrowseRecord of the template
:param context: the context
:param rule_line: the BrowseRecord of the rule line to update
:return: a dictionary with rule line fields as key and values as value
'''
res = {}
if not rule_line or rule_line.group.id != template.group.id:
res['group'] = template.group.id
if not rule_line or rule_line.origin_tax.id != template.origin_tax.id:
res['origin_tax'] = template.origin_tax.id
if not rule_line or rule_line.sequence != template.sequence:
res['sequence'] = template.sequence
if not rule_line or rule_line.template.id != template.id:
res['template'] = template.id
return res
def create_rule_line(self, cursor, user, template, template2tax,
template2rule, context=None, template2rule_line=None):
'''
Create tax rule line based on template.
:param cursor: the database cursor
:param user: the user id
:param template: the template id or the BrowseRecord of template
used for tax rule line creation
:param template2tax: a dictionary with tax template id as key
and tax id as value, used to convert template id into
tax.
:param template2rule: a dictionary with tax rule template id as key
and tax rule id as value, used to convert template id into
tax rule.
:param context: the context
:param template2rule_line: a dictionary with tax rule line template id
as key and tax rule line id as value, used to convert template
id into tax rule line. The dictionary is filled with new
tax rule lines
:return: id of the tax rule line created
'''
rule_line_obj = self.pool.get('account.tax.rule.line')
if template2rule_line is None:
template2rule_line = {}
if isinstance(template, (int, long)):
template = self.browse(cursor, user, template, context=context)
if template.id not in template2rule_line:
vals = self._get_tax_rule_line_value(cursor, user, template,
context=context)
vals['rule'] = template2rule[template.rule.id]
if template.origin_tax:
vals['origin_tax'] = template2tax[template.origin_tax.id]
else:
vals['origin_tax'] = False
if template.tax:
vals['tax'] = template2tax[template.tax.id]
else:
vals['tax'] = False
new_id = rule_line_obj.create(cursor, user, vals, context=context)
template2rule_line[template.id] = new_id
else:
new_id = template2rule_line[template.id]
return new_id
RuleLineTemplate()
class RuleLine(ModelSQL, ModelView):
'Tax Rule Line'
_name = 'account.tax.rule.line'
_description = __doc__
_rec_name = 'tax'
rule = fields.Many2One('account.tax.rule', 'Rule', required=True,
select=1, ondelete='CASCADE')
group = fields.Many2One('account.tax.group', 'Tax Group')
origin_tax = fields.Many2One('account.tax', 'Original Tax',
domain=[
('company', '=', Get(Eval('_parent_rule', {}), 'company')),
('group', '=', Eval('group')),
],
help='If the original tax is filled, the rule will be applied ' \
'only for this tax.')
tax = fields.Many2One('account.tax', 'Substitution Tax',
domain=[
('company', '=', Get(Eval('_parent_rule', {}), 'company')),
('group', '=', Eval('group')),
])
sequence = fields.Integer('Sequence')
template = fields.Many2One('account.tax.rule.line.template', 'Template')
def __init__(self):
super(RuleLine, self).__init__()
self._order.insert(0, ('rule', 'ASC'))
self._order.insert(0, ('sequence', 'ASC'))
def match(self, cursor, user, line, pattern, context=None):
'''
Match line on pattern
:param cursor: the database cursor
:param user: the user id
:param line: a BrowseRecord of rule line
:param pattern: a dictonary with rule line field as key
and match value as value
:param context: the context
:return: a boolean
'''
res = True
for field in pattern.keys():
if field not in self._columns:
continue
if not line[field] and field != 'group':
continue
if self._columns[field]._type == 'many2one':
if line[field].id != pattern[field]:
res = False
break
else:
if line[field] != pattern[field]:
res = False
break
return res
def get_taxes(self, cursor, user, line, context=None):
'''
Return list of taxes for a line
:param cursor: the database cursor
:param user: the user id
:param line: a BrowseRecord of rule line
:param context: the context
:return: a list of tax id
'''
if line.tax:
return [line.tax.id]
return False
def update_rule_line(self, cursor, user, rule_line, template2tax,
template2rule, context=None, template2rule_line=None):
'''
Update tax rule line based on template.
:param cursor: the database cursor
:param user: the user id
:param rule_line: a rule line id or the BrowseRecord of the rule line
:param template2tax: a dictionary with tax template id as key
and tax id as value, used to convert template id into
tax.
:param template2rule: a dictionary with tax rule template id as key
and tax rule id as value, used to convert template id into
tax rule.
:param context: the context
:param template2rule_line: a dictionary with tax rule line template id
as key and tax rule line id as value, used to convert template
id into tax rule line. The dictionary is filled with new
tax rule lines
'''
template_obj = self.pool.get('account.tax.rule.line.template')
if template2rule_line is None:
template2rule_line = {}
if isinstance(rule_line, (int, long)):
rule_line = self.browse(cursor, user, rule_line, context=context)
if rule_line.template:
vals = template_obj._get_tax_rule_line_value(cursor, user,
rule_line.template, context=context, rule_line=rule_line)
if rule_line.rule.id != template2rule[rule_line.template.rule.id]:
vals['rule'] = template2rule[rule_line.template.rule.id]
if rule_line.origin_tax:
if rule_line.template.origin_tax:
if rule_line.origin_tax.id != \
template2tax[rule_line.template.origin_tax.id]:
vals['origin_tax'] = template2tax[
rule_line.template.origin_tax.id]
if rule_line.tax:
if rule_line.template.tax:
if rule_line.tax.id != \
template2tax[rule_line.template.tax.id]:
vals['tax'] = template2tax[rule_line.template.tax.id]
if vals:
self.write(cursor, user, rule_line.id, vals, context=context)
template2rule_line[rule_line.template.id] = rule_line.id
RuleLine()
class OpenCode(Wizard):
'Open Code'
_name = 'account.tax.open_code'
states = {
'init': {
'result': {
'type': 'action',
'action': '_action_open_code',
'state': 'end',
},
},
}
def _action_open_code(self, cursor, user, data, context=None):
if context is None:
context = {}
model_data_obj = self.pool.get('ir.model.data')
act_window_obj = self.pool.get('ir.action.act_window')
fiscalyear_obj = self.pool.get('account.fiscalyear')
period_obj = self.pool.get('account.period')
if not context.get('fiscalyear'):
fiscalyear_ids = fiscalyear_obj.search(cursor, user, [
('state', '=', 'open'),
], context=context)
else:
fiscalyear_ids = [context['fiscalyear']]
period_ids = []
if not context.get('periods'):
period_ids = period_obj.search(cursor, user, [
('fiscalyear', 'in', fiscalyear_ids),
], context=context)
else:
period_ids = context['periods']
act_window_id = model_data_obj.get_id(cursor, user, 'account',
'act_tax_line_form', context=context)
res = act_window_obj.read(cursor, user, act_window_id, context=context)
res['pyson_domain'] = PYSONEncoder().encode([
('move_line.period', 'in', period_ids),
('code', '=', data['id']),
])
if context.get('fiscalyear'):
res['pyson_context'] = PYSONEncoder().encode({
'fiscalyear': context['fiscalyear'],
})
else:
res['pyson_context'] = PYSONEncoder().encode({
'periods': period_ids,
})
return res
OpenCode()
class AccountTemplateTaxTemplate(ModelSQL):
'Account Template - Tax Template'
_name = 'account.account.template-account.tax.template'
_table = 'account_account_template_tax_rel'
_description = __doc__
account = fields.Many2One('account.account.template', 'Account Template',
ondelete='CASCADE', select=1, required=True)
tax = fields.Many2One('account.tax.template', 'Tax Template',
ondelete='RESTRICT', select=1, required=True)
AccountTemplateTaxTemplate()
class AccountTemplate(ModelSQL, ModelView):
_name = 'account.account.template'
taxes = fields.Many2Many('account.account.template-account.tax.template',
'account', 'tax', 'Default Taxes',
domain=[('parent', '=', False)])
AccountTemplate()
class AccountTax(ModelSQL):
'Account - Tax'
_name = 'account.account-account.tax'
_table = 'account_account_tax_rel'
_description = __doc__
account = fields.Many2One('account.account', 'Account', ondelete='CASCADE',
select=1, required=True)
tax = fields.Many2One('account.tax', 'Tax', ondelete='RESTRICT',
select=1, required=True)
AccountTax()
class Account(ModelSQL, ModelView):
_name = 'account.account'
taxes = fields.Many2Many('account.account-account.tax',
'account', 'tax', 'Default Taxes',
domain=[
('company', '=', Eval('company')),
('parent', '=', False),
],
help='Default tax for manual encoding of move lines \n' \
'for journal types: "expense" and "revenue"',
depends=['company'])
Account()