1583 lines
60 KiB
Python
1583 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.
|
|
import datetime
|
|
from collections import namedtuple
|
|
from decimal import Decimal
|
|
|
|
from sql import Null
|
|
from sql.aggregate import Sum
|
|
|
|
from trytond.model import ModelView, ModelSQL, MatchMixin, fields
|
|
from trytond.wizard import Wizard, StateView, StateAction, Button
|
|
from trytond import backend
|
|
from trytond.pyson import Eval, If, Bool, PYSONEncoder
|
|
from trytond.transaction import Transaction
|
|
from trytond.pool import Pool, PoolMeta
|
|
|
|
__all__ = ['TaxGroup', 'TaxCodeTemplate', 'TaxCode',
|
|
'OpenChartTaxCodeStart', 'OpenChartTaxCode',
|
|
'TaxTemplate', 'Tax', 'TaxLine', 'TaxRuleTemplate', 'TaxRule',
|
|
'TaxRuleLineTemplate', 'TaxRuleLine',
|
|
'OpenTaxCode',
|
|
'AccountTemplateTaxTemplate', 'AccountTemplate2', 'AccountTax', 'Account2']
|
|
__metaclass__ = PoolMeta
|
|
|
|
KINDS = [
|
|
('sale', 'Sale'),
|
|
('purchase', 'Purchase'),
|
|
('both', 'Both'),
|
|
]
|
|
|
|
|
|
class TaxGroup(ModelSQL, ModelView):
|
|
'Tax Group'
|
|
__name__ = 'account.tax.group'
|
|
name = fields.Char('Name', size=None, required=True, translate=True)
|
|
code = fields.Char('Code', size=None, required=True)
|
|
kind = fields.Selection(KINDS, 'Kind', required=True)
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
TableHandler = backend.get('TableHandler')
|
|
super(TaxGroup, cls).__register__(module_name)
|
|
cursor = Transaction().cursor
|
|
table = TableHandler(cursor, cls, module_name)
|
|
|
|
# Migration from 1.4 drop code_uniq constraint
|
|
table.drop_constraint('code_uniq')
|
|
|
|
@staticmethod
|
|
def default_kind():
|
|
return 'both'
|
|
|
|
|
|
class TaxCodeTemplate(ModelSQL, ModelView):
|
|
'Tax Code Template'
|
|
__name__ = 'account.tax.code.template'
|
|
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', '=', None)], required=True)
|
|
description = fields.Text('Description', translate=True)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(TaxCodeTemplate, cls).__setup__()
|
|
cls._order.insert(0, ('code', 'ASC'))
|
|
cls._order.insert(0, ('account', 'ASC'))
|
|
|
|
@classmethod
|
|
def validate(cls, templates):
|
|
super(TaxCodeTemplate, cls).validate(templates)
|
|
cls.check_recursion(templates)
|
|
|
|
def _get_tax_code_value(self, code=None):
|
|
'''
|
|
Set values for tax code creation.
|
|
'''
|
|
res = {}
|
|
if not code or code.name != self.name:
|
|
res['name'] = self.name
|
|
if not code or code.code != self.code:
|
|
res['code'] = self.code
|
|
if not code or code.description != self.description:
|
|
res['description'] = self.description
|
|
if not code or code.template.id != self.id:
|
|
res['template'] = self.id
|
|
return res
|
|
|
|
def create_tax_code(self, company_id, template2tax_code=None,
|
|
parent_id=None):
|
|
'''
|
|
Create recursively tax codes based on template.
|
|
template2tax_code is 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.
|
|
Return the id of the tax code created
|
|
'''
|
|
pool = Pool()
|
|
TaxCode = pool.get('account.tax.code')
|
|
Lang = pool.get('ir.lang')
|
|
Config = pool.get('ir.configuration')
|
|
|
|
if template2tax_code is None:
|
|
template2tax_code = {}
|
|
|
|
if self.id not in template2tax_code:
|
|
vals = self._get_tax_code_value()
|
|
vals['company'] = company_id
|
|
vals['parent'] = parent_id
|
|
|
|
new_tax_code, = TaxCode.create([vals])
|
|
|
|
prev_lang = self._context.get('language') or Config.get_language()
|
|
prev_data = {}
|
|
for field_name, field in self._fields.iteritems():
|
|
if getattr(field, 'translate', False):
|
|
prev_data[field_name] = getattr(self, field_name)
|
|
for lang in Lang.get_translatable_languages():
|
|
if lang == prev_lang:
|
|
continue
|
|
with Transaction().set_context(language=lang):
|
|
template = self.__class__(self.id)
|
|
data = {}
|
|
for field_name, field in template._fields.iteritems():
|
|
if (getattr(field, 'translate', False)
|
|
and (getattr(template, field_name) !=
|
|
prev_data[field_name])):
|
|
data[field_name] = getattr(template, field_name)
|
|
if data:
|
|
TaxCode.write([new_tax_code], data)
|
|
template2tax_code[self.id] = new_tax_code.id
|
|
new_id = template2tax_code[self.id]
|
|
|
|
new_childs = []
|
|
for child in self.childs:
|
|
new_childs.append(child.create_tax_code(company_id,
|
|
template2tax_code=template2tax_code, parent_id=new_id))
|
|
return new_id
|
|
|
|
|
|
class TaxCode(ModelSQL, ModelView):
|
|
'Tax Code'
|
|
__name__ = 'account.tax.code'
|
|
name = fields.Char('Name', size=None, required=True, select=True,
|
|
translate=True)
|
|
code = fields.Char('Code', size=None, select=True)
|
|
active = fields.Boolean('Active', select=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True,
|
|
select=True)
|
|
parent = fields.Many2One('account.tax.code', 'Parent', select=True,
|
|
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_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)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(TaxCode, cls).__setup__()
|
|
cls._order.insert(0, ('code', 'ASC'))
|
|
|
|
@classmethod
|
|
def validate(cls, codes):
|
|
super(TaxCode, cls).validate(codes)
|
|
cls.check_recursion(codes)
|
|
|
|
@staticmethod
|
|
def default_active():
|
|
return True
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@fields.depends('company')
|
|
def on_change_with_currency_digits(self, name=None):
|
|
if self.company:
|
|
return self.company.currency.digits
|
|
return 2
|
|
|
|
@classmethod
|
|
def get_sum(cls, codes, name):
|
|
cursor = Transaction().cursor
|
|
res = {}
|
|
pool = Pool()
|
|
MoveLine = pool.get('account.move.line')
|
|
TaxLine = pool.get('account.tax.line')
|
|
|
|
code = cls.__table__()
|
|
tax_line = TaxLine.__table__()
|
|
move_line = MoveLine.__table__()
|
|
|
|
childs = cls.search([
|
|
('parent', 'child_of', [c.id for c in codes]),
|
|
])
|
|
all_codes = list(set(codes) | set(childs))
|
|
line_query, _ = MoveLine.query_get(move_line)
|
|
cursor.execute(*code.join(tax_line, condition=tax_line.code == code.id
|
|
).join(move_line, condition=tax_line.move_line == move_line.id
|
|
).select(code.id, Sum(tax_line.amount),
|
|
where=code.id.in_([c.id for c in all_codes])
|
|
& code.active & line_query,
|
|
group_by=code.id))
|
|
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 codes:
|
|
res.setdefault(code.id, Decimal('0.0'))
|
|
childs = cls.search([
|
|
('parent', 'child_of', [code.id]),
|
|
])
|
|
for child in childs:
|
|
res[code.id] += code.company.currency.round(
|
|
code_sum.get(child.id, Decimal('0.0')))
|
|
res[code.id] = code.company.currency.round(res[code.id])
|
|
return res
|
|
|
|
def get_rec_name(self, name):
|
|
if self.code:
|
|
return self.code + ' - ' + self.name
|
|
else:
|
|
return self.name
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
return ['OR',
|
|
('code',) + tuple(clause[1:]),
|
|
(cls._rec_name,) + tuple(clause[1:]),
|
|
]
|
|
|
|
@classmethod
|
|
def delete(cls, codes):
|
|
codes = cls.search([
|
|
('parent', 'child_of', [c.id for c in codes]),
|
|
])
|
|
super(TaxCode, cls).delete(codes)
|
|
|
|
def update_tax_code(self, template2tax_code=None):
|
|
'''
|
|
Update recursively tax code based on template.
|
|
template2tax_code is 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
|
|
'''
|
|
pool = Pool()
|
|
Lang = pool.get('ir.lang')
|
|
Config = pool.get('ir.configuration')
|
|
|
|
if template2tax_code is None:
|
|
template2tax_code = {}
|
|
|
|
if self.template:
|
|
vals = self.template._get_tax_code_value(code=self)
|
|
if vals:
|
|
self.write([self], vals)
|
|
|
|
prev_lang = self._context.get('language') or Config.get_language()
|
|
prev_data = {}
|
|
for field_name, field in self.template._fields.iteritems():
|
|
if getattr(field, 'translate', False):
|
|
prev_data[field_name] = getattr(self.template, field_name)
|
|
for lang in Lang.get_translatable_languages():
|
|
if lang == prev_lang:
|
|
continue
|
|
with Transaction().set_context(language=lang):
|
|
code = self.__class__(self.id)
|
|
data = {}
|
|
for field_name, field in code.template._fields.iteritems():
|
|
if (getattr(field, 'translate', False)
|
|
and (getattr(code.template, field_name) !=
|
|
prev_data[field_name])):
|
|
data[field_name] = getattr(code.template,
|
|
field_name)
|
|
if data:
|
|
self.write([code], data)
|
|
template2tax_code[self.template.id] = self.id
|
|
|
|
for child in self.childs:
|
|
child.update_tax_code(template2tax_code=template2tax_code)
|
|
|
|
|
|
class OpenChartTaxCodeStart(ModelView):
|
|
'Open Chart of Tax Codes'
|
|
__name__ = 'account.tax.code.open_chart.start'
|
|
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': 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': Eval('method') != 'periods',
|
|
}, depends=['method'])
|
|
|
|
@staticmethod
|
|
def default_method():
|
|
return 'periods'
|
|
|
|
|
|
class OpenChartTaxCode(Wizard):
|
|
'Open Chart of Tax Codes'
|
|
__name__ = 'account.tax.code.open_chart'
|
|
start = StateView('account.tax.code.open_chart.start',
|
|
'account.tax_code_open_chart_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Open', 'open_', 'tryton-ok', default=True),
|
|
])
|
|
open_ = StateAction('account.act_tax_code_tree2')
|
|
|
|
def do_open_(self, action):
|
|
if self.start.method == 'fiscalyear':
|
|
action['pyson_context'] = PYSONEncoder().encode({
|
|
'fiscalyear': (self.start.fiscalyear.id
|
|
if self.start.fiscalyear else None),
|
|
})
|
|
if self.start.fiscalyear:
|
|
action['name'] += ' - %s' % self.start.fiscalyear.rec_name
|
|
else:
|
|
action['pyson_context'] = PYSONEncoder().encode({
|
|
'periods': [x.id for x in self.start.periods],
|
|
})
|
|
period_str = ', '.join(p.rec_name for p in self.start.periods)
|
|
if period_str:
|
|
action['name'] += ' (%s)' % period_str
|
|
return action, {}
|
|
|
|
def transition_open_(self):
|
|
return 'end'
|
|
|
|
|
|
class TaxTemplate(ModelSQL, ModelView):
|
|
'Account Tax Template'
|
|
__name__ = 'account.tax.template'
|
|
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')
|
|
start_date = fields.Date('Starting Date')
|
|
end_date = fields.Date('Ending Date')
|
|
amount = fields.Numeric('Amount', digits=(16, 8),
|
|
states={
|
|
'required': Eval('type') == 'fixed',
|
|
'invisible': Eval('type') != 'fixed',
|
|
},
|
|
depends=['type'])
|
|
rate = fields.Numeric('Rate', digits=(14, 10),
|
|
states={
|
|
'required': Eval('type') == 'percentage',
|
|
'invisible': Eval('type') != 'percentage',
|
|
}, depends=['type'])
|
|
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', '=', None)], required=True)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(TaxTemplate, cls).__setup__()
|
|
cls._order.insert(0, ('sequence', 'ASC'))
|
|
cls._order.insert(0, ('account', 'ASC'))
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
TableHandler = backend.get('TableHandler')
|
|
super(TaxTemplate, cls).__register__(module_name)
|
|
cursor = Transaction().cursor
|
|
table = TableHandler(cursor, cls, module_name)
|
|
|
|
# Migration from 1.0 group is no more required
|
|
table.not_null_action('group', action='remove')
|
|
|
|
# Migration from 2.4: drop required on sequence
|
|
table.not_null_action('sequence', action='remove')
|
|
|
|
# Migration from 2.8: rename percentage into rate
|
|
if table.column_exist('percentage'):
|
|
sql_table = cls.__table__()
|
|
cursor.execute(*sql_table.update(
|
|
columns=[sql_table.rate],
|
|
values=[sql_table.percentage / 100]))
|
|
table.drop_column('percentage')
|
|
|
|
@staticmethod
|
|
def order_sequence(tables):
|
|
table, _ = tables[None]
|
|
return [table.sequence == Null, table.sequence]
|
|
|
|
@staticmethod
|
|
def default_type():
|
|
return 'percentage'
|
|
|
|
@staticmethod
|
|
def default_include_base_amount():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_invoice_base_sign():
|
|
return Decimal('1')
|
|
|
|
@staticmethod
|
|
def default_invoice_tax_sign():
|
|
return Decimal('1')
|
|
|
|
@staticmethod
|
|
def default_credit_note_base_sign():
|
|
return Decimal('1')
|
|
|
|
@staticmethod
|
|
def default_credit_note_tax_sign():
|
|
return Decimal('1')
|
|
|
|
def _get_tax_value(self, tax=None):
|
|
'''
|
|
Set values for tax creation.
|
|
'''
|
|
res = {}
|
|
for field in ('name', 'description', 'sequence', 'amount',
|
|
'rate', 'type', 'invoice_base_sign', 'invoice_tax_sign',
|
|
'credit_note_base_sign', 'credit_note_tax_sign',
|
|
'start_date', 'end_date'):
|
|
if not tax or getattr(tax, field) != getattr(self, field):
|
|
res[field] = getattr(self, field)
|
|
for field in ('group',):
|
|
if not tax or getattr(tax, field) != getattr(self, field):
|
|
value = getattr(self, field)
|
|
if value:
|
|
res[field] = getattr(self, field).id
|
|
else:
|
|
res[field] = None
|
|
if not tax or tax.template != self:
|
|
res['template'] = self.id
|
|
return res
|
|
|
|
def create_tax(self, company_id, template2tax_code, template2account,
|
|
template2tax=None, parent_id=None):
|
|
'''
|
|
Create recursively taxes based on template.
|
|
|
|
template2tax_code is a dictionary with tax code template id as key and
|
|
tax code id as value, used to convert tax code template into tax code.
|
|
template2account is a dictionary with account template id as key and
|
|
account id as value, used to convert account template into account
|
|
code.
|
|
template2tax is 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.
|
|
Return id of the tax created
|
|
'''
|
|
pool = Pool()
|
|
Tax = pool.get('account.tax')
|
|
Lang = pool.get('ir.lang')
|
|
Config = pool.get('ir.configuration')
|
|
|
|
if template2tax is None:
|
|
template2tax = {}
|
|
|
|
if self.id not in template2tax:
|
|
vals = self._get_tax_value()
|
|
vals['company'] = company_id
|
|
vals['parent'] = parent_id
|
|
if self.invoice_account:
|
|
vals['invoice_account'] = \
|
|
template2account[self.invoice_account.id]
|
|
else:
|
|
vals['invoice_account'] = None
|
|
if self.credit_note_account:
|
|
vals['credit_note_account'] = \
|
|
template2account[self.credit_note_account.id]
|
|
else:
|
|
vals['credit_note_account'] = None
|
|
if self.invoice_base_code:
|
|
vals['invoice_base_code'] = \
|
|
template2tax_code[self.invoice_base_code.id]
|
|
else:
|
|
vals['invoice_base_code'] = None
|
|
if self.invoice_tax_code:
|
|
vals['invoice_tax_code'] = \
|
|
template2tax_code[self.invoice_tax_code.id]
|
|
else:
|
|
vals['invoice_tax_code'] = None
|
|
if self.credit_note_base_code:
|
|
vals['credit_note_base_code'] = \
|
|
template2tax_code[self.credit_note_base_code.id]
|
|
else:
|
|
vals['credit_note_base_code'] = None
|
|
if self.credit_note_tax_code:
|
|
vals['credit_note_tax_code'] = \
|
|
template2tax_code[self.credit_note_tax_code.id]
|
|
else:
|
|
vals['credit_note_tax_code'] = None
|
|
|
|
new_tax, = Tax.create([vals])
|
|
|
|
prev_lang = self._context.get('language') or Config.get_language()
|
|
prev_data = {}
|
|
for field_name, field in self._fields.iteritems():
|
|
if getattr(field, 'translate', False):
|
|
prev_data[field_name] = getattr(self, field_name)
|
|
for lang in Lang.get_translatable_languages():
|
|
if lang == prev_lang:
|
|
continue
|
|
with Transaction().set_context(language=lang):
|
|
template = self.__class__(self.id)
|
|
data = {}
|
|
for field_name, field in template._fields.iteritems():
|
|
if (getattr(field, 'translate', False)
|
|
and (getattr(template, field_name)
|
|
!= prev_data[field_name])):
|
|
data[field_name] = getattr(template, field_name)
|
|
if data:
|
|
Tax.write([new_tax], data)
|
|
template2tax[self.id] = new_tax.id
|
|
new_id = template2tax[self.id]
|
|
|
|
new_childs = []
|
|
for child in self.childs:
|
|
new_childs.append(child.create_tax(company_id, template2tax_code,
|
|
template2account, template2tax=template2tax,
|
|
parent_id=new_id))
|
|
return new_id
|
|
|
|
|
|
class Tax(ModelSQL, ModelView):
|
|
'''
|
|
Account Tax
|
|
|
|
Type:
|
|
percentage: tax = price * rate
|
|
fixed: tax = amount
|
|
none: tax = none
|
|
'''
|
|
__name__ = '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')
|
|
start_date = fields.Date('Starting Date')
|
|
end_date = fields.Date('Ending Date')
|
|
currency_digits = fields.Function(fields.Integer('Currency Digits'),
|
|
'on_change_with_currency_digits')
|
|
amount = fields.Numeric('Amount', digits=(16, Eval('currency_digits', 2)),
|
|
states={
|
|
'required': Eval('type') == 'fixed',
|
|
'invisible': Eval('type') != 'fixed',
|
|
}, help='In company\'s currency',
|
|
depends=['type', 'currency_digits'])
|
|
rate = fields.Numeric('Rate', digits=(14, 10),
|
|
states={
|
|
'required': Eval('type') == 'percentage',
|
|
'invisible': Eval('type') != 'percentage',
|
|
}, 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(Eval('context', {}).contains('company'), '=', '!='),
|
|
Eval('context', {}).get('company', -1)),
|
|
], select=True)
|
|
invoice_account = fields.Many2One('account.account', 'Invoice Account',
|
|
domain=[
|
|
('company', '=', Eval('company')),
|
|
('kind', 'not in', ['view', 'receivable', 'payable']),
|
|
],
|
|
states={
|
|
'readonly': (Eval('type') == 'none') | ~Eval('company'),
|
|
'required': (Eval('type') != 'none') & Eval('company'),
|
|
},
|
|
depends=['company', 'type'])
|
|
credit_note_account = fields.Many2One('account.account',
|
|
'Credit Note Account',
|
|
domain=[
|
|
('company', '=', Eval('company')),
|
|
('kind', 'not in', ['view', 'receivable', 'payable']),
|
|
],
|
|
states={
|
|
'readonly': (Eval('type') == 'none') | ~Eval('company'),
|
|
'required': (Eval('type') != 'none') & Eval('company'),
|
|
},
|
|
depends=['company', 'type'])
|
|
invoice_base_code = fields.Many2One('account.tax.code',
|
|
'Invoice Base Code',
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
],
|
|
states={
|
|
'readonly': Eval('type') == 'none',
|
|
},
|
|
depends=['type', 'company'])
|
|
invoice_base_sign = fields.Numeric('Invoice Base Sign', digits=(2, 0),
|
|
help='Usualy 1 or -1',
|
|
states={
|
|
'required': Eval('type') != 'none',
|
|
'readonly': Eval('type') == 'none',
|
|
}, depends=['type'])
|
|
invoice_tax_code = fields.Many2One('account.tax.code',
|
|
'Invoice Tax Code',
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
],
|
|
states={
|
|
'readonly': Eval('type') == 'none',
|
|
},
|
|
depends=['type', 'company'])
|
|
invoice_tax_sign = fields.Numeric('Invoice Tax Sign', digits=(2, 0),
|
|
help='Usualy 1 or -1',
|
|
states={
|
|
'required': Eval('type') != 'none',
|
|
'readonly': Eval('type') == 'none',
|
|
}, depends=['type'])
|
|
credit_note_base_code = fields.Many2One('account.tax.code',
|
|
'Credit Note Base Code',
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
],
|
|
states={
|
|
'readonly': Eval('type') == 'none',
|
|
},
|
|
depends=['type', 'company'])
|
|
credit_note_base_sign = fields.Numeric('Credit Note Base Sign',
|
|
digits=(2, 0), help='Usualy 1 or -1',
|
|
states={
|
|
'required': Eval('type') != 'none',
|
|
'readonly': Eval('type') == 'none',
|
|
}, depends=['type'])
|
|
credit_note_tax_code = fields.Many2One('account.tax.code',
|
|
'Credit Note Tax Code',
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
],
|
|
states={
|
|
'readonly': Eval('type') == 'none',
|
|
},
|
|
depends=['type', 'company'])
|
|
credit_note_tax_sign = fields.Numeric('Credit Note Tax Sign',
|
|
digits=(2, 0), help='Usualy 1 or -1',
|
|
states={
|
|
'required': Eval('type') != 'none',
|
|
'readonly': Eval('type') == 'none',
|
|
}, depends=['type'])
|
|
template = fields.Many2One('account.tax.template', 'Template')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Tax, cls).__setup__()
|
|
cls._order.insert(0, ('sequence', 'ASC'))
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
TableHandler = backend.get('TableHandler')
|
|
super(Tax, cls).__register__(module_name)
|
|
cursor = Transaction().cursor
|
|
table = TableHandler(cursor, cls, module_name)
|
|
|
|
# Migration from 1.0 group is no more required
|
|
table.not_null_action('group', action='remove')
|
|
|
|
# Migration from 2.4: drop required on sequence
|
|
table.not_null_action('sequence', action='remove')
|
|
|
|
# Migration from 2.8: rename percentage into rate
|
|
if table.column_exist('percentage'):
|
|
sql_table = cls.__table__()
|
|
cursor.execute(*sql_table.update(
|
|
columns=[sql_table.rate],
|
|
values=[sql_table.percentage / 100]))
|
|
table.drop_column('percentage')
|
|
|
|
@staticmethod
|
|
def order_sequence(tables):
|
|
table, _ = tables[None]
|
|
return [table.sequence == Null, table.sequence]
|
|
|
|
@staticmethod
|
|
def default_active():
|
|
return True
|
|
|
|
@staticmethod
|
|
def default_type():
|
|
return 'percentage'
|
|
|
|
@staticmethod
|
|
def default_include_base_amount():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_invoice_base_sign():
|
|
return Decimal('1')
|
|
|
|
@staticmethod
|
|
def default_invoice_tax_sign():
|
|
return Decimal('1')
|
|
|
|
@staticmethod
|
|
def default_credit_note_base_sign():
|
|
return Decimal('1')
|
|
|
|
@staticmethod
|
|
def default_credit_note_tax_sign():
|
|
return Decimal('1')
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@fields.depends('company')
|
|
def on_change_with_currency_digits(self, name=None):
|
|
if self.company:
|
|
return self.company.currency.digits
|
|
return 2
|
|
|
|
def _process_tax(self, price_unit):
|
|
if self.type == 'percentage':
|
|
amount = price_unit * self.rate
|
|
return {
|
|
'base': price_unit,
|
|
'amount': amount,
|
|
'tax': self,
|
|
}
|
|
if self.type == 'fixed':
|
|
amount = self.amount
|
|
return {
|
|
'base': price_unit,
|
|
'amount': amount,
|
|
'tax': self,
|
|
}
|
|
|
|
@classmethod
|
|
def _unit_compute(cls, taxes, price_unit, date):
|
|
res = []
|
|
for tax in taxes:
|
|
start_date = tax.start_date or datetime.date.min
|
|
end_date = tax.end_date or datetime.date.max
|
|
if not (start_date <= date <= end_date):
|
|
continue
|
|
if tax.type != 'none':
|
|
res.append(tax._process_tax(price_unit))
|
|
if len(tax.childs):
|
|
res.extend(cls._unit_compute(tax.childs, price_unit, date))
|
|
return res
|
|
|
|
@classmethod
|
|
def _reverse_rate_amount(cls, taxes, date):
|
|
rate = None
|
|
amount = 0
|
|
for tax in taxes:
|
|
start_date = tax.start_date or datetime.date.min
|
|
end_date = tax.end_date or datetime.date.max
|
|
if not (start_date <= date <= end_date):
|
|
continue
|
|
|
|
if tax.type == 'percentage':
|
|
if rate is None:
|
|
rate = tax.rate
|
|
else:
|
|
rate *= tax.rate
|
|
elif tax.type == 'fixed':
|
|
amount += tax.amount
|
|
|
|
if tax.childs:
|
|
child_rate, child_amount = cls._reverse_rate_amount(
|
|
tax.childs, date)
|
|
if rate is None:
|
|
rate = child_rate
|
|
else:
|
|
rate *= child_rate
|
|
amount += child_amount
|
|
return rate, amount
|
|
|
|
@classmethod
|
|
def _reverse_unit_compute(cls, price_unit, taxes, date):
|
|
rate, amount = cls._reverse_rate_amount(taxes, date)
|
|
price_unit -= amount
|
|
if rate is not None:
|
|
price_unit = price_unit / (1 + rate)
|
|
return price_unit
|
|
|
|
@classmethod
|
|
def sort_taxes(cls, taxes, reverse=False):
|
|
'''
|
|
Return a list of taxes sorted
|
|
'''
|
|
return sorted(taxes, key=lambda t: (t.sequence, t.id), reverse=reverse)
|
|
|
|
@classmethod
|
|
def compute(cls, taxes, price_unit, quantity, date=None):
|
|
'''
|
|
Compute taxes for price_unit and quantity at the date.
|
|
Return list of dict for each taxes and their childs with:
|
|
base
|
|
amount
|
|
tax
|
|
'''
|
|
pool = Pool()
|
|
Date = pool.get('ir.date')
|
|
if date is None:
|
|
date = Date.today()
|
|
taxes = cls.sort_taxes(taxes)
|
|
res = cls._unit_compute(taxes, price_unit, date)
|
|
quantity = Decimal(str(quantity or 0.0))
|
|
for row in res:
|
|
row['base'] *= quantity
|
|
row['amount'] *= quantity
|
|
return res
|
|
|
|
@classmethod
|
|
def reverse_compute(cls, price_unit, taxes, date=None):
|
|
'''
|
|
Reverse compute the price_unit for taxes at the date.
|
|
'''
|
|
pool = Pool()
|
|
Date = pool.get('ir.date')
|
|
if date is None:
|
|
date = Date.today()
|
|
taxes = cls.sort_taxes(taxes, reverse=True)
|
|
return cls._reverse_unit_compute(price_unit, taxes, date)
|
|
|
|
def update_tax(self, template2tax_code, template2account,
|
|
template2tax=None):
|
|
'''
|
|
Update recursively taxes based on template.
|
|
template2tax_code is a dictionary with tax code template id as key and
|
|
tax code id as value, used to convert tax code template into tax code.
|
|
template2account is a dictionary with account template id as key and
|
|
account id as value, used to convert account template into account
|
|
code.
|
|
template2tax is 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.
|
|
'''
|
|
pool = Pool()
|
|
Lang = pool.get('ir.lang')
|
|
Config = pool.get('ir.configuration')
|
|
|
|
if template2tax is None:
|
|
template2tax = {}
|
|
|
|
if self.template:
|
|
vals = self.template._get_tax_value(tax=self)
|
|
invoice_account_id = (self.invoice_account.id
|
|
if self.invoice_account else None)
|
|
if (self.template.invoice_account and
|
|
invoice_account_id != template2account.get(
|
|
self.template.invoice_account.id)):
|
|
vals['invoice_account'] = template2account.get(
|
|
self.template.invoice_account.id)
|
|
elif (not self.template.invoice_account
|
|
and self.invoice_account):
|
|
vals['invoice_account'] = None
|
|
credit_note_account_id = (self.credit_note_account.id
|
|
if self.credit_note_account else None)
|
|
if (self.template.credit_note_account and
|
|
credit_note_account_id != template2account.get(
|
|
self.template.credit_note_account.id)):
|
|
vals['credit_note_account'] = template2account.get(
|
|
self.template.credit_note_account.id)
|
|
elif (not self.template.credit_note_account
|
|
and self.credit_note_account):
|
|
vals['credit_note_account'] = None
|
|
invoice_base_code_id = (self.invoice_base_code.id
|
|
if self.invoice_base_code else None)
|
|
if (self.template.invoice_base_code and
|
|
invoice_base_code_id != template2tax_code.get(
|
|
self.template.invoice_base_code.id)):
|
|
vals['invoice_base_code'] = template2tax_code.get(
|
|
self.template.invoice_base_code.id)
|
|
elif (not self.template.invoice_base_code
|
|
and self.invoice_base_code):
|
|
vals['invoice_base_code'] = None
|
|
invoice_tax_code_id = (self.invoice_tax_code.id
|
|
if self.invoice_tax_code else None)
|
|
if (self.template.invoice_tax_code
|
|
and invoice_tax_code_id != template2tax_code.get(
|
|
self.template.invoice_tax_code.id)):
|
|
vals['invoice_tax_code'] = template2tax_code.get(
|
|
self.template.invoice_tax_code.id)
|
|
elif (not self.template.invoice_tax_code
|
|
and self.invoice_tax_code):
|
|
vals['invoice_tax_code'] = None
|
|
credit_note_base_code_id = (self.credit_note_base_code.id
|
|
if self.credit_note_base_code else None)
|
|
if (self.template.credit_note_base_code
|
|
and credit_note_base_code_id != template2tax_code.get(
|
|
self.template.credit_note_base_code.id)):
|
|
vals['credit_note_base_code'] = template2tax_code.get(
|
|
self.template.credit_note_base_code.id)
|
|
elif (not self.template.credit_note_base_code
|
|
and self.credit_note_base_code):
|
|
vals['credit_note_base_code'] = None
|
|
credit_note_tax_code_id = (self.credit_note_tax_code.id
|
|
if self.credit_note_tax_code else None)
|
|
if (self.template.credit_note_tax_code
|
|
and credit_note_tax_code_id != template2tax_code.get(
|
|
self.template.credit_note_tax_code.id)):
|
|
vals['credit_note_tax_code'] = template2tax_code.get(
|
|
self.template.credit_note_tax_code.id)
|
|
elif (not self.template.credit_note_tax_code
|
|
and self.credit_note_tax_code):
|
|
vals['credit_note_tax_code'] = None
|
|
|
|
if vals:
|
|
self.write([self], vals)
|
|
|
|
prev_lang = self._context.get('language') or Config.get_language()
|
|
prev_data = {}
|
|
for field_name, field in self.template._fields.iteritems():
|
|
if getattr(field, 'translate', False):
|
|
prev_data[field_name] = getattr(self.template, field_name)
|
|
for lang in Lang.get_translatable_languages():
|
|
if lang == prev_lang:
|
|
continue
|
|
with Transaction().set_context(language=lang):
|
|
tax = self.__class__(self.id)
|
|
data = {}
|
|
for field_name, field in tax.template._fields.iteritems():
|
|
if (getattr(field, 'translate', False)
|
|
and (getattr(tax.template, field_name)
|
|
!= prev_data[field_name])):
|
|
data[field_name] = getattr(tax.template,
|
|
field_name)
|
|
if data:
|
|
self.write([tax], data)
|
|
template2tax[self.template.id] = self.id
|
|
|
|
for child in self.childs:
|
|
child.update_tax(template2tax_code, template2account,
|
|
template2tax=template2tax)
|
|
|
|
|
|
class _TaxKey(dict):
|
|
|
|
def __init__(self, **kwargs):
|
|
self.update(kwargs)
|
|
|
|
def _key(self):
|
|
return (self['base_code'], self['base_sign'],
|
|
self['tax_code'], self['tax_sign'],
|
|
self['account'], self['tax'])
|
|
|
|
def __eq__(self, other):
|
|
if isinstance(other, _TaxKey):
|
|
return self._key() == other._key()
|
|
return self._key() == other
|
|
|
|
def __hash__(self):
|
|
return hash(self._key())
|
|
|
|
_TaxableLine = namedtuple('_TaxableLine', ('taxes', 'unit_price', 'quantity'))
|
|
|
|
|
|
class TaxableMixin(object):
|
|
|
|
@property
|
|
def taxable_lines(self):
|
|
"""A list of tuples where
|
|
- the first element is the taxes applicable
|
|
- the second element is the line unit price
|
|
- the third element is the line quantity
|
|
"""
|
|
return []
|
|
|
|
@property
|
|
def tax_type(self):
|
|
"The taxation type it can be 'invoice' or 'credit_note'"
|
|
return None
|
|
|
|
@property
|
|
def currency(self):
|
|
"The currency used by the taxable object"
|
|
return None
|
|
|
|
@property
|
|
def tax_date(self):
|
|
"Date to use when computing the tax"
|
|
pool = Pool()
|
|
Date = pool.get('ir.date')
|
|
return Date.today()
|
|
|
|
def _get_tax_context(self):
|
|
return {}
|
|
|
|
@staticmethod
|
|
def _compute_tax_line(type_, amount, base, tax):
|
|
assert type_ in ('invoice', 'credit_note')
|
|
|
|
line = {}
|
|
line['manual'] = False
|
|
line['description'] = tax.description
|
|
line['base'] = base
|
|
line['amount'] = amount
|
|
line['tax'] = tax.id if tax else None
|
|
|
|
for attribute in ['base_code', 'tax_code', 'account']:
|
|
value = getattr(tax, '%s_%s' % (type_, attribute), None)
|
|
line[attribute] = value.id if value else None
|
|
|
|
for attribute in ['base_sign', 'tax_sign']:
|
|
value = getattr(tax, '%s_%s' % (type_, attribute), None)
|
|
line[attribute] = value
|
|
|
|
return _TaxKey(**line)
|
|
|
|
def _round_taxes(self, taxes):
|
|
if not self.currency:
|
|
return
|
|
for taxline in taxes.itervalues():
|
|
for attribute in ('base', 'amount'):
|
|
taxline[attribute] = self.currency.round(taxline[attribute])
|
|
|
|
def _get_taxes(self):
|
|
pool = Pool()
|
|
Tax = pool.get('account.tax')
|
|
Configuration = pool.get('account.configuration')
|
|
|
|
config = Configuration(1)
|
|
taxes = {}
|
|
with Transaction().set_context(self._get_tax_context()):
|
|
taxable_lines = [_TaxableLine(*params)
|
|
for params in self.taxable_lines]
|
|
for line in taxable_lines:
|
|
l_taxes = Tax.compute(line.taxes, line.unit_price,
|
|
line.quantity, self.tax_date)
|
|
for tax in l_taxes:
|
|
taxline = self._compute_tax_line(self.tax_type, **tax)
|
|
if taxline not in taxes:
|
|
taxes[taxline] = taxline
|
|
else:
|
|
taxes[taxline]['base'] += taxline['base']
|
|
taxes[taxline]['amount'] += taxline['amount']
|
|
if config.tax_rounding == 'line':
|
|
self._round_taxes(taxes)
|
|
if config.tax_rounding == 'document':
|
|
self._round_taxes(taxes)
|
|
return taxes
|
|
|
|
|
|
class TaxLine(ModelSQL, ModelView):
|
|
'Tax Line'
|
|
__name__ = 'account.tax.line'
|
|
_rec_name = 'amount'
|
|
currency_digits = fields.Function(fields.Integer('Currency Digits'),
|
|
'on_change_with_currency_digits')
|
|
amount = fields.Numeric('Amount', digits=(16, Eval('currency_digits', 2)),
|
|
required=True, depends=['currency_digits'])
|
|
code = fields.Many2One('account.tax.code', 'Code', select=True,
|
|
required=True,
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
],
|
|
depends=['company'])
|
|
tax = fields.Many2One('account.tax', 'Tax', select=True,
|
|
ondelete='RESTRICT',
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
],
|
|
depends=['company'])
|
|
move_line = fields.Many2One('account.move.line', 'Move Line',
|
|
required=True, select=True, ondelete='CASCADE')
|
|
company = fields.Function(fields.Many2One('company.company', 'Company'),
|
|
'on_change_with_company')
|
|
|
|
@fields.depends('move_line')
|
|
def on_change_with_currency_digits(self, name=None):
|
|
if self.move_line:
|
|
return self.move_line.currency_digits
|
|
return 2
|
|
|
|
@fields.depends('tax')
|
|
def on_change_tax(self):
|
|
self.code = None
|
|
|
|
@fields.depends('_parent_move_line.account')
|
|
def on_change_with_company(self, name=None):
|
|
if self.move_line:
|
|
return self.move_line.account.company.id
|
|
|
|
|
|
class TaxRuleTemplate(ModelSQL, ModelView):
|
|
'Tax Rule Template'
|
|
__name__ = 'account.tax.rule.template'
|
|
name = fields.Char('Name', required=True, translate=True)
|
|
kind = fields.Selection(KINDS, 'Kind', required=True)
|
|
lines = fields.One2Many('account.tax.rule.line.template', 'rule', 'Lines')
|
|
account = fields.Many2One('account.account.template', 'Account Template',
|
|
domain=[('parent', '=', None)], required=True)
|
|
|
|
@staticmethod
|
|
def default_kind():
|
|
return 'both'
|
|
|
|
def _get_tax_rule_value(self, rule=None):
|
|
'''
|
|
Set values for tax rule creation.
|
|
'''
|
|
res = {}
|
|
if not rule or rule.name != self.name:
|
|
res['name'] = self.name
|
|
if not rule or rule.kind != self.kind:
|
|
res['kind'] = self.kind
|
|
if not rule or rule.template.id != self.id:
|
|
res['template'] = self.id
|
|
return res
|
|
|
|
def create_rule(self, company_id, template2rule=None):
|
|
'''
|
|
Create tax rule based on template.
|
|
template2rule is 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
|
|
'''
|
|
pool = Pool()
|
|
Rule = pool.get('account.tax.rule')
|
|
Lang = pool.get('ir.lang')
|
|
Config = pool.get('ir.configuration')
|
|
|
|
if template2rule is None:
|
|
template2rule = {}
|
|
|
|
if self.id not in template2rule:
|
|
vals = self._get_tax_rule_value()
|
|
vals['company'] = company_id
|
|
new_rule, = Rule.create([vals])
|
|
|
|
prev_lang = self._context.get('language') or Config.get_language()
|
|
prev_data = {}
|
|
for field_name, field in self._fields.iteritems():
|
|
if getattr(field, 'translate', False):
|
|
prev_data[field_name] = getattr(self, field_name)
|
|
for lang in Lang.get_translatable_languages():
|
|
if lang == prev_lang:
|
|
continue
|
|
with Transaction().set_context(language=lang):
|
|
template = self.__class__(self.id)
|
|
data = {}
|
|
for field_name, field in template._fields.iteritems():
|
|
if (getattr(field, 'translate', False)
|
|
and (getattr(self, field_name)
|
|
!= prev_data[field_name])):
|
|
data[field_name] = getattr(self, field_name)
|
|
if data:
|
|
Rule.write([new_rule], data)
|
|
template2rule[self.id] = new_rule.id
|
|
return template2rule[self.id]
|
|
|
|
|
|
class TaxRule(ModelSQL, ModelView):
|
|
'Tax Rule'
|
|
__name__ = 'account.tax.rule'
|
|
name = fields.Char('Name', required=True, translate=True)
|
|
kind = fields.Selection(KINDS, 'Kind', required=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True,
|
|
select=True, domain=[
|
|
('id', If(Eval('context', {}).contains('company'), '=', '!='),
|
|
Eval('context', {}).get('company', -1)),
|
|
])
|
|
lines = fields.One2Many('account.tax.rule.line', 'rule', 'Lines')
|
|
template = fields.Many2One('account.tax.rule.template', 'Template')
|
|
|
|
@staticmethod
|
|
def default_kind():
|
|
return 'both'
|
|
|
|
def apply(self, tax, pattern):
|
|
'''
|
|
Apply rule on tax
|
|
pattern is a dictonary with rule line field as key and match value as
|
|
value.
|
|
Return a list of the tax id to use or None
|
|
'''
|
|
pattern = pattern.copy()
|
|
pattern['group'] = tax.group.id if tax and tax.group else None
|
|
pattern['origin_tax'] = tax.id if tax else None
|
|
|
|
for line in self.lines:
|
|
if line.match(pattern):
|
|
return line.get_taxes()
|
|
return tax and [tax.id] or None
|
|
|
|
def update_rule(self, template2rule=None):
|
|
'''
|
|
Update tax rule based on template.
|
|
template2rule is 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.
|
|
'''
|
|
pool = Pool()
|
|
Lang = pool.get('ir.lang')
|
|
Config = pool.get('ir.configuration')
|
|
|
|
if template2rule is None:
|
|
template2rule = {}
|
|
|
|
if self.template:
|
|
vals = self.template._get_tax_rule_value(rule=self)
|
|
if vals:
|
|
self.write([self], vals)
|
|
|
|
prev_lang = self._context.get('language') or Config.get_language()
|
|
prev_data = {}
|
|
for field_name, field in self.template._fields.iteritems():
|
|
if getattr(field, 'translate', False):
|
|
prev_data[field_name] = getattr(self.template, field_name)
|
|
for lang in Lang.get_translatable_languages():
|
|
if lang == prev_lang:
|
|
continue
|
|
with Transaction().set_context(language=lang):
|
|
rule = self.__class__(self.id)
|
|
data = {}
|
|
for field_name, field in rule.template._fields.iteritems():
|
|
if (getattr(field, 'translate', False)
|
|
and (getattr(rule.template, field_name)
|
|
!= prev_data[field_name])):
|
|
data[field_name] = getattr(rule.template,
|
|
field_name)
|
|
if data:
|
|
self.write([rule], data)
|
|
template2rule[self.template.id] = self.id
|
|
|
|
|
|
class TaxRuleLineTemplate(ModelSQL, ModelView):
|
|
'Tax Rule Line Template'
|
|
__name__ = 'account.tax.rule.line.template'
|
|
rule = fields.Many2One('account.tax.rule.template', 'Rule', required=True,
|
|
ondelete='CASCADE')
|
|
group = fields.Many2One('account.tax.group', 'Tax Group',
|
|
ondelete='RESTRICT')
|
|
origin_tax = fields.Many2One('account.tax.template', 'Original Tax',
|
|
domain=[
|
|
('account', '=', Eval('_parent_rule', {}).get('account', 0)),
|
|
('group', '=', Eval('group')),
|
|
['OR',
|
|
('group', '=', None),
|
|
If(Eval('_parent_rule', {}).get('kind', 'both') == 'sale',
|
|
('group.kind', 'in', ['sale', 'both']),
|
|
If(Eval('_parent_rule', {}).get('kind', 'both') ==
|
|
'purchase',
|
|
('group.kind', 'in', ['purchase', 'both']),
|
|
('group.kind', 'in', ['sale', 'purchase', 'both']))),
|
|
],
|
|
],
|
|
help=('If the original tax template is filled, the rule will be '
|
|
'applied only for this tax template.'),
|
|
depends=['group'],
|
|
ondelete='RESTRICT')
|
|
tax = fields.Many2One('account.tax.template', 'Substitution Tax',
|
|
domain=[
|
|
('account', '=', Eval('_parent_rule', {}).get('account', 0)),
|
|
('group', '=', Eval('group')),
|
|
['OR',
|
|
('group', '=', None),
|
|
If(Eval('_parent_rule', {}).get('kind', 'both') == 'sale',
|
|
('group.kind', 'in', ['sale', 'both']),
|
|
If(Eval('_parent_rule', {}).get('kind', 'both') ==
|
|
'purchase',
|
|
('group.kind', 'in', ['purchase', 'both']),
|
|
('group.kind', 'in', ['sale', 'purchase', 'both']))),
|
|
],
|
|
],
|
|
depends=['group'],
|
|
ondelete='RESTRICT')
|
|
sequence = fields.Integer('Sequence')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(TaxRuleLineTemplate, cls).__setup__()
|
|
cls._order.insert(0, ('rule', 'ASC'))
|
|
cls._order.insert(0, ('sequence', 'ASC'))
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
TableHandler = backend.get('TableHandler')
|
|
cursor = Transaction().cursor
|
|
table = TableHandler(cursor, cls, module_name)
|
|
|
|
super(TaxRuleLineTemplate, cls).__register__(module_name)
|
|
|
|
# Migration from 2.4: drop required on sequence
|
|
table.not_null_action('sequence', action='remove')
|
|
|
|
@staticmethod
|
|
def order_sequence(tables):
|
|
table, _ = tables[None]
|
|
return [table.sequence == Null, table.sequence]
|
|
|
|
def _get_tax_rule_line_value(self, rule_line=None):
|
|
'''
|
|
Set values for tax rule line creation.
|
|
'''
|
|
res = {}
|
|
if not rule_line or rule_line.group != self.group:
|
|
res['group'] = self.group.id if self.group else None
|
|
if not rule_line or rule_line.sequence != self.sequence:
|
|
res['sequence'] = self.sequence
|
|
if not rule_line or rule_line.template != self:
|
|
res['template'] = self.id
|
|
return res
|
|
|
|
def create_rule_line(self, template2tax, template2rule,
|
|
template2rule_line=None):
|
|
'''
|
|
Create tax rule line based on template.
|
|
template2tax is a dictionary with tax template id as key and tax id as
|
|
value, used to convert template id into tax.
|
|
template2rule is a dictionary with tax rule template id as key and tax
|
|
rule id as value, used to convert template id into tax rule.
|
|
template2rule_line is 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
|
|
'''
|
|
RuleLine = Pool().get('account.tax.rule.line')
|
|
|
|
if template2rule_line is None:
|
|
template2rule_line = {}
|
|
|
|
if self.id not in template2rule_line:
|
|
vals = self._get_tax_rule_line_value()
|
|
vals['rule'] = template2rule[self.rule.id]
|
|
if self.origin_tax:
|
|
vals['origin_tax'] = template2tax[self.origin_tax.id]
|
|
else:
|
|
vals['origin_tax'] = None
|
|
if self.tax:
|
|
vals['tax'] = template2tax[self.tax.id]
|
|
else:
|
|
vals['tax'] = None
|
|
new_rule_line, = RuleLine.create([vals])
|
|
template2rule_line[self.id] = new_rule_line.id
|
|
return template2rule_line[self.id]
|
|
|
|
|
|
class TaxRuleLine(ModelSQL, ModelView, MatchMixin):
|
|
'Tax Rule Line'
|
|
__name__ = 'account.tax.rule.line'
|
|
_rec_name = 'tax'
|
|
rule = fields.Many2One('account.tax.rule', 'Rule', required=True,
|
|
select=True, ondelete='CASCADE')
|
|
group = fields.Many2One('account.tax.group', 'Tax Group',
|
|
ondelete='RESTRICT')
|
|
origin_tax = fields.Many2One('account.tax', 'Original Tax',
|
|
domain=[
|
|
('company', '=', Eval('_parent_rule', {}).get('company')),
|
|
('group', '=', Eval('group')),
|
|
['OR',
|
|
('group', '=', None),
|
|
If(Eval('_parent_rule', {}).get('kind', 'both') == 'sale',
|
|
('group.kind', 'in', ['sale', 'both']),
|
|
If(Eval('_parent_rule', {}).get('kind', 'both') ==
|
|
'purchase',
|
|
('group.kind', 'in', ['purchase', 'both']),
|
|
('group.kind', 'in', ['sale', 'purchase', 'both']))),
|
|
],
|
|
],
|
|
help=('If the original tax is filled, the rule will be applied '
|
|
'only for this tax.'),
|
|
depends=['group'],
|
|
ondelete='RESTRICT')
|
|
tax = fields.Many2One('account.tax', 'Substitution Tax',
|
|
domain=[
|
|
('company', '=', Eval('_parent_rule', {}).get('company')),
|
|
('group', '=', Eval('group')),
|
|
['OR',
|
|
('group', '=', None),
|
|
If(Eval('_parent_rule', {}).get('kind', 'both') == 'sale',
|
|
('group.kind', 'in', ['sale', 'both']),
|
|
If(Eval('_parent_rule', {}).get('kind', 'both') ==
|
|
'purchase',
|
|
('group.kind', 'in', ['purchase', 'both']),
|
|
('group.kind', 'in', ['sale', 'purchase', 'both']))),
|
|
],
|
|
],
|
|
depends=['group'],
|
|
ondelete='RESTRICT')
|
|
sequence = fields.Integer('Sequence')
|
|
template = fields.Many2One('account.tax.rule.line.template', 'Template')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(TaxRuleLine, cls).__setup__()
|
|
cls._order.insert(0, ('rule', 'ASC'))
|
|
cls._order.insert(0, ('sequence', 'ASC'))
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
TableHandler = backend.get('TableHandler')
|
|
cursor = Transaction().cursor
|
|
table = TableHandler(cursor, cls, module_name)
|
|
|
|
super(TaxRuleLine, cls).__register__(module_name)
|
|
|
|
# Migration from 2.4: drop required on sequence
|
|
table.not_null_action('sequence', action='remove')
|
|
|
|
@staticmethod
|
|
def order_sequence(tables):
|
|
table, _ = tables[None]
|
|
return [table.sequence == Null, table.sequence]
|
|
|
|
def match(self, pattern):
|
|
if 'group' in pattern and not self.group:
|
|
if pattern['group']:
|
|
return False
|
|
return super(TaxRuleLine, self).match(pattern)
|
|
|
|
def get_taxes(self):
|
|
'''
|
|
Return list of taxes for a line
|
|
'''
|
|
if self.tax:
|
|
return [self.tax.id]
|
|
return None
|
|
|
|
def update_rule_line(self, template2tax, template2rule,
|
|
template2rule_line=None):
|
|
'''
|
|
Update tax rule line based on template.
|
|
template2tax is a dictionary with tax template id as key and tax id as
|
|
value, used to convert template id into tax.
|
|
template2rule is a dictionary with tax rule template id as key and tax
|
|
rule id as value, used to convert template id into tax rule.
|
|
template2rule_line is 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.
|
|
'''
|
|
if template2rule_line is None:
|
|
template2rule_line = {}
|
|
|
|
if self.template:
|
|
vals = self.template._get_tax_rule_line_value(rule_line=self)
|
|
if self.rule.id != template2rule[self.template.rule.id]:
|
|
vals['rule'] = template2rule[self.template.rule.id]
|
|
if self.origin_tax:
|
|
if self.template.origin_tax:
|
|
if (self.origin_tax.id !=
|
|
template2tax[self.template.origin_tax.id]):
|
|
vals['origin_tax'] = template2tax[
|
|
self.template.origin_tax.id]
|
|
else:
|
|
vals['origin_tax'] = None
|
|
elif self.template.origin_tax:
|
|
vals['origin_tax'] = template2tax[self.template.origin_tax.id]
|
|
if self.tax:
|
|
if self.template.tax:
|
|
if self.tax.id != template2tax[self.template.tax.id]:
|
|
vals['tax'] = template2tax[self.template.tax.id]
|
|
else:
|
|
vals['tax'] = None
|
|
elif self.template.tax:
|
|
vals['tax'] = template2tax[self.template.tax.id]
|
|
if vals:
|
|
self.write([self], vals)
|
|
template2rule_line[self.template.id] = self.id
|
|
|
|
|
|
class OpenTaxCode(Wizard):
|
|
'Open Code'
|
|
__name__ = 'account.tax.open_code'
|
|
start_state = 'open_'
|
|
open_ = StateAction('account.act_tax_line_form')
|
|
|
|
def do_open_(self, action):
|
|
pool = Pool()
|
|
FiscalYear = pool.get('account.fiscalyear')
|
|
Period = pool.get('account.period')
|
|
|
|
if not Transaction().context.get('fiscalyear'):
|
|
fiscalyears = FiscalYear.search([
|
|
('state', '=', 'open'),
|
|
])
|
|
else:
|
|
fiscalyears = [FiscalYear(Transaction().context['fiscalyear'])]
|
|
|
|
periods = []
|
|
if not Transaction().context.get('periods'):
|
|
periods = Period.search([
|
|
('fiscalyear', 'in', [f.id for f in fiscalyears]),
|
|
])
|
|
else:
|
|
periods = Period.browse(Transaction().context['periods'])
|
|
|
|
action['pyson_domain'] = PYSONEncoder().encode([
|
|
('move_line.period', 'in', [p.id for p in periods]),
|
|
('code', '=', Transaction().context['active_id']),
|
|
])
|
|
if Transaction().context.get('fiscalyear'):
|
|
action['pyson_context'] = PYSONEncoder().encode({
|
|
'fiscalyear': Transaction().context['fiscalyear'],
|
|
})
|
|
else:
|
|
action['pyson_context'] = PYSONEncoder().encode({
|
|
'periods': [p.id for p in periods],
|
|
})
|
|
return action, {}
|
|
|
|
|
|
class AccountTemplateTaxTemplate(ModelSQL):
|
|
'Account Template - Tax Template'
|
|
__name__ = 'account.account.template-account.tax.template'
|
|
_table = 'account_account_template_tax_rel'
|
|
account = fields.Many2One('account.account.template', 'Account Template',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
tax = fields.Many2One('account.tax.template', 'Tax Template',
|
|
ondelete='RESTRICT', select=True, required=True)
|
|
|
|
|
|
class AccountTemplate2:
|
|
__name__ = 'account.account.template'
|
|
taxes = fields.Many2Many('account.account.template-account.tax.template',
|
|
'account', 'tax', 'Default Taxes',
|
|
domain=[('parent', '=', None)])
|
|
|
|
|
|
class AccountTax(ModelSQL):
|
|
'Account - Tax'
|
|
__name__ = 'account.account-account.tax'
|
|
_table = 'account_account_tax_rel'
|
|
account = fields.Many2One('account.account', 'Account', ondelete='CASCADE',
|
|
select=True, required=True)
|
|
tax = fields.Many2One('account.tax', 'Tax', ondelete='RESTRICT',
|
|
select=True, required=True)
|
|
|
|
|
|
class Account2:
|
|
__name__ = 'account.account'
|
|
taxes = fields.Many2Many('account.account-account.tax',
|
|
'account', 'tax', 'Default Taxes',
|
|
domain=[
|
|
('company', '=', Eval('company')),
|
|
('parent', '=', None),
|
|
],
|
|
help=('Default tax for manual encoding of move lines \n'
|
|
'for journal types: "expense" and "revenue"'),
|
|
depends=['company'])
|