Since 6c611c520744, the readonly of field is enforced so the copied value of readonly field must be the default value to skip the check.
3465 lines
126 KiB
Python
3465 lines
126 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
|
|
import operator
|
|
from collections import defaultdict
|
|
from decimal import Decimal
|
|
from itertools import groupby, zip_longest
|
|
|
|
from dateutil.relativedelta import relativedelta
|
|
from sql import Column, Literal, Null, Window
|
|
from sql.aggregate import Count, Sum
|
|
from sql.conditionals import Case, Coalesce
|
|
|
|
from trytond import backend
|
|
from trytond.i18n import gettext
|
|
from trytond.model import (
|
|
Check, Index, ModelSQL, ModelView, Unique, fields, sequence_ordered,
|
|
sum_tree, tree)
|
|
from trytond.model.exceptions import AccessError
|
|
from trytond.modules.currency.fields import Monetary
|
|
from trytond.pool import Pool
|
|
from trytond.pyson import Bool, Eval, Id, If, PYSONEncoder
|
|
from trytond.report import Report
|
|
from trytond.tools import (
|
|
is_full_text, lstrip_wildcard, pair, sql_pairing, sqlite_apply_types)
|
|
from trytond.transaction import Transaction, check_access, inactive_records
|
|
from trytond.wizard import (
|
|
Button, StateAction, StateTransition, StateView, Wizard)
|
|
|
|
from .common import ActivePeriodMixin, ContextCompanyMixin, PeriodMixin
|
|
from .exceptions import (
|
|
AccountValidationError, ChartWarning, FiscalYearNotFoundError,
|
|
SecondCurrencyError)
|
|
from .move import DescriptionOriginMixin
|
|
|
|
|
|
def TypeMixin(template=False):
|
|
|
|
class Mixin:
|
|
__slots__ = ()
|
|
name = fields.Char('Name', required=True)
|
|
|
|
statement = fields.Selection([
|
|
(None, ""),
|
|
('balance', "Balance"),
|
|
('income', "Income"),
|
|
('off-balance', "Off-Balance"),
|
|
], "Statement",
|
|
states={
|
|
'required': Bool(Eval('parent')),
|
|
})
|
|
assets = fields.Boolean(
|
|
"Assets",
|
|
states={
|
|
'invisible': Eval('statement') != 'balance',
|
|
})
|
|
|
|
receivable = fields.Boolean(
|
|
"Receivable",
|
|
domain=[
|
|
If((Eval('statement') != 'balance')
|
|
| ~Eval('assets', True),
|
|
('receivable', '=', False), ()),
|
|
],
|
|
states={
|
|
'invisible': ((Eval('statement') != 'balance')
|
|
| ~Eval('assets', True)),
|
|
})
|
|
stock = fields.Boolean(
|
|
"Stock",
|
|
domain=[
|
|
If(Eval('statement') == 'off-balance',
|
|
('stock', '=', False), ()),
|
|
],
|
|
states={
|
|
'invisible': Eval('statement') == 'off-balance',
|
|
})
|
|
|
|
payable = fields.Boolean(
|
|
"Payable",
|
|
domain=[
|
|
If((Eval('statement') != 'balance')
|
|
| Eval('assets', False),
|
|
('payable', '=', False), ()),
|
|
],
|
|
states={
|
|
'invisible': ((Eval('statement') != 'balance')
|
|
| Eval('assets', False)),
|
|
})
|
|
|
|
debt = fields.Boolean(
|
|
"Debt",
|
|
domain=[
|
|
If((Eval('statement') != 'balance')
|
|
| Eval('assets', False),
|
|
('debt', '=', False), ()),
|
|
],
|
|
states={
|
|
'invisible': ((Eval('statement') != 'balance')
|
|
| Eval('assets', False)),
|
|
},
|
|
help="Check to allow booking debt via supplier invoice.")
|
|
|
|
revenue = fields.Boolean(
|
|
"Revenue",
|
|
domain=[
|
|
If(Eval('statement') != 'income',
|
|
('revenue', '=', False), ()),
|
|
],
|
|
states={
|
|
'invisible': Eval('statement') != 'income',
|
|
})
|
|
|
|
expense = fields.Boolean(
|
|
"Expense",
|
|
domain=[
|
|
If(Eval('statement') != 'income',
|
|
('expense', '=', False), ()),
|
|
],
|
|
states={
|
|
'invisible': Eval('statement') != 'income',
|
|
})
|
|
if not template:
|
|
for fname in dir(Mixin):
|
|
field = getattr(Mixin, fname)
|
|
if not isinstance(field, fields.Field):
|
|
continue
|
|
field.states['ediable'] = (
|
|
~Eval('template', None) | Eval('template_override', False))
|
|
return Mixin
|
|
|
|
|
|
class TypeTemplate(
|
|
TypeMixin(template=True), sequence_ordered(), tree(separator='\\'),
|
|
ModelSQL, ModelView):
|
|
__name__ = 'account.account.type.template'
|
|
parent = fields.Many2One(
|
|
'account.account.type.template', "Parent",
|
|
domain=['OR',
|
|
If(Eval('statement') == 'off-balance',
|
|
('statement', '=', 'off-balance'),
|
|
If(Eval('statement') == 'balance',
|
|
('statement', '=', 'balance'),
|
|
('statement', '!=', 'off-balance')),
|
|
),
|
|
('statement', '=', None),
|
|
])
|
|
childs = fields.One2Many(
|
|
'account.account.type.template', 'parent', "Children")
|
|
|
|
def _get_type_value(self, type=None):
|
|
'''
|
|
Set the values for account creation.
|
|
'''
|
|
res = {}
|
|
if not type or type.name != self.name:
|
|
res['name'] = self.name
|
|
if not type or type.sequence != self.sequence:
|
|
res['sequence'] = self.sequence
|
|
if not type or type.statement != self.statement:
|
|
res['statement'] = self.statement
|
|
if not type or type.assets != self.assets:
|
|
res['assets'] = self.assets
|
|
for boolean in [
|
|
'receivable', 'stock', 'payable', 'revenue', 'expense',
|
|
'debt']:
|
|
if not type or getattr(type, boolean) != getattr(self, boolean):
|
|
res[boolean] = getattr(self, boolean)
|
|
if not type or type.template != self:
|
|
res['template'] = self.id
|
|
return res
|
|
|
|
def create_type(self, company_id, template2type=None):
|
|
'''
|
|
Create recursively types based on template.
|
|
template2type is a dictionary with template id as key and type id as
|
|
value, used to convert template id into type. The dictionary is filled
|
|
with new types.
|
|
'''
|
|
pool = Pool()
|
|
Type = pool.get('account.account.type')
|
|
assert self.parent is None
|
|
|
|
if template2type is None:
|
|
template2type = {}
|
|
|
|
def create(templates):
|
|
values = []
|
|
created = []
|
|
for template in templates:
|
|
if template.id not in template2type:
|
|
vals = template._get_type_value()
|
|
vals['company'] = company_id
|
|
if template.parent:
|
|
vals['parent'] = template2type[template.parent.id]
|
|
else:
|
|
vals['parent'] = None
|
|
values.append(vals)
|
|
created.append(template)
|
|
|
|
types = Type.create(values)
|
|
for template, type_ in zip(created, types):
|
|
template2type[template.id] = type_.id
|
|
|
|
childs = [self]
|
|
while childs:
|
|
create(childs)
|
|
childs = sum((c.childs for c in childs), ())
|
|
|
|
|
|
class Type(
|
|
TypeMixin(), sequence_ordered(), tree(separator='\\'),
|
|
ModelSQL, ModelView):
|
|
__name__ = 'account.account.type'
|
|
|
|
parent = fields.Many2One(
|
|
'account.account.type', 'Parent', ondelete='RESTRICT',
|
|
states={
|
|
'editable': (
|
|
~Eval('template', None)
|
|
| Eval('template_override', False)),
|
|
},
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
['OR',
|
|
If(Eval('statement') == 'off-balance',
|
|
('statement', '=', 'off-balance'),
|
|
If(Eval('statement') == 'balance',
|
|
('statement', '=', 'balance'),
|
|
('statement', '!=', 'off-balance')),
|
|
),
|
|
('statement', '=', None),
|
|
],
|
|
])
|
|
childs = fields.One2Many('account.account.type', 'parent', 'Children',
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
])
|
|
currency = fields.Function(fields.Many2One(
|
|
'currency.currency', "Currency"),
|
|
'get_currency', searcher='search_currency')
|
|
amount = fields.Function(Monetary(
|
|
"Amount", currency='currency', digits='currency'),
|
|
'get_amount')
|
|
amount_cmp = fields.Function(Monetary(
|
|
"Amount", currency='currency', digits='currency'),
|
|
'get_amount_cmp')
|
|
|
|
company = fields.Many2One('company.company', 'Company', required=True,
|
|
ondelete="RESTRICT")
|
|
template = fields.Many2One('account.account.type.template', 'Template')
|
|
template_override = fields.Boolean('Override Template',
|
|
help="Check to override template definition",
|
|
states={
|
|
'invisible': ~Bool(Eval('template', -1)),
|
|
})
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
table = cls.__table__()
|
|
cls._sql_indexes.update({
|
|
# Index for receivable/payable balance
|
|
Index(
|
|
table,
|
|
(table.id, Index.Range(cardinality='high')),
|
|
where=table.receivable | table.payable),
|
|
})
|
|
|
|
@classmethod
|
|
def default_template_override(cls):
|
|
return False
|
|
|
|
@classmethod
|
|
def default_company(cls):
|
|
return Transaction().context.get('company')
|
|
|
|
@fields.depends('parent', '_parent_parent.statement')
|
|
def on_change_parent(self):
|
|
if self.parent:
|
|
self.statement = self.parent.statement
|
|
|
|
def get_currency(self, name):
|
|
return self.company.currency.id
|
|
|
|
@classmethod
|
|
def search_currency(cls, name, clause):
|
|
return [('company.' + clause[0], *clause[1:])]
|
|
|
|
@classmethod
|
|
def get_amount(cls, types, name):
|
|
pool = Pool()
|
|
Account = pool.get('account.account')
|
|
GeneralLedger = pool.get('account.general_ledger.account')
|
|
context = Transaction().context
|
|
|
|
childs = cls.search([
|
|
('parent', 'child_of', [t.id for t in types]),
|
|
])
|
|
|
|
period_ids = from_date = to_date = None
|
|
if context.get('start_period') or context.get('end_period'):
|
|
start_period_ids = GeneralLedger.get_period_ids('start_%s' % name)
|
|
end_period_ids = GeneralLedger.get_period_ids('end_%s' % name)
|
|
period_ids = list(
|
|
set(end_period_ids).difference(set(start_period_ids)))
|
|
elif context.get('from_date') or context.get('to_date'):
|
|
from_date = context.get('from_date')
|
|
to_date = context.get('to_date')
|
|
|
|
with Transaction().set_context(
|
|
periods=period_ids, from_date=from_date, to_date=to_date):
|
|
accounts = Account.search([
|
|
('type', 'in', [t.id for t in childs]),
|
|
])
|
|
debit_credit_accounts = Account.search([
|
|
('type', '!=', None),
|
|
['OR',
|
|
('debit_type', 'in', [t.id for t in childs]),
|
|
('credit_type', 'in', [t.id for t in childs]),
|
|
],
|
|
])
|
|
|
|
values = defaultdict(Decimal)
|
|
for account in accounts:
|
|
balance = account.credit - account.debit
|
|
if ((not account.debit_type or balance > 0)
|
|
and (not account.credit_type or balance < 0)):
|
|
values[account.type.id] += balance
|
|
for account in debit_credit_accounts:
|
|
balance = account.credit - account.debit
|
|
if account.debit_type and balance < 0:
|
|
values[account.debit_type.id] += balance
|
|
elif account.credit_type and balance > 0:
|
|
values[account.credit_type.id] += balance
|
|
|
|
result = sum_tree(childs, values)
|
|
for type_ in types:
|
|
result[type_.id] = type_.currency.round(result[type_.id])
|
|
if type_.statement == 'balance' and type_.assets:
|
|
result[type_.id] *= -1
|
|
return result
|
|
|
|
@classmethod
|
|
def get_amount_cmp(cls, types, name):
|
|
transaction = Transaction()
|
|
current = transaction.context
|
|
if not current.get('comparison'):
|
|
return dict.fromkeys([t.id for t in types], None)
|
|
new = {}
|
|
for key, value in current.items():
|
|
if key.endswith('_cmp'):
|
|
new[key[:-4]] = value
|
|
with transaction.set_context(new):
|
|
return cls.get_amount(types, name)
|
|
|
|
@classmethod
|
|
def view_attributes(cls):
|
|
return super().view_attributes() + [
|
|
('/tree/field[@name="amount_cmp"]', 'tree_invisible',
|
|
~Eval('comparison', False)),
|
|
]
|
|
|
|
@classmethod
|
|
def copy(cls, types, default=None):
|
|
if default is None:
|
|
default = {}
|
|
else:
|
|
default = default.copy()
|
|
default.setdefault('template', None)
|
|
return super().copy(types, default=default)
|
|
|
|
def update_type(self, template2type=None):
|
|
'''
|
|
Update recursively types based on template.
|
|
template2type is a dictionary with template id as key and type id as
|
|
value, used to convert template id into type. The dictionary is filled
|
|
with new types
|
|
'''
|
|
if template2type is None:
|
|
template2type = {}
|
|
|
|
values = []
|
|
childs = [self]
|
|
while childs:
|
|
for child in childs:
|
|
if child.template:
|
|
if not child.template_override:
|
|
vals = child.template._get_type_value(type=child)
|
|
if vals:
|
|
values.append([child])
|
|
values.append(vals)
|
|
template2type[child.template.id] = child.id
|
|
childs = sum((c.childs for c in childs), ())
|
|
if values:
|
|
self.write(*values)
|
|
|
|
# Update parent
|
|
childs = [self]
|
|
with self.__class__.bulk_save() as save:
|
|
while childs:
|
|
for child in childs:
|
|
if child.template:
|
|
if not child.template_override:
|
|
if child.template.parent:
|
|
# Fallback to current parent
|
|
# to keep under the same root
|
|
parent = template2type.get(
|
|
child.template.parent.id,
|
|
child.parent)
|
|
else:
|
|
parent = None
|
|
old_parent = (
|
|
child.parent.id if child.parent else None)
|
|
if parent != old_parent:
|
|
child.parent = parent
|
|
save.push(child)
|
|
childs = sum((c.childs for c in childs), ())
|
|
|
|
|
|
class OpenType(Wizard):
|
|
__name__ = 'account.account.open_type'
|
|
_readonly = True
|
|
start = StateTransition()
|
|
account = StateAction('account.act_account_balance_sheet')
|
|
ledger_account = StateAction('account.act_account_general_ledger')
|
|
|
|
def transition_start(self):
|
|
context_model = Transaction().context.get('context_model')
|
|
if context_model == 'account.balance_sheet.comparison.context':
|
|
return 'account'
|
|
elif context_model == 'account.income_statement.context':
|
|
return 'ledger_account'
|
|
return 'end'
|
|
|
|
def open_action(self, action):
|
|
pool = Pool()
|
|
action['name'] = '%s (%s)' % (action['name'], self.record.rec_name)
|
|
trans_context = Transaction().context
|
|
context = {
|
|
'active_id': trans_context.get('active_id'),
|
|
'active_ids': trans_context.get('active_ids', []),
|
|
'active_model': trans_context.get('active_model'),
|
|
}
|
|
context_model = trans_context.get('context_model')
|
|
if context_model:
|
|
Model = pool.get(context_model)
|
|
for fname in Model._fields.keys():
|
|
if fname == 'id':
|
|
continue
|
|
context[fname] = trans_context.get(fname)
|
|
action['pyson_context'] = PYSONEncoder().encode(context)
|
|
return action, {}
|
|
|
|
do_account = open_action
|
|
do_ledger_account = open_action
|
|
|
|
|
|
class AccountTypeStatement(Report):
|
|
__name__ = 'account.account.type.statement'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
pool = Pool()
|
|
Company = pool.get('company.company')
|
|
context = Transaction().context
|
|
|
|
report_context = super().get_context(records, header, data)
|
|
report_context['company'] = Company(context['company'])
|
|
|
|
if data.get('model_context') is not None:
|
|
Context = pool.get(data['model_context'])
|
|
values = {}
|
|
for field in Context._fields:
|
|
if field in context:
|
|
values[field] = context[field]
|
|
report_context['ctx'] = Context(**values)
|
|
|
|
report_context['types'] = zip_longest(
|
|
records, data.get('paths') or [], fillvalue=[])
|
|
return report_context
|
|
|
|
|
|
def AccountMixin(template=False):
|
|
|
|
class Mixin:
|
|
__slots__ = ()
|
|
_order_name = 'rec_name'
|
|
name = fields.Char("Name", required=True)
|
|
code = fields.Char("Code")
|
|
|
|
closed = fields.Boolean(
|
|
"Closed",
|
|
states={
|
|
'invisible': ~Eval('type'),
|
|
},
|
|
help="Check to prevent posting move on the account.")
|
|
reconcile = fields.Boolean(
|
|
"Reconcile",
|
|
states={
|
|
'invisible': ~Eval('type'),
|
|
},
|
|
help="Allow move lines of this account to be reconciled.")
|
|
|
|
party_required = fields.Boolean('Party Required',
|
|
domain=[
|
|
If(~Eval('type') | ~Eval('deferral', False),
|
|
('party_required', '=', False),
|
|
()),
|
|
],
|
|
states={
|
|
'invisible': ~Eval('type') | ~Eval('deferral', False),
|
|
})
|
|
|
|
general_ledger_balance = fields.Boolean('General Ledger Balance',
|
|
states={
|
|
'invisible': ~Eval('type'),
|
|
},
|
|
help="Display only the balance in the general ledger report.")
|
|
|
|
deferral = fields.Function(fields.Boolean(
|
|
"Deferral",
|
|
states={
|
|
'invisible': ~Eval('type'),
|
|
}),
|
|
'on_change_with_deferral', searcher='search_deferral')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
cls.code.search_unaccented = False
|
|
super().__setup__()
|
|
|
|
if not cls.childs.domain:
|
|
cls.childs.domain = []
|
|
for type_ in ['type', 'debit_type']:
|
|
field = getattr(cls, type_)
|
|
field.domain = [
|
|
If(Eval('parent')
|
|
& Eval('_parent_parent.%s' % type_)
|
|
& Eval('_parent_parent.parent'),
|
|
('id', '=', Eval('_parent_parent.%s' % type_)),
|
|
()),
|
|
]
|
|
field.states['required'] = (
|
|
Eval('parent')
|
|
& Eval('_parent_parent.%s' % type_)
|
|
& Eval('_parent_parent.parent'))
|
|
|
|
cls.childs.domain.append(
|
|
If(Eval(type_) & Eval('parent'),
|
|
(type_, '=', Eval(type_)),
|
|
()))
|
|
|
|
@classmethod
|
|
def default_closed(cls):
|
|
return False
|
|
|
|
@classmethod
|
|
def default_reconcile(cls):
|
|
return False
|
|
|
|
@classmethod
|
|
def default_party_required(cls):
|
|
return False
|
|
|
|
@classmethod
|
|
def default_general_ledger_balance(cls):
|
|
return False
|
|
|
|
@fields.depends('type')
|
|
def on_change_with_deferral(self, name=None):
|
|
return (self.type
|
|
and self.type.statement in {'balance', 'off-balance'})
|
|
|
|
@classmethod
|
|
def search_deferral(cls, name, clause):
|
|
_, operator, value = clause
|
|
if operator in {'in', 'not in'}:
|
|
if operator == 'in':
|
|
operator = '='
|
|
else:
|
|
operator = '!='
|
|
if True in value and False not in value:
|
|
value = '=', True
|
|
elif False in value and True not in value:
|
|
value = '=', False
|
|
else:
|
|
return [('id', operator, None)]
|
|
if ((operator == '=' and value)
|
|
or (operator == '!=' and not value)):
|
|
return [
|
|
('type.statement', 'in', ['balance', 'off-balance']),
|
|
]
|
|
else:
|
|
return ['OR',
|
|
('type', '=', None),
|
|
('type.statement', 'not in', ['balance', 'off-balance']),
|
|
]
|
|
|
|
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):
|
|
_, operator, operand, *extra = clause
|
|
if operator.startswith('!') or operator.startswith('not '):
|
|
bool_op = 'AND'
|
|
else:
|
|
bool_op = 'OR'
|
|
code_value = operand
|
|
if operator.endswith('like') and is_full_text(operand):
|
|
code_value = lstrip_wildcard(operand)
|
|
return [bool_op,
|
|
('code', operator, code_value, *extra),
|
|
(cls._rec_name, operator, operand, *extra),
|
|
]
|
|
|
|
@staticmethod
|
|
def order_rec_name(tables):
|
|
table, _ = tables[None]
|
|
return [table.code, table.name]
|
|
|
|
if not template:
|
|
for fname in dir(Mixin):
|
|
field = getattr(Mixin, fname)
|
|
if (not isinstance(field, fields.Field)
|
|
or isinstance(field, fields.Function)):
|
|
continue
|
|
field.states['editable'] = (
|
|
~Eval('template', None) | Eval('template_override', False))
|
|
return Mixin
|
|
|
|
|
|
class AccountTemplate(
|
|
AccountMixin(template=True), PeriodMixin, tree(), ModelSQL, ModelView):
|
|
__name__ = 'account.account.template'
|
|
type = fields.Many2One(
|
|
'account.account.type.template', "Type")
|
|
debit_type = fields.Many2One(
|
|
'account.account.type.template', "Debit Type",
|
|
domain=[
|
|
If(Eval('credit_type', None),
|
|
('debit_type', '=', None),
|
|
()),
|
|
])
|
|
credit_type = fields.Many2One(
|
|
'account.account.type.template', "Credit Type",
|
|
domain=[
|
|
If(Eval('debit_type', None),
|
|
('credit_type', '=', None),
|
|
()),
|
|
])
|
|
parent = fields.Many2One('account.account.template', "Parent")
|
|
childs = fields.One2Many('account.account.template', 'parent', 'Children')
|
|
taxes = fields.Many2Many('account.account.template-account.tax.template',
|
|
'account', 'tax', 'Default Taxes',
|
|
domain=[('parent', '=', None)])
|
|
replaced_by = fields.Many2One(
|
|
'account.account.template', "Replaced By",
|
|
states={
|
|
'invisible': ~Eval('end_date'),
|
|
})
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls._order.insert(0, ('code', 'ASC'))
|
|
cls._order.insert(1, ('name', 'ASC'))
|
|
table = cls.__table__()
|
|
cls._sql_constraints.append(
|
|
('only_one_debit_credit_types', Check(
|
|
table, (table.debit_type + table.credit_type) == Null),
|
|
'account.msg_only_one_debit_credit_types'))
|
|
|
|
def _get_account_value(self, account=None):
|
|
'''
|
|
Set the values for account creation.
|
|
'''
|
|
res = {}
|
|
if not account or account.name != self.name:
|
|
res['name'] = self.name
|
|
if not account or account.code != self.code:
|
|
res['code'] = self.code
|
|
if not account or account.start_date != self.start_date:
|
|
res['start_date'] = self.start_date
|
|
if not account or account.end_date != self.end_date:
|
|
res['end_date'] = self.end_date
|
|
if not account or account.closed != self.closed:
|
|
res['closed'] = self.closed
|
|
if not account or account.reconcile != self.reconcile:
|
|
res['reconcile'] = self.reconcile
|
|
if not account or account.party_required != self.party_required:
|
|
res['party_required'] = self.party_required
|
|
if (not account
|
|
or account.general_ledger_balance
|
|
!= self.general_ledger_balance):
|
|
res['general_ledger_balance'] = self.general_ledger_balance
|
|
if not account or account.template != self:
|
|
res['template'] = self.id
|
|
return res
|
|
|
|
def create_account(self, company_id, template2account=None,
|
|
template2type=None):
|
|
'''
|
|
Create recursively accounts based on template.
|
|
template2account is a dictionary with template id as key and account id
|
|
as value, used to convert template id into account. The dictionary is
|
|
filled with new accounts
|
|
template2type is a dictionary with type template id as key and type id
|
|
as value, used to convert type template id into type.
|
|
'''
|
|
pool = Pool()
|
|
Account = pool.get('account.account')
|
|
assert self.parent is None
|
|
|
|
if template2account is None:
|
|
template2account = {}
|
|
|
|
if template2type is None:
|
|
template2type = {}
|
|
|
|
def create(templates):
|
|
values = []
|
|
created = []
|
|
for template in templates:
|
|
if template.id not in template2account:
|
|
vals = template._get_account_value()
|
|
vals['company'] = company_id
|
|
if template.parent:
|
|
vals['parent'] = template2account[template.parent.id]
|
|
else:
|
|
vals['parent'] = None
|
|
if template.type:
|
|
vals['type'] = template2type.get(template.type.id)
|
|
else:
|
|
vals['type'] = None
|
|
if template.debit_type:
|
|
vals['debit_type'] = template2type.get(
|
|
template.debit_type.id)
|
|
else:
|
|
vals['debit_type'] = None
|
|
if template.credit_type:
|
|
vals['credit_type'] = template2type.get(
|
|
template.credit_type.id)
|
|
else:
|
|
vals['credit_type'] = None
|
|
values.append(vals)
|
|
created.append(template)
|
|
|
|
accounts = Account.create(values)
|
|
for template, account in zip(created, accounts):
|
|
template2account[template.id] = account.id
|
|
|
|
childs = [self]
|
|
while childs:
|
|
create(childs)
|
|
childs = sum((c.childs for c in childs), ())
|
|
|
|
def update_account2(self, template2account, template2tax,
|
|
template_done=None):
|
|
'''
|
|
Update recursively account taxes and replaced_by based on template.
|
|
template2account is a dictionary with template id as key and account id
|
|
as value, used to convert template id into account.
|
|
template2tax is a dictionary with tax template id as key and tax id as
|
|
value, used to convert tax template id into tax.
|
|
template_done is a list of template id already updated. The list is
|
|
filled.
|
|
'''
|
|
Account = Pool().get('account.account')
|
|
|
|
if template2account is None:
|
|
template2account = {}
|
|
if template2tax is None:
|
|
template2tax = {}
|
|
if template_done is None:
|
|
template_done = []
|
|
|
|
def update(templates):
|
|
to_write = []
|
|
for template in templates:
|
|
if template.id not in template_done:
|
|
template_done.append(template.id)
|
|
account = Account(template2account[template.id])
|
|
if account.template_override:
|
|
continue
|
|
values = {}
|
|
if template.taxes:
|
|
tax_ids = [template2tax[x.id] for x in template.taxes]
|
|
values['taxes'] = [('add', tax_ids)]
|
|
if template.replaced_by:
|
|
values['replaced_by'] = template2account[
|
|
template.replaced_by.id]
|
|
if values:
|
|
to_write.append([account])
|
|
to_write.append(values)
|
|
if to_write:
|
|
Account.write(*to_write)
|
|
|
|
childs = [self]
|
|
while childs:
|
|
update(childs)
|
|
childs = sum((c.childs for c in childs), ())
|
|
|
|
|
|
class AccountTemplateTaxTemplate(ModelSQL):
|
|
__name__ = 'account.account.template-account.tax.template'
|
|
account = fields.Many2One(
|
|
'account.account.template', "Account Template",
|
|
ondelete='CASCADE', required=True)
|
|
tax = fields.Many2One(
|
|
'account.tax.template', "Tax Template",
|
|
ondelete='CASCADE', required=True)
|
|
|
|
@classmethod
|
|
def __register__(cls, module):
|
|
# Migration from 7.0: rename to standard name
|
|
backend.TableHandler.table_rename(
|
|
'account_account_template_tax_rel', cls._table)
|
|
super().__register__(module)
|
|
|
|
|
|
class Account(
|
|
AccountMixin(), ContextCompanyMixin, ActivePeriodMixin, tree(),
|
|
ModelSQL, ModelView):
|
|
__name__ = 'account.account'
|
|
_states = {
|
|
'editable': ~Eval('template', None) | Eval('template_override', False),
|
|
}
|
|
company = fields.Many2One('company.company', 'Company', required=True,
|
|
ondelete="RESTRICT")
|
|
currency = fields.Function(fields.Many2One(
|
|
'currency.currency', "Currency"),
|
|
'get_currency', searcher='search_currency')
|
|
second_currency = fields.Many2One(
|
|
'currency.currency', "Second Currency", ondelete="RESTRICT",
|
|
domain=[
|
|
('id', '!=', Eval('currency', -1)),
|
|
],
|
|
states={
|
|
'editable': _states['editable'],
|
|
'invisible': ~Eval('deferral', False),
|
|
},
|
|
help="Force all moves for this account to have this second currency.")
|
|
type = fields.Many2One(
|
|
'account.account.type', "Type", ondelete='RESTRICT',
|
|
states={
|
|
'editable': _states['editable'],
|
|
},
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
])
|
|
debit_type = fields.Many2One(
|
|
'account.account.type', "Debit Type", ondelete='RESTRICT',
|
|
states={
|
|
'editable': _states['editable'],
|
|
'invisible': (
|
|
~Eval('type') | Eval('credit_type')
|
|
| (_states['editable']) & ~Eval('debit_type')),
|
|
},
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
If(Eval('credit_type', None),
|
|
('debit_type', '=', None),
|
|
()),
|
|
],
|
|
help="The type used if not empty and debit > credit.")
|
|
credit_type = fields.Many2One(
|
|
'account.account.type', "Credit Type", ondelete='RESTRICT',
|
|
states={
|
|
'editable': _states['editable'],
|
|
'invisible': (
|
|
~Eval('type') | Eval('debit_type')
|
|
| (_states['editable']) & ~Eval('credit_type')),
|
|
},
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
If(Eval('debit_type', None),
|
|
('credit_type', '=', None),
|
|
()),
|
|
],
|
|
help="The type used if not empty and debit < credit.")
|
|
parent = fields.Many2One(
|
|
'account.account', "Parent",
|
|
left="left", right="right", ondelete="RESTRICT", states=_states,
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
])
|
|
left = fields.Integer("Left", required=True)
|
|
right = fields.Integer("Right", required=True)
|
|
childs = fields.One2Many(
|
|
'account.account', 'parent', "Children",
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
])
|
|
balance = fields.Function(Monetary(
|
|
"Balance", currency='currency', digits='currency'),
|
|
'get_balance')
|
|
credit = fields.Function(Monetary(
|
|
"Credit", currency='currency', digits='currency',
|
|
states={
|
|
'invisible': ~Eval('line_count', -1),
|
|
}),
|
|
'get_credit_debit')
|
|
debit = fields.Function(Monetary(
|
|
"Debit", currency='currency', digits='currency',
|
|
states={
|
|
'invisible': ~Eval('line_count', -1),
|
|
}),
|
|
'get_credit_debit')
|
|
amount_second_currency = fields.Function(Monetary(
|
|
"Amount Second Currency",
|
|
currency='second_currency', digits='second_currency',
|
|
states={
|
|
'invisible': ~Eval('second_currency'),
|
|
}),
|
|
'get_credit_debit')
|
|
line_count = fields.Function(
|
|
fields.Integer("Line Count"), 'get_credit_debit')
|
|
note = fields.Text('Note')
|
|
deferrals = fields.One2Many(
|
|
'account.account.deferral', 'account', "Deferrals", readonly=True,
|
|
states={
|
|
'invisible': ~Eval('type'),
|
|
})
|
|
taxes = fields.Many2Many('account.account-account.tax',
|
|
'account', 'tax', 'Default Taxes',
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
('parent', '=', None),
|
|
],
|
|
help="Default taxes for documents to be applied "
|
|
"when there is no other source.")
|
|
replaced_by = fields.Many2One(
|
|
'account.account', "Replaced By",
|
|
domain=[('company', '=', Eval('company', -1))],
|
|
states={
|
|
'editable': _states['editable'],
|
|
'invisible': ~Eval('end_date'),
|
|
})
|
|
template = fields.Many2One('account.account.template', 'Template')
|
|
template_override = fields.Boolean('Override Template',
|
|
help="Check to override template definition",
|
|
states={
|
|
'invisible': ~Bool(Eval('template', -1)),
|
|
})
|
|
del _states
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
for date in [cls.start_date, cls.end_date]:
|
|
date.states = {
|
|
'editable': (
|
|
~Eval('template', None)
|
|
| Eval('template_override', False)),
|
|
}
|
|
cls._order.insert(0, ('code', 'ASC'))
|
|
cls._order.insert(1, ('name', 'ASC'))
|
|
table = cls.__table__()
|
|
cls._sql_constraints.append(
|
|
('only_one_debit_credit_types', Check(
|
|
table, (table.debit_type + table.credit_type) == Null),
|
|
'account.msg_only_one_debit_credit_types'))
|
|
cls._sql_indexes.update({
|
|
Index(table,
|
|
(Coalesce(table.code, ''),
|
|
Index.Equality(cardinality='high'))),
|
|
Index(table,
|
|
(Coalesce(table.code, ''),
|
|
Index.Similarity(cardinality='high'))),
|
|
Index(
|
|
table,
|
|
(table.left, Index.Range(cardinality='high')),
|
|
(table.right, Index.Range(cardinality='high')))})
|
|
|
|
@classmethod
|
|
def validate_fields(cls, accounts, field_names):
|
|
super().validate_fields(accounts, field_names)
|
|
cls.check_second_currency(accounts, field_names)
|
|
cls.check_move_domain(accounts, field_names)
|
|
|
|
@staticmethod
|
|
def default_left():
|
|
return 0
|
|
|
|
@staticmethod
|
|
def default_right():
|
|
return 0
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company') or None
|
|
|
|
@classmethod
|
|
def default_template_override(cls):
|
|
return False
|
|
|
|
def get_currency(self, name):
|
|
return self.company.currency.id
|
|
|
|
@classmethod
|
|
def search_currency(cls, name, clause):
|
|
return [('company.' + clause[0], *clause[1:])]
|
|
|
|
@classmethod
|
|
def get_balance(cls, accounts, name):
|
|
pool = Pool()
|
|
MoveLine = pool.get('account.move.line')
|
|
FiscalYear = pool.get('account.fiscalyear')
|
|
transaction = Transaction()
|
|
cursor = transaction.connection.cursor()
|
|
context = transaction.context
|
|
|
|
table_a = cls.__table__()
|
|
table_c = cls.__table__()
|
|
line = MoveLine.__table__()
|
|
balances = defaultdict(Decimal)
|
|
for company, c_accounts in groupby(accounts, key=lambda a: a.company):
|
|
c_accounts = list(c_accounts)
|
|
ids = [a.id for a in c_accounts]
|
|
with transaction.set_context(company=company.id):
|
|
line_query, fiscalyear_ids = MoveLine.query_get(line)
|
|
red_sql = fields.SQL_OPERATORS['in'](table_a.id, ids)
|
|
if context.get('flat_balance'):
|
|
query = (table_a
|
|
.join(line, condition=line.account == table_a.id))
|
|
else:
|
|
query = (table_a
|
|
.join(table_c,
|
|
condition=(table_c.left >= table_a.left)
|
|
& (table_c.right <= table_a.right))
|
|
.join(line, condition=line.account == table_c.id))
|
|
query = query.select(
|
|
table_a.id,
|
|
Sum(Coalesce(line.debit, 0) - Coalesce(line.credit, 0)
|
|
).as_('balance'),
|
|
where=red_sql & line_query,
|
|
group_by=table_a.id)
|
|
if backend.name == 'sqlite':
|
|
sqlite_apply_types(query, [None, 'NUMERIC'])
|
|
cursor.execute(*query)
|
|
balances.update(dict(cursor))
|
|
|
|
for account in c_accounts:
|
|
balances[account.id] = account.currency.round(
|
|
balances[account.id])
|
|
|
|
fiscalyears = FiscalYear.browse(fiscalyear_ids)
|
|
|
|
def func(accounts, names):
|
|
return {names[0]: cls.get_balance(accounts, names[0])}
|
|
cls._cumulate(
|
|
fiscalyears, c_accounts, [name], {name: balances}, func)
|
|
return balances
|
|
|
|
@classmethod
|
|
def get_credit_debit(cls, accounts, names):
|
|
'''
|
|
Function to compute debit, credit for accounts.
|
|
If cumulate is set in the context, it is the cumulate amount over all
|
|
previous fiscal year.
|
|
'''
|
|
pool = Pool()
|
|
MoveLine = pool.get('account.move.line')
|
|
FiscalYear = pool.get('account.fiscalyear')
|
|
transaction = Transaction()
|
|
cursor = transaction.connection.cursor()
|
|
|
|
result = {}
|
|
for name in names:
|
|
if name not in {
|
|
'credit', 'debit', 'amount_second_currency', 'line_count'}:
|
|
raise ValueError('Unknown name: %s' % name)
|
|
col_type = int if name == 'line_count' else Decimal
|
|
result[name] = defaultdict(col_type)
|
|
|
|
table = cls.__table__()
|
|
line = MoveLine.__table__()
|
|
|
|
for company, c_accounts in groupby(accounts, key=lambda a: a.company):
|
|
c_accounts = list(c_accounts)
|
|
ids = [a.id for a in c_accounts]
|
|
with transaction.set_context(company=company.id):
|
|
line_query, fiscalyear_ids = MoveLine.query_get(line)
|
|
columns = [table.id]
|
|
types = [None]
|
|
for name in names:
|
|
if name == 'line_count':
|
|
columns.append(Count())
|
|
types.append(None)
|
|
else:
|
|
columns.append(
|
|
Sum(Coalesce(Column(line, name), 0)).as_(name))
|
|
types.append('NUMERIC')
|
|
where = fields.SQL_OPERATORS['in'](table.id, ids)
|
|
query = (table.join(line, 'LEFT',
|
|
condition=line.account == table.id
|
|
).select(*columns,
|
|
where=where & line_query,
|
|
group_by=table.id))
|
|
if backend.name == 'sqlite':
|
|
sqlite_apply_types(query, types)
|
|
cursor.execute(*query)
|
|
for row in cursor:
|
|
account_id = row[0]
|
|
for i, name in enumerate(names, 1):
|
|
result[name][account_id] = row[i]
|
|
for account in c_accounts:
|
|
for name in names:
|
|
if name == 'line_count':
|
|
continue
|
|
if (name == 'amount_second_currency'
|
|
and account.second_currency):
|
|
currency = account.second_currency
|
|
else:
|
|
currency = account.currency
|
|
result[name][account.id] = currency.round(
|
|
result[name][account.id])
|
|
|
|
cumulate_names = []
|
|
if transaction.context.get('cumulate'):
|
|
cumulate_names = names
|
|
elif 'amount_second_currency' in names:
|
|
cumulate_names = ['amount_second_currency']
|
|
if cumulate_names:
|
|
fiscalyears = FiscalYear.browse(fiscalyear_ids)
|
|
cls._cumulate(fiscalyears, c_accounts, cumulate_names, result,
|
|
cls.get_credit_debit)
|
|
return result
|
|
|
|
@classmethod
|
|
def _cumulate(
|
|
cls, fiscalyears, accounts, names, values, func,
|
|
deferral='account.account.deferral'):
|
|
"""
|
|
Cumulate previous fiscalyear values into values
|
|
func is the method to compute values
|
|
"""
|
|
pool = Pool()
|
|
FiscalYear = pool.get('account.fiscalyear')
|
|
|
|
youngest_fiscalyear = None
|
|
for fiscalyear in fiscalyears:
|
|
if (not youngest_fiscalyear
|
|
or (youngest_fiscalyear.start_date
|
|
> fiscalyear.start_date)):
|
|
youngest_fiscalyear = fiscalyear
|
|
|
|
fiscalyear = None
|
|
if youngest_fiscalyear:
|
|
fiscalyears = FiscalYear.search([
|
|
('end_date', '<', youngest_fiscalyear.start_date),
|
|
('company', '=', youngest_fiscalyear.company),
|
|
], order=[('end_date', 'DESC')], limit=1)
|
|
if fiscalyears:
|
|
fiscalyear, = fiscalyears
|
|
|
|
if not fiscalyear:
|
|
return values
|
|
|
|
if fiscalyear.state == 'closed' and deferral:
|
|
Deferral = pool.get(deferral)
|
|
id2deferral = {}
|
|
deferrals = Deferral.search([
|
|
('fiscalyear', '=', fiscalyear),
|
|
('account', 'in', accounts),
|
|
])
|
|
for deferral in deferrals:
|
|
id2deferral[deferral.account.id] = deferral
|
|
|
|
for account in accounts:
|
|
if account.id in id2deferral:
|
|
deferral = id2deferral[account.id]
|
|
for name in names:
|
|
values[name][account.id] += getattr(deferral, name)
|
|
else:
|
|
with Transaction().set_context(fiscalyear=fiscalyear.id,
|
|
date=None, periods=None, from_date=None, to_date=None):
|
|
previous_result = func(accounts, names)
|
|
for name in names:
|
|
vals = values[name]
|
|
for account in accounts:
|
|
vals[account.id] += previous_result[name][account.id]
|
|
|
|
return values
|
|
|
|
__on_change_parent_fields = ['name', 'code', 'company', 'type',
|
|
'debit_type', 'credit_type', 'reconcile', 'deferral', 'party_required',
|
|
'general_ledger_balance', 'taxes']
|
|
|
|
@fields.depends('parent', *(__on_change_parent_fields
|
|
+ ['_parent_parent.%s' % f for f in __on_change_parent_fields]))
|
|
def on_change_parent(self):
|
|
if not self.parent:
|
|
return
|
|
if self.id is not None and self.id >= 0:
|
|
return
|
|
for field in self.__on_change_parent_fields:
|
|
if (not getattr(self, field)
|
|
or field in {'reconcile', 'deferral',
|
|
'party_required', 'general_ledger_balance'}):
|
|
setattr(self, field, getattr(self.parent, field))
|
|
|
|
@classmethod
|
|
def check_second_currency(cls, accounts, field_names=None):
|
|
pool = Pool()
|
|
Line = pool.get('account.move.line')
|
|
if field_names and not (
|
|
field_names & (
|
|
{'second_currency', 'type'}
|
|
| set(cls.deferral.validation_depends))):
|
|
return
|
|
for account in accounts:
|
|
if not account.second_currency:
|
|
continue
|
|
if (not account.type
|
|
or account.type.payable
|
|
or account.type.revenue
|
|
or account.type.receivable
|
|
or account.type.expense):
|
|
raise SecondCurrencyError(
|
|
gettext('account.msg_account_invalid_type_second_currency',
|
|
account=account.rec_name))
|
|
if not account.deferral:
|
|
raise SecondCurrencyError(
|
|
gettext('account'
|
|
'.msg_account_invalid_deferral_second_currency',
|
|
account=account.rec_name))
|
|
lines = Line.search([
|
|
('account', '=', account.id),
|
|
('second_currency', '!=', account.second_currency.id),
|
|
], order=[], limit=1)
|
|
if lines:
|
|
raise SecondCurrencyError(
|
|
gettext('account'
|
|
'.msg_account_invalid_lines_second_currency',
|
|
currency=account.second_currency.rec_name,
|
|
account=account.rec_name))
|
|
|
|
@classmethod
|
|
def check_move_domain(cls, accounts, field_names=None):
|
|
pool = Pool()
|
|
Line = pool.get('account.move.line')
|
|
if field_names and not (field_names & {'closed', 'type'}):
|
|
return
|
|
accounts = [a for a in accounts if a.closed or not a.type]
|
|
if Line.search([
|
|
('account', 'in', accounts),
|
|
], order=[], limit=1):
|
|
for account in accounts:
|
|
if not account.closed:
|
|
continue
|
|
lines = Line.search([
|
|
('account', '=', account.id),
|
|
], order=[], limit=1)
|
|
if lines:
|
|
if account.closed:
|
|
raise AccountValidationError(gettext(
|
|
'account.msg_account_closed_lines',
|
|
account=account.rec_name))
|
|
elif account.type:
|
|
raise AccountValidationError(gettext(
|
|
'account.msg_account_no_type_lines',
|
|
account=account.rec_name))
|
|
|
|
@classmethod
|
|
def copy(cls, accounts, default=None):
|
|
if default is None:
|
|
default = {}
|
|
else:
|
|
default = default.copy()
|
|
default.setdefault('template', None)
|
|
default.setdefault('deferrals')
|
|
new_accounts = super().copy(accounts, default=default)
|
|
cls._rebuild_tree('parent', None, 0)
|
|
return new_accounts
|
|
|
|
def update_account(self, template2account=None, template2type=None):
|
|
'''
|
|
Update recursively accounts based on template.
|
|
template2account is a dictionary with template id as key and account id
|
|
as value, used to convert template id into account. The dictionary is
|
|
filled with new accounts.
|
|
template2type is a dictionary with type template id as key and type id
|
|
as value, used to convert type template id into type.
|
|
'''
|
|
if template2account is None:
|
|
template2account = {}
|
|
|
|
if template2type is None:
|
|
template2type = {}
|
|
|
|
values = []
|
|
childs = [self]
|
|
while childs:
|
|
for child in childs:
|
|
if child.template:
|
|
if not child.template_override:
|
|
vals = child.template._get_account_value(account=child)
|
|
current_type = child.type.id if child.type else None
|
|
if child.template.type:
|
|
template_type = template2type.get(
|
|
child.template.type.id)
|
|
else:
|
|
template_type = None
|
|
if current_type != template_type:
|
|
vals['type'] = template_type
|
|
current_debit_type = (
|
|
child.debit_type.id if child.debit_type else None)
|
|
if child.template.debit_type:
|
|
template_debit_type = template2type.get(
|
|
child.template.debit_type.id)
|
|
else:
|
|
template_debit_type = None
|
|
if current_debit_type != template_debit_type:
|
|
vals['debit_type'] = template_debit_type
|
|
current_credit_type = (
|
|
child.credit_type.id if child.credit_type
|
|
else None)
|
|
if child.template.credit_type:
|
|
template_credit_type = template2type.get(
|
|
child.template.credit_type.id)
|
|
else:
|
|
template_credit_type = None
|
|
if current_credit_type != template_credit_type:
|
|
vals['credit_type'] = template_credit_type
|
|
if vals:
|
|
values.append([child])
|
|
values.append(vals)
|
|
template2account[child.template.id] = child.id
|
|
childs = sum((c.childs for c in childs), ())
|
|
if values:
|
|
self.write(*values)
|
|
|
|
def update_account2(self, template2account, template2tax):
|
|
'''
|
|
Update recursively account taxes and replaced_by base on template.
|
|
template2account is a dictionary with template id as key and account id
|
|
as value, used to convert template id into account.
|
|
template2tax is a dictionary with tax template id as key and tax id as
|
|
value, used to convert tax template id into tax.
|
|
'''
|
|
if template2account is None:
|
|
template2account = {}
|
|
|
|
if template2tax is None:
|
|
template2tax = {}
|
|
|
|
to_write = []
|
|
childs = [self]
|
|
while childs:
|
|
for child in childs:
|
|
if not child.template or child.template_override:
|
|
continue
|
|
values = {}
|
|
tax_ids = [template2tax[x.id] for x in child.template.taxes
|
|
if x.id in template2tax]
|
|
old_tax_ids = [x.id for x in child.taxes]
|
|
taxes_to_add = [x for x in tax_ids if x not in old_tax_ids]
|
|
taxes_to_remove = [x for x in old_tax_ids if x not in tax_ids]
|
|
if taxes_to_add or taxes_to_remove:
|
|
values['taxes'] = [
|
|
('add', taxes_to_add),
|
|
('remove', taxes_to_remove),
|
|
]
|
|
if child.template.parent:
|
|
parent = template2account[child.template.parent.id]
|
|
else:
|
|
parent = None
|
|
old_parent = child.parent.id if child.parent else None
|
|
if parent != old_parent:
|
|
values['parent'] = parent
|
|
if child.template.replaced_by:
|
|
replaced_by = template2account[
|
|
child.template.replaced_by.id]
|
|
else:
|
|
replaced_by = None
|
|
old_replaced_by = (
|
|
child.replaced_by.id if child.replaced_by else None)
|
|
if old_replaced_by != replaced_by:
|
|
values['replaced_by'] = replaced_by
|
|
if values:
|
|
to_write.append([child])
|
|
to_write.append(values)
|
|
childs = sum((c.childs for c in childs), ())
|
|
if to_write:
|
|
self.write(*to_write)
|
|
|
|
def current(self, date=None):
|
|
"Return the actual account for the date"
|
|
pool = Pool()
|
|
Date = pool.get('ir.date')
|
|
context = Transaction().context
|
|
if date is None:
|
|
with Transaction().set_context(company=self.company.id):
|
|
date = context.get('date') or Date.today()
|
|
if self.start_date and date < self.start_date:
|
|
return None
|
|
elif self.end_date and self.end_date < date:
|
|
if self.replaced_by:
|
|
return self.replaced_by.current(date=date)
|
|
else:
|
|
return None
|
|
else:
|
|
return self
|
|
|
|
@classmethod
|
|
def view_attributes(cls):
|
|
return super().view_attributes() + [
|
|
('//page[@id="general_ledger"]', 'states', {
|
|
'invisible': ((Eval('id', -1) < 0)
|
|
| ~Id('account', 'group_account').in_(
|
|
Eval('context', {}).get('groups', []))),
|
|
}),
|
|
('//link[@name="account.act_general_ledger_account_party_form"]',
|
|
'states', {
|
|
'invisible': ~Eval('party_required', False),
|
|
}),
|
|
]
|
|
|
|
@classmethod
|
|
def check_modification(cls, mode, accounts, values=None, external=False):
|
|
pool = Pool()
|
|
MoveLine = pool.get('account.move.line')
|
|
super().check_modification(
|
|
mode, accounts, values=values, external=external)
|
|
# Only external to allow update from template
|
|
if mode == 'write' and external and 'party_required' in values:
|
|
if to_check := [
|
|
a.id for a in accounts
|
|
if a.party_required != values['party_required']]:
|
|
if lines := MoveLine.search([
|
|
('account', 'in', to_check),
|
|
],
|
|
limit=1, order=[]):
|
|
line, = lines
|
|
raise AccessError(gettext(
|
|
'account.msg_account_party_required_with_moves',
|
|
account=line.account.rec_name))
|
|
|
|
|
|
class AccountParty(ActivePeriodMixin, ModelSQL):
|
|
__name__ = 'account.account.party'
|
|
account = fields.Many2One('account.account', "Account")
|
|
party = fields.Many2One(
|
|
'party.party', "Party",
|
|
context={
|
|
'company': Eval('company', -1),
|
|
},
|
|
depends={'company'})
|
|
name = fields.Char("Name")
|
|
code = fields.Char("Code")
|
|
company = fields.Many2One('company.company', "Company")
|
|
type = fields.Many2One('account.account.type', "Type")
|
|
debit_type = fields.Many2One('account.account.type', "Debit Type")
|
|
credit_type = fields.Many2One('account.account.type', "Credit Type")
|
|
closed = fields.Boolean("Closed")
|
|
|
|
balance = fields.Function(Monetary(
|
|
"Balance", currency='currency', digits='currency'),
|
|
'get_balance')
|
|
credit = fields.Function(Monetary(
|
|
"Credit", currency='currency', digits='currency'),
|
|
'get_credit_debit')
|
|
debit = fields.Function(Monetary(
|
|
"Debit", currency='currency', digits='currency'),
|
|
'get_credit_debit')
|
|
amount_second_currency = fields.Function(Monetary(
|
|
"Amount Second Currency",
|
|
currency='second_currency', digits='second_currency',
|
|
states={
|
|
'invisible': ~Eval('second_currency'),
|
|
}),
|
|
'get_credit_debit')
|
|
line_count = fields.Function(
|
|
fields.Integer("Line Count"), 'get_credit_debit')
|
|
second_currency = fields.Many2One(
|
|
'currency.currency', "Secondary Currency")
|
|
|
|
currency = fields.Function(fields.Many2One(
|
|
'currency.currency', "Currency"),
|
|
'get_currency', searcher='search_currency')
|
|
|
|
@classmethod
|
|
def table_query(cls):
|
|
pool = Pool()
|
|
Line = pool.get('account.move.line')
|
|
Account = pool.get('account.account')
|
|
line = Line.__table__()
|
|
account = Account.__table__()
|
|
account_p = Account.__table__()
|
|
account_c = Account.__table__()
|
|
|
|
account_party = (line
|
|
.join(account_c, condition=line.account == account_c.id)
|
|
.join(account_p,
|
|
condition=(account_c.left >= account_p.left)
|
|
& (account_c.right <= account_p.right))
|
|
.select(
|
|
account_p.id.as_('account'),
|
|
line.party.as_('party'),
|
|
distinct=True,
|
|
where=(line.party != Null)
|
|
& (account_p.party_required)))
|
|
|
|
columns = []
|
|
for fname, field in cls._fields.items():
|
|
if not hasattr(field, 'set'):
|
|
if fname == 'id':
|
|
column = sql_pairing(
|
|
account_party.party,
|
|
account.id)
|
|
elif fname == 'account':
|
|
column = account.id
|
|
elif fname == 'party':
|
|
column = Column(account_party, fname)
|
|
else:
|
|
column = Column(account, fname)
|
|
columns.append(column.as_(fname))
|
|
|
|
return (
|
|
account_party
|
|
.join(account,
|
|
condition=account_party.account == account.id)
|
|
.select(
|
|
*columns))
|
|
|
|
@classmethod
|
|
def get_balance(cls, records, name):
|
|
pool = Pool()
|
|
Account = pool.get('account.account')
|
|
MoveLine = pool.get('account.move.line')
|
|
FiscalYear = pool.get('account.fiscalyear')
|
|
transaction = Transaction()
|
|
cursor = transaction.connection.cursor()
|
|
context = transaction.context
|
|
|
|
table_a = Account.__table__()
|
|
table_c = Account.__table__()
|
|
line = MoveLine.__table__()
|
|
balances = defaultdict(Decimal)
|
|
|
|
for company, c_records in groupby(records, lambda r: r.company):
|
|
c_records = list(c_records)
|
|
account_ids = {a.account.id for a in c_records}
|
|
party_ids = {a.party.id for a in c_records}
|
|
with transaction.set_context(company=company.id):
|
|
line_query, fiscalyear_ids = MoveLine.query_get(line)
|
|
account_sql = fields.SQL_OPERATORS['in'](table_a.id, account_ids)
|
|
party_sql = fields.SQL_OPERATORS['in'](line.party, party_ids)
|
|
if context.get('flat_balance'):
|
|
query = (table_a
|
|
.join(line, condition=line.account == table_a.id))
|
|
else:
|
|
query = (table_a
|
|
.join(table_c,
|
|
condition=(table_c.left >= table_a.left)
|
|
& (table_c.right <= table_a.right))
|
|
.join(line, condition=line.account == table_c.id))
|
|
query = query.select(
|
|
table_a.id,
|
|
line.party,
|
|
Sum(
|
|
Coalesce(line.debit, 0)
|
|
- Coalesce(line.credit, 0)).as_('balance'),
|
|
where=account_sql & party_sql & line_query,
|
|
group_by=[table_a.id, line.party])
|
|
if backend.name == 'sqlite':
|
|
sqlite_apply_types(query, [None, None, 'NUMERIC'])
|
|
cursor.execute(*query)
|
|
for account_id, party_id, balance in cursor:
|
|
balances[pair(party_id, account_id)] = balance
|
|
|
|
for record in c_records:
|
|
balances[record.id] = record.currency.round(
|
|
balances[record.id])
|
|
|
|
fiscalyears = FiscalYear.browse(fiscalyear_ids)
|
|
|
|
def func(records, names):
|
|
return {names[0]: cls.get_balance(records, names[0])}
|
|
Account._cumulate(
|
|
fiscalyears, c_records, [name], {name: balances}, func,
|
|
deferral=None)[name]
|
|
return balances
|
|
|
|
@classmethod
|
|
def get_credit_debit(cls, records, names):
|
|
pool = Pool()
|
|
Account = pool.get('account.account')
|
|
MoveLine = pool.get('account.move.line')
|
|
FiscalYear = pool.get('account.fiscalyear')
|
|
transaction = Transaction()
|
|
cursor = transaction.connection.cursor()
|
|
|
|
result = {}
|
|
for name in names:
|
|
if name not in {
|
|
'credit', 'debit', 'amount_second_currency', 'line_count'}:
|
|
raise ValueError('Unknown name: %s' % name)
|
|
column_type = int if name == 'line_count' else Decimal
|
|
result[name] = defaultdict(column_type)
|
|
|
|
table = Account.__table__()
|
|
line = MoveLine.__table__()
|
|
columns = [line.party, table.id]
|
|
types = [None, None]
|
|
for name in names:
|
|
if name == 'line_count':
|
|
columns.append(Count().as_(name))
|
|
types.append(None)
|
|
else:
|
|
columns.append(Sum(Coalesce(Column(line, name), 0)).as_(name))
|
|
types.append('NUMERIC')
|
|
|
|
for company, c_records in groupby(records, key=lambda r: r.company):
|
|
c_records = list(c_records)
|
|
account_ids = {a.account.id for a in c_records}
|
|
party_ids = {a.party.id for a in c_records}
|
|
|
|
with transaction.set_context(company=company.id):
|
|
line_query, fiscalyear_ids = MoveLine.query_get(line)
|
|
|
|
account_sql = fields.SQL_OPERATORS['in'](table.id, account_ids)
|
|
party_sql = fields.SQL_OPERATORS['in'](line.party, party_ids)
|
|
query = (table.join(line, 'LEFT',
|
|
condition=line.account == table.id
|
|
).select(*columns,
|
|
where=account_sql & party_sql & line_query,
|
|
group_by=[table.id, line.party]))
|
|
if backend.name == 'sqlite':
|
|
sqlite_apply_types(query, types)
|
|
cursor.execute(*query)
|
|
for row in cursor:
|
|
id_ = pair(*row[0:2])
|
|
for i, name in enumerate(names, 2):
|
|
result[name][id_] = row[i]
|
|
for record in c_records:
|
|
for name in names:
|
|
if name == 'line_count':
|
|
continue
|
|
if (name == 'amount_second_currency'
|
|
and record.second_currency):
|
|
currency = record.second_currency
|
|
else:
|
|
currency = record.currency
|
|
result[name][record.id] = currency.round(
|
|
result[name][record.id])
|
|
|
|
cumulate_names = []
|
|
if transaction.context.get('cumulate'):
|
|
cumulate_names = names
|
|
elif 'amount_second_currency' in names:
|
|
cumulate_names = ['amount_second_currency']
|
|
if cumulate_names:
|
|
fiscalyears = FiscalYear.browse(fiscalyear_ids)
|
|
Account._cumulate(
|
|
fiscalyears, c_records, cumulate_names, result,
|
|
cls.get_credit_debit, deferral=None)
|
|
return result
|
|
|
|
def get_currency(self, name):
|
|
return self.company.currency.id
|
|
|
|
@classmethod
|
|
def search_currency(cls, name, clause):
|
|
return [('company.' + clause[0], *clause[1:])]
|
|
|
|
|
|
class AccountDeferral(ModelSQL, ModelView):
|
|
"Used to defer the debit/credit of accounts by fiscal year"
|
|
__name__ = 'account.account.deferral'
|
|
account = fields.Many2One('account.account', "Account", required=True)
|
|
fiscalyear = fields.Many2One(
|
|
'account.fiscalyear', "Fiscal Year", required=True)
|
|
debit = Monetary(
|
|
"Debit", currency='currency', digits='currency', required=True)
|
|
credit = Monetary(
|
|
"Credit", currency='currency', digits='currency', required=True)
|
|
balance = fields.Function(Monetary(
|
|
"Balance", currency='currency', digits='currency'),
|
|
'get_balance')
|
|
currency = fields.Function(fields.Many2One(
|
|
'currency.currency', "Currency"),
|
|
'get_currency', searcher='search_currency')
|
|
amount_second_currency = Monetary(
|
|
"Amount Second Currency",
|
|
currency='second_currency', digits='second_currency', required=True,
|
|
states={
|
|
'invisible': ~Eval('second_currency'),
|
|
})
|
|
line_count = fields.Integer("Line Count", required=True)
|
|
second_currency = fields.Function(fields.Many2One(
|
|
'currency.currency', "Second Currency"), 'get_second_currency')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('deferral_uniq', Unique(t, t.account, t.fiscalyear),
|
|
'account.msg_deferral_unique'),
|
|
]
|
|
cls._sql_indexes.add(
|
|
Index(
|
|
t,
|
|
(t.fiscalyear, Index.Range()),
|
|
(t.account, Index.Range())))
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
pool = Pool()
|
|
MoveLine = pool.get('account.move.line')
|
|
Move = pool.get('account.move')
|
|
Period = pool.get('account.period')
|
|
|
|
cursor = Transaction().connection.cursor()
|
|
deferral = cls.__table__()
|
|
move_line = MoveLine.__table__()
|
|
move = Move.__table__()
|
|
period = Period.__table__()
|
|
|
|
exist = backend.TableHandler.table_exist(cls._table)
|
|
table_h = cls.__table_handler__(module_name)
|
|
created_line_count = exist and not table_h.column_exist('line_count')
|
|
|
|
super().__register__(module_name)
|
|
|
|
# Migration from 6.2: add line_count on deferrals
|
|
if created_line_count:
|
|
counting_query = (move_line
|
|
.join(move, condition=move_line.move == move.id)
|
|
.join(period, condition=move.period == period.id)
|
|
.select(
|
|
move_line.account, period.fiscalyear,
|
|
Count().as_('line_count'),
|
|
group_by=[move_line.account, period.fiscalyear]))
|
|
cursor.execute(*deferral.update(
|
|
[deferral.line_count], [counting_query.line_count],
|
|
from_=[counting_query],
|
|
where=((deferral.account == counting_query.account)
|
|
& (deferral.fiscalyear == counting_query.fiscalyear))))
|
|
|
|
@classmethod
|
|
def default_amount_second_currency(cls):
|
|
return Decimal(0)
|
|
|
|
@classmethod
|
|
def default_line_count(cls):
|
|
return 0
|
|
|
|
@classmethod
|
|
def get_balance(cls, deferrals, name):
|
|
pool = Pool()
|
|
Account = pool.get('account.account')
|
|
cursor = Transaction().connection.cursor()
|
|
|
|
table = cls.__table__()
|
|
table_child = cls.__table__()
|
|
account = Account.__table__()
|
|
account_child = Account.__table__()
|
|
balances = defaultdict(Decimal)
|
|
|
|
where = fields.SQL_OPERATORS['in'](
|
|
table.id, [d.id for d in deferrals])
|
|
query = (table
|
|
.join(account, condition=table.account == account.id)
|
|
.join(account_child,
|
|
condition=(account_child.left >= account.left)
|
|
& (account_child.right <= account.right))
|
|
.join(table_child,
|
|
condition=(table_child.account == account_child.id)
|
|
& (table_child.fiscalyear == table.fiscalyear))
|
|
.select(
|
|
table.id.as_('id'),
|
|
Sum(table_child.debit - table_child.credit).as_('balance'),
|
|
where=where,
|
|
group_by=table.id))
|
|
if backend.name == 'sqlite':
|
|
sqlite_apply_types(query, [None, 'NUMERIC'])
|
|
cursor.execute(*query)
|
|
balances.update(dict(cursor))
|
|
return balances
|
|
|
|
def get_currency(self, name):
|
|
return self.account.currency.id
|
|
|
|
@classmethod
|
|
def search_currency(cls, name, clause):
|
|
return [('account.' + clause[0], *clause[1:])]
|
|
|
|
def get_second_currency(self, name):
|
|
if self.account.second_currency:
|
|
return self.account.second_currency.id
|
|
|
|
def get_rec_name(self, name):
|
|
return '%s - %s' % (self.account.rec_name, self.fiscalyear.rec_name)
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
if clause[1].startswith('!') or clause[1].startswith('not '):
|
|
bool_op = 'AND'
|
|
else:
|
|
bool_op = 'OR'
|
|
return [bool_op,
|
|
('account.rec_name',) + tuple(clause[1:]),
|
|
('fiscalyear.rec_name',) + tuple(clause[1:]),
|
|
]
|
|
|
|
@classmethod
|
|
def write(cls, deferrals, values, *args):
|
|
raise AccessError(gettext('account.msg_write_deferral'))
|
|
|
|
|
|
class AccountTax(ModelSQL):
|
|
__name__ = 'account.account-account.tax'
|
|
account = fields.Many2One(
|
|
'account.account', "Account", ondelete='CASCADE', required=True)
|
|
tax = fields.Many2One(
|
|
'account.tax', "Tax", ondelete='RESTRICT', required=True)
|
|
|
|
@classmethod
|
|
def __register__(cls, module):
|
|
# Migration from 7.0: rename to standard name
|
|
backend.TableHandler.table_rename(
|
|
'account_account_tax_rel', cls._table)
|
|
super().__register__(module)
|
|
|
|
|
|
class AccountContext(ModelView):
|
|
__name__ = 'account.account.context'
|
|
|
|
company = fields.Many2One('company.company', "Company", required=True)
|
|
fiscalyear = fields.Many2One(
|
|
'account.fiscalyear', "Fiscal Year",
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
],
|
|
help="Leave empty for all open fiscal year.")
|
|
posted = fields.Boolean('Posted Moves', help="Only include posted moves.")
|
|
|
|
@classmethod
|
|
def default_company(cls):
|
|
return Transaction().context.get('company')
|
|
|
|
@fields.depends('company', 'fiscalyear')
|
|
def on_change_company(self):
|
|
if self.fiscalyear and self.fiscalyear.company != self.company:
|
|
self.fiscalyear = None
|
|
|
|
@classmethod
|
|
def default_posted(cls):
|
|
return False
|
|
|
|
|
|
class _GeneralLedgerAccount(ActivePeriodMixin, ModelSQL, ModelView):
|
|
|
|
account = fields.Many2One('account.account', "Account")
|
|
company = fields.Many2One('company.company', 'Company')
|
|
start_debit = fields.Function(Monetary(
|
|
"Start Debit", currency='currency', digits='currency',
|
|
states={
|
|
'invisible': ~Eval('line_count', -1),
|
|
}),
|
|
'get_account', searcher='search_account')
|
|
debit = fields.Function(Monetary(
|
|
"Debit", currency='currency', digits='currency',
|
|
states={
|
|
'invisible': ~Eval('line_count', -1),
|
|
}),
|
|
'get_debit_credit', searcher='search_debit_credit')
|
|
end_debit = fields.Function(Monetary(
|
|
"End Debit", currency='currency', digits='currency',
|
|
states={
|
|
'invisible': ~Eval('line_count', -1),
|
|
}),
|
|
'get_account', searcher='search_account')
|
|
start_credit = fields.Function(Monetary(
|
|
"Start Credit", currency='currency', digits='currency',
|
|
states={
|
|
'invisible': ~Eval('line_count', -1),
|
|
}),
|
|
'get_account', searcher='search_account')
|
|
credit = fields.Function(Monetary(
|
|
"Credit", currency='currency', digits='currency',
|
|
states={
|
|
'invisible': ~Eval('line_count', -1),
|
|
}),
|
|
'get_debit_credit', searcher='search_debit_credit')
|
|
end_credit = fields.Function(Monetary(
|
|
"End Credit", currency='currency', digits='currency',
|
|
states={
|
|
'invisible': ~Eval('line_count', -1),
|
|
}),
|
|
'get_account', searcher='search_account')
|
|
line_count = fields.Function(
|
|
fields.Integer("Line Count"),
|
|
'get_debit_credit', searcher='search_debit_credit')
|
|
start_balance = fields.Function(Monetary(
|
|
"Start Balance", currency='currency', digits='currency'),
|
|
'get_account', searcher='search_account')
|
|
end_balance = fields.Function(Monetary(
|
|
"End Balance", currency='currency', digits='currency'),
|
|
'get_account', searcher='search_account')
|
|
currency = fields.Function(fields.Many2One(
|
|
'currency.currency', "Currency"),
|
|
'get_currency', searcher='search_currency')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls._order.insert(0, ('account', 'ASC'))
|
|
|
|
@classmethod
|
|
def table_query(cls):
|
|
pool = Pool()
|
|
LedgerAccountContext = pool.get(
|
|
'account.general_ledger.account.context')
|
|
context = LedgerAccountContext.get_context()
|
|
Account = cls._get_account()
|
|
account = Account.__table__()
|
|
columns = []
|
|
for fname, field in cls._fields.items():
|
|
if not hasattr(field, 'set'):
|
|
if (isinstance(field, fields.Many2One)
|
|
and field.get_target() == Account):
|
|
column = Column(account, 'id')
|
|
else:
|
|
column = Column(account, fname)
|
|
columns.append(column.as_(fname))
|
|
return account.select(*columns,
|
|
where=(account.company == context.get('company'))
|
|
& (account.type != Null)
|
|
& (account.closed != Literal(True)))
|
|
|
|
@classmethod
|
|
def get_period_ids(cls, name):
|
|
pool = Pool()
|
|
Period = pool.get('account.period')
|
|
LedgerAccountContext = pool.get(
|
|
'account.general_ledger.account.context')
|
|
context = LedgerAccountContext.get_context()
|
|
|
|
period = None
|
|
if name.startswith('start_'):
|
|
period_ids = []
|
|
if context.get('start_period'):
|
|
period = Period(context['start_period'])
|
|
elif name.startswith('end_'):
|
|
period_ids = []
|
|
if context.get('end_period'):
|
|
period = Period(context['end_period'])
|
|
else:
|
|
periods = Period.search([
|
|
('fiscalyear', '=', context.get('fiscalyear')),
|
|
('type', '=', 'standard'),
|
|
],
|
|
order=[('start_date', 'DESC')], limit=1)
|
|
if periods:
|
|
period, = periods
|
|
|
|
if period:
|
|
if name.startswith('start_'):
|
|
date_clause = ('end_date', '<=', period.start_date)
|
|
else:
|
|
date_clause = [
|
|
('end_date', '<=', period.end_date),
|
|
('start_date', '<', period.end_date),
|
|
]
|
|
periods = Period.search([
|
|
('fiscalyear', '=', context.get('fiscalyear')),
|
|
date_clause,
|
|
])
|
|
if period.start_date == period.end_date:
|
|
periods.append(period)
|
|
if periods:
|
|
period_ids = [p.id for p in periods]
|
|
if name.startswith('end_'):
|
|
# Always include ending period
|
|
period_ids.append(period.id)
|
|
return period_ids
|
|
|
|
@classmethod
|
|
def get_dates(cls, name):
|
|
pool = Pool()
|
|
LedgerAccountContext = pool.get(
|
|
'account.general_ledger.account.context')
|
|
context = LedgerAccountContext.get_context()
|
|
if name.startswith('start_'):
|
|
from_date = context.get('from_date') or datetime.date.min
|
|
if from_date:
|
|
try:
|
|
from_date -= datetime.timedelta(days=1)
|
|
except OverflowError:
|
|
pass
|
|
return None, from_date
|
|
elif name.startswith('end_'):
|
|
return None, context.get('to_date')
|
|
return None, None
|
|
|
|
@classmethod
|
|
def _account_context(cls, name):
|
|
pool = Pool()
|
|
LedgerAccountContext = pool.get(
|
|
'account.general_ledger.account.context')
|
|
period_ids, from_date, to_date = None, None, None
|
|
context = LedgerAccountContext.get_context()
|
|
if context.get('start_period') or context.get('end_period'):
|
|
period_ids = cls.get_period_ids(name)
|
|
elif context.get('from_date') or context.get('to_date'):
|
|
from_date, to_date = cls.get_dates(name)
|
|
else:
|
|
if name.startswith('start_'):
|
|
period_ids = []
|
|
return {
|
|
'periods': period_ids,
|
|
'from_date': from_date,
|
|
'to_date': to_date,
|
|
'flat_balance': True,
|
|
}
|
|
|
|
@classmethod
|
|
def get_account(cls, records, name):
|
|
Account = cls._get_account()
|
|
|
|
with Transaction().set_context(cls._account_context(name)):
|
|
accounts = Account.browse(records)
|
|
fname = name
|
|
for test in ['start_', 'end_']:
|
|
if name.startswith(test):
|
|
fname = name[len(test):]
|
|
break
|
|
return {a.id: getattr(a, fname) for a in accounts}
|
|
|
|
@classmethod
|
|
def search_account(cls, name, domain):
|
|
Account = cls._get_account()
|
|
|
|
with Transaction().set_context(cls._account_context(name)):
|
|
accounts = Account.search([], order=[])
|
|
|
|
_, operator_, operand = domain
|
|
operator_ = {
|
|
'=': operator.eq,
|
|
'>=': operator.ge,
|
|
'>': operator.gt,
|
|
'<=': operator.le,
|
|
'<': operator.lt,
|
|
'!=': operator.ne,
|
|
'in': lambda v, l: v in l,
|
|
'not in': lambda v, l: v not in l,
|
|
}.get(operator_, lambda v, l: False)
|
|
fname = name
|
|
for test in ['start_', 'end_']:
|
|
if name.startswith(test):
|
|
fname = name[len(test):]
|
|
break
|
|
|
|
ids = [a.id for a in accounts
|
|
if operand is not None and operator_(getattr(a, fname), operand)]
|
|
return [('id', 'in', ids)]
|
|
|
|
@classmethod
|
|
def _debit_credit_context(cls):
|
|
pool = Pool()
|
|
LedgerAccountContext = pool.get(
|
|
'account.general_ledger.account.context')
|
|
period_ids, from_date, to_date = None, None, None
|
|
context = LedgerAccountContext.get_context()
|
|
if context.get('start_period') or context.get('end_period'):
|
|
start_period_ids = set(cls.get_period_ids('start_balance'))
|
|
end_period_ids = set(cls.get_period_ids('end_balance'))
|
|
period_ids = list(end_period_ids.difference(start_period_ids))
|
|
elif context.get('from_date') or context.get('to_date'):
|
|
from_date = context.get('from_date')
|
|
to_date = context.get('to_date')
|
|
return {
|
|
'periods': period_ids,
|
|
'from_date': from_date,
|
|
'to_date': to_date,
|
|
}
|
|
|
|
@classmethod
|
|
def get_debit_credit(cls, records, name):
|
|
Account = cls._get_account()
|
|
|
|
with Transaction().set_context(cls._debit_credit_context()):
|
|
accounts = Account.browse(records)
|
|
return {a.id: getattr(a, name) for a in accounts}
|
|
|
|
@classmethod
|
|
def search_debit_credit(cls, name, domain):
|
|
Account = cls._get_account()
|
|
|
|
with Transaction().set_context(cls._debit_credit_context()):
|
|
accounts = Account.search([], order=[])
|
|
|
|
_, operator_, operand = domain
|
|
operator_ = {
|
|
'=': operator.eq,
|
|
'>=': operator.ge,
|
|
'>': operator.gt,
|
|
'<=': operator.le,
|
|
'<': operator.lt,
|
|
'!=': operator.ne,
|
|
'in': lambda v, l: v in l,
|
|
'not in': lambda v, l: v not in l,
|
|
}.get(operator_, lambda v, l: False)
|
|
|
|
ids = [a.id for a in accounts
|
|
if operand is not None and operator_(getattr(a, name), operand)]
|
|
return [('id', 'in', ids)]
|
|
|
|
def get_currency(self, name):
|
|
return self.company.currency.id
|
|
|
|
@classmethod
|
|
def search_currency(cls, name, clause):
|
|
return [('company.' + clause[0], *clause[1:])]
|
|
|
|
def get_rec_name(self, name):
|
|
return self.account.rec_name
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
return [('account.rec_name',) + tuple(clause[1:])]
|
|
|
|
|
|
class GeneralLedgerAccount(_GeneralLedgerAccount):
|
|
__name__ = 'account.general_ledger.account'
|
|
|
|
type = fields.Many2One('account.account.type', "Type")
|
|
debit_type = fields.Many2One('account.account.type', "Debit Type")
|
|
credit_type = fields.Many2One('account.account.type', "Credit Type")
|
|
lines = fields.One2Many(
|
|
'account.general_ledger.line', 'account', "Lines", readonly=True,
|
|
order=[('move.date', 'ASC'), ('id', None)])
|
|
parties = fields.One2Many(
|
|
'account.general_ledger.account.party', 'account', "Parties",
|
|
readonly=True)
|
|
party_required = fields.Boolean("Party Required")
|
|
general_ledger_balance = fields.Boolean("General Ledger Balance")
|
|
|
|
@classmethod
|
|
def _get_account(cls):
|
|
pool = Pool()
|
|
return pool.get('account.account')
|
|
|
|
|
|
class GeneralLedgerAccountContext(ModelView):
|
|
__name__ = 'account.general_ledger.account.context'
|
|
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
|
|
required=True,
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
],
|
|
depends=['company'])
|
|
start_period = fields.Many2One('account.period', 'Start Period',
|
|
domain=[
|
|
('fiscalyear', '=', Eval('fiscalyear', -1)),
|
|
('start_date', '<=', (Eval('end_period'), 'start_date')),
|
|
],
|
|
states={
|
|
'invisible': Eval('from_date', False) | Eval('to_date', False),
|
|
})
|
|
end_period = fields.Many2One('account.period', 'End Period',
|
|
domain=[
|
|
('fiscalyear', '=', Eval('fiscalyear', -1)),
|
|
('start_date', '>=', (Eval('start_period'), 'start_date'))
|
|
],
|
|
states={
|
|
'invisible': Eval('from_date', False) | Eval('to_date', False),
|
|
})
|
|
from_date = fields.Date("From Date",
|
|
domain=[
|
|
If(Eval('to_date') & Eval('from_date'),
|
|
('from_date', '<=', Eval('to_date')),
|
|
()),
|
|
],
|
|
states={
|
|
'invisible': (Eval('start_period', False)
|
|
| Eval('end_period', False)),
|
|
})
|
|
to_date = fields.Date("To Date",
|
|
domain=[
|
|
If(Eval('from_date') & Eval('to_date'),
|
|
('to_date', '>=', Eval('from_date')),
|
|
()),
|
|
],
|
|
states={
|
|
'invisible': (Eval('start_period', False)
|
|
| Eval('end_period', False)),
|
|
})
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
posted = fields.Boolean('Posted Moves', help="Only include posted moves.")
|
|
journal = fields.Many2One(
|
|
'account.journal', "Journal",
|
|
context={
|
|
'company': Eval('company', -1),
|
|
},
|
|
depends={'company'},
|
|
help="Only include moves from the journal.")
|
|
|
|
@classmethod
|
|
def default_fiscalyear(cls):
|
|
pool = Pool()
|
|
FiscalYear = pool.get('account.fiscalyear')
|
|
Period = pool.get('account.period')
|
|
context = Transaction().context
|
|
fiscalyear_id = context.get('fiscalyear')
|
|
period_id = context.get('period')
|
|
if fiscalyear_id is not None and fiscalyear_id >= 0:
|
|
return fiscalyear_id
|
|
elif period_id is not None and period_id >= 0:
|
|
period = Period(period_id)
|
|
return period.fiscalyear.id
|
|
else:
|
|
try:
|
|
fiscalyear = FiscalYear.find(
|
|
context.get('company'), test_state=False)
|
|
except FiscalYearNotFoundError:
|
|
return None
|
|
return fiscalyear.id
|
|
|
|
@classmethod
|
|
def default_start_period(cls):
|
|
return Transaction().context.get('start_period')
|
|
|
|
@classmethod
|
|
def default_end_period(cls):
|
|
return Transaction().context.get('end_period')
|
|
|
|
@classmethod
|
|
def default_company(cls):
|
|
return Transaction().context.get('company')
|
|
|
|
@classmethod
|
|
def default_posted(cls):
|
|
return Transaction().context.get('posted', False)
|
|
|
|
@classmethod
|
|
def default_journal(cls):
|
|
return Transaction().context.get('journal')
|
|
|
|
@classmethod
|
|
def default_from_date(cls):
|
|
return Transaction().context.get('from_date')
|
|
|
|
@classmethod
|
|
def default_to_date(cls):
|
|
return Transaction().context.get('to_date')
|
|
|
|
@fields.depends('company', 'fiscalyear', methods=['on_change_fiscalyear'])
|
|
def on_change_company(self):
|
|
pool = Pool()
|
|
FiscalYear = pool.get('account.fiscalyear')
|
|
if not self.company:
|
|
self.fiscalyear = None
|
|
self.on_change_fiscalyear()
|
|
elif not self.fiscalyear or self.fiscalyear.company != self.company:
|
|
try:
|
|
self.fiscalyear = FiscalYear.find(
|
|
self.company, test_state=False)
|
|
self.on_change_fiscalyear()
|
|
except FiscalYearNotFoundError:
|
|
pass
|
|
|
|
@fields.depends('fiscalyear', 'start_period', 'end_period')
|
|
def on_change_fiscalyear(self):
|
|
if (self.start_period
|
|
and self.start_period.fiscalyear != self.fiscalyear):
|
|
self.start_period = None
|
|
if (self.end_period
|
|
and self.end_period.fiscalyear != self.fiscalyear):
|
|
self.end_period = None
|
|
|
|
@fields.depends('start_period')
|
|
def on_change_start_period(self):
|
|
if self.start_period:
|
|
self.from_date = self.to_date = None
|
|
|
|
@fields.depends('end_period')
|
|
def on_change_end_period(self):
|
|
if self.end_period:
|
|
self.from_date = self.to_date = None
|
|
|
|
@fields.depends('from_date')
|
|
def on_change_from_date(self):
|
|
if self.from_date:
|
|
self.start_period = self.end_period = None
|
|
|
|
@fields.depends('to_date')
|
|
def on_change_to_date(self):
|
|
if self.to_date:
|
|
self.start_period = self.end_period = None
|
|
|
|
@classmethod
|
|
def get_context(cls, fields_names=None):
|
|
fields_names = fields_names.copy() if fields_names is not None else []
|
|
fields_names += [
|
|
'fiscalyear', 'start_period', 'end_period', 'from_date', 'to_date',
|
|
'company', 'posted', 'journal']
|
|
return cls.default_get(fields_names, with_rec_name=False)
|
|
|
|
|
|
class GeneralLedgerAccountParty(_GeneralLedgerAccount):
|
|
__name__ = 'account.general_ledger.account.party'
|
|
|
|
party = fields.Many2One(
|
|
'party.party', "Party",
|
|
context={
|
|
'company': Eval('company', -1),
|
|
},
|
|
depends={'company'})
|
|
lines = fields.Function(fields.Many2Many(
|
|
'account.general_ledger.line', None, None, "Lines", readonly=True,
|
|
context={
|
|
'party_cumulate': True,
|
|
}),
|
|
'get_lines')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls._order.insert(1, ('party', 'ASC'))
|
|
|
|
@classmethod
|
|
def get_lines(cls, parties, name):
|
|
pool = Pool()
|
|
Line = pool.get('account.general_ledger.line')
|
|
Move = pool.get('account.move')
|
|
table = cls.__table__()
|
|
line = Line.__table__()
|
|
move = Move.__table__()
|
|
cursor = Transaction().connection.cursor()
|
|
|
|
query = (line
|
|
.join(table,
|
|
condition=(
|
|
(line.account == table.account)
|
|
& (line.party == table.party)))
|
|
.join(move,
|
|
condition=line.move == move.id)
|
|
.select(table.id, line.id,
|
|
where=fields.SQL_OPERATORS['in'](
|
|
table.id, list(map(int, parties))),
|
|
order_by=[table.id, move.date.asc, line.id]))
|
|
cursor.execute(*query)
|
|
return defaultdict(list, (
|
|
(i, [r[1] for r in g])
|
|
for i, g in groupby(cursor, lambda row: row[0])))
|
|
|
|
@classmethod
|
|
def _get_account(cls):
|
|
pool = Pool()
|
|
return pool.get('account.account.party')
|
|
|
|
def get_rec_name(self, name):
|
|
return ' - '.join((self.account.rec_name, self.party.rec_name))
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
if clause[1].startswith('!') or clause[1].startswith('not '):
|
|
bool_op = 'AND'
|
|
else:
|
|
bool_op = 'OR'
|
|
return [bool_op,
|
|
('account.rec_name',) + tuple(clause[1:]),
|
|
('party.rec_name',) + tuple(clause[1:]),
|
|
]
|
|
|
|
|
|
class OpenGeneralLedgerAccountParty(Wizard):
|
|
__name__ = 'account.general_ledger.account.party.open'
|
|
start_state = 'open'
|
|
_readonly = True
|
|
open = StateAction('account.act_general_ledger_line_form')
|
|
|
|
def do_open(self, action):
|
|
action['name'] = '%s (%s)' % (action['name'], self.record.rec_name)
|
|
domain = [
|
|
('account', '=', self.record.account.id),
|
|
('party', '=', self.record.party.id),
|
|
]
|
|
action['pyson_domain'] = PYSONEncoder().encode(domain)
|
|
action['context_model'] = 'account.general_ledger.account.context'
|
|
action['pyson_context'] = PYSONEncoder().encode({
|
|
'party_cumulate': True,
|
|
})
|
|
return action, {}
|
|
|
|
|
|
class GeneralLedgerLine(DescriptionOriginMixin, ModelSQL, ModelView):
|
|
__name__ = 'account.general_ledger.line'
|
|
|
|
move = fields.Many2One('account.move', 'Move')
|
|
date = fields.Date('Date')
|
|
account = fields.Many2One(
|
|
'account.general_ledger.account', 'Account', required=True)
|
|
party = fields.Many2One('party.party', 'Party',
|
|
states={
|
|
'invisible': ~Eval('party_required', False),
|
|
},
|
|
context={
|
|
'company': Eval('company', -1),
|
|
},
|
|
depends={'company'})
|
|
party_required = fields.Boolean('Party Required')
|
|
account_party = fields.Function(
|
|
fields.Many2One(
|
|
'account.general_ledger.account.party', "Account Party"),
|
|
'get_account_party')
|
|
company = fields.Many2One('company.company', 'Company')
|
|
debit = Monetary(
|
|
"Debit", currency='currency', digits='currency')
|
|
credit = Monetary(
|
|
"Credit", currency='currency', digits='currency')
|
|
internal_balance = Monetary(
|
|
"Internal Balance", currency='currency', digits='currency')
|
|
balance = fields.Function(Monetary(
|
|
"Balance", currency='currency', digits='currency'),
|
|
'get_balance')
|
|
origin = fields.Reference('Origin', selection='get_origin')
|
|
description = fields.Char('Description')
|
|
amount_second_currency = Monetary(
|
|
"Amount Second Currency",
|
|
currency='second_currency', digits='second_currency')
|
|
second_currency = fields.Many2One('currency.currency', 'Second Currency')
|
|
reconciliation = fields.Many2One(
|
|
'account.move.reconciliation', "Reconciliation")
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('posted', 'Posted'),
|
|
], "State", sort=False)
|
|
state_string = state.translated('state')
|
|
currency = fields.Function(fields.Many2One(
|
|
'currency.currency', "Currency"),
|
|
'get_currency', searcher='search_currency')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls.__access__.add('account')
|
|
cls._order.insert(0, ('date', 'ASC'))
|
|
cls.description_used.setter = None
|
|
|
|
@classmethod
|
|
def table_query(cls):
|
|
pool = Pool()
|
|
Line = pool.get('account.move.line')
|
|
Move = pool.get('account.move')
|
|
LedgerAccount = pool.get('account.general_ledger.account')
|
|
LedgerLineContext = pool.get(
|
|
'account.general_ledger.line.context')
|
|
Account = pool.get('account.account')
|
|
context = LedgerLineContext.get_context()
|
|
line = Line.__table__()
|
|
move = Move.__table__()
|
|
account = Account.__table__()
|
|
columns = []
|
|
for fname, field in cls._fields.items():
|
|
if hasattr(field, 'set'):
|
|
continue
|
|
field_line = getattr(Line, fname, None)
|
|
if fname == 'internal_balance':
|
|
w_columns = [line.account]
|
|
if context.get('party_cumulate', False):
|
|
w_columns.append(line.party)
|
|
column = Sum(line.debit - line.credit,
|
|
window=Window(w_columns,
|
|
order_by=[move.date.asc, line.id])).as_(
|
|
'internal_balance')
|
|
elif fname == 'party_required':
|
|
column = Column(account, 'party_required').as_(fname)
|
|
elif fname in {'origin', 'description'}:
|
|
column = Coalesce(
|
|
Column(line, fname), Column(move, fname)).as_(fname)
|
|
elif (not field_line
|
|
or fname == 'state'
|
|
or isinstance(field_line, fields.Function)):
|
|
column = Column(move, fname).as_(fname)
|
|
else:
|
|
column = Column(line, fname).as_(fname)
|
|
columns.append(column)
|
|
with Transaction().set_context(
|
|
context, **LedgerAccount._debit_credit_context()):
|
|
line_query, fiscalyear_ids = Line.query_get(line)
|
|
return line.join(move, condition=line.move == move.id
|
|
).join(account, condition=line.account == account.id
|
|
).select(*columns, where=line_query)
|
|
|
|
def get_currency(self, name):
|
|
return self.company.currency.id
|
|
|
|
@classmethod
|
|
def search_currency(cls, name, clause):
|
|
return [('company.' + clause[0], *clause[1:])]
|
|
|
|
@classmethod
|
|
def get_origin(cls):
|
|
pool = Pool()
|
|
Move = pool.get('account.move')
|
|
Line = pool.get('account.move.line')
|
|
return list(set(Move.get_origin()) | set(Line.get_origin()))
|
|
|
|
def get_balance(self, name):
|
|
pool = Pool()
|
|
LedgerLineContext = pool.get(
|
|
'account.general_ledger.line.context')
|
|
context = LedgerLineContext.get_context()
|
|
balance = self.internal_balance
|
|
if context.get('party_cumulate', False) and self.account_party:
|
|
balance += self.account_party.start_balance
|
|
else:
|
|
balance += self.account.start_balance
|
|
return balance
|
|
|
|
@classmethod
|
|
def get_account_party(cls, records, name):
|
|
pool = Pool()
|
|
AccountParty = pool.get('account.general_ledger.account.party')
|
|
table = cls.__table__()
|
|
account_party = AccountParty.__table__()
|
|
cursor = Transaction().connection.cursor()
|
|
|
|
query = (account_party
|
|
.join(table,
|
|
condition=(
|
|
(account_party.account == table.account)
|
|
& (account_party.party == table.party)))
|
|
.select(table.id, account_party.id,
|
|
where=fields.SQL_OPERATORS['in'](
|
|
table.id, list(map(int, records)))))
|
|
cursor.execute(*query)
|
|
return defaultdict(type(None), cursor)
|
|
|
|
@classmethod
|
|
def reconcile(cls, *lines_list, **kwargs):
|
|
pool = Pool()
|
|
Line = pool.get('account.move.line')
|
|
lines_list = [Line.browse(l) for l in lines_list]
|
|
return Line.reconcile(*lines_list, **kwargs)
|
|
|
|
|
|
class GeneralLedgerLineContext(GeneralLedgerAccountContext):
|
|
__name__ = 'account.general_ledger.line.context'
|
|
|
|
party_cumulate = fields.Boolean('Cumulate per Party')
|
|
|
|
@classmethod
|
|
def default_party_cumulate(cls):
|
|
return Transaction().context.get('party_cumulate', False)
|
|
|
|
@classmethod
|
|
def get_context(cls, fields_names=None):
|
|
fields_names = fields_names.copy() if fields_names is not None else []
|
|
fields_names.append('party_cumulate')
|
|
return super().get_context(fields_names=fields_names)
|
|
|
|
|
|
class GeneralLedger(Report):
|
|
__name__ = 'account.general_ledger'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
pool = Pool()
|
|
Company = pool.get('company.company')
|
|
Fiscalyear = pool.get('account.fiscalyear')
|
|
Period = pool.get('account.period')
|
|
context = Transaction().context
|
|
|
|
report_context = super().get_context(records, header, data)
|
|
|
|
report_context['company'] = Company(context['company'])
|
|
report_context['fiscalyear'] = Fiscalyear(context['fiscalyear'])
|
|
|
|
for period in ['start_period', 'end_period']:
|
|
if context.get(period):
|
|
report_context[period] = Period(context[period])
|
|
else:
|
|
report_context[period] = None
|
|
report_context['from_date'] = context.get('from_date')
|
|
report_context['to_date'] = context.get('to_date')
|
|
|
|
report_context['accounts'] = records
|
|
return report_context
|
|
|
|
|
|
class TrialBalance(Report):
|
|
__name__ = 'account.trial_balance'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
pool = Pool()
|
|
Company = pool.get('company.company')
|
|
Fiscalyear = pool.get('account.fiscalyear')
|
|
Period = pool.get('account.period')
|
|
context = Transaction().context
|
|
|
|
report_context = super().get_context(records, header, data)
|
|
|
|
report_context['company'] = Company(context['company'])
|
|
report_context['fiscalyear'] = Fiscalyear(context['fiscalyear'])
|
|
|
|
for period in ['start_period', 'end_period']:
|
|
if context.get(period):
|
|
report_context[period] = Period(context[period])
|
|
else:
|
|
report_context[period] = None
|
|
report_context['from_date'] = context.get('from_date')
|
|
report_context['to_date'] = context.get('to_date')
|
|
|
|
report_context['accounts'] = records
|
|
report_context['sum'] = cls.sum
|
|
return report_context
|
|
|
|
@classmethod
|
|
def sum(cls, accounts, field):
|
|
return sum((getattr(a, field) for a in accounts), Decimal('0'))
|
|
|
|
|
|
class BalanceSheetContext(ModelView):
|
|
__name__ = 'account.balance_sheet.context'
|
|
date = fields.Date('Date', required=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
posted = fields.Boolean('Posted Moves', help="Only include posted moves.")
|
|
|
|
@staticmethod
|
|
def default_date():
|
|
Date_ = Pool().get('ir.date')
|
|
return Transaction().context.get('date', Date_.today())
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@staticmethod
|
|
def default_posted():
|
|
return Transaction().context.get('posted', False)
|
|
|
|
|
|
class BalanceSheetComparisonContext(BalanceSheetContext):
|
|
__name__ = 'account.balance_sheet.comparison.context'
|
|
comparison = fields.Boolean('Comparison')
|
|
date_cmp = fields.Date('Date', states={
|
|
'required': Eval('comparison', False),
|
|
'invisible': ~Eval('comparison', False),
|
|
})
|
|
|
|
@classmethod
|
|
def default_comparison(cls):
|
|
return False
|
|
|
|
@fields.depends('comparison', 'date', 'date_cmp')
|
|
def on_change_comparison(self):
|
|
self.date_cmp = None
|
|
if self.comparison and self.date:
|
|
self.date_cmp = self.date - relativedelta(years=1)
|
|
|
|
@classmethod
|
|
def view_attributes(cls):
|
|
return super().view_attributes() + [
|
|
('/form/separator[@id="comparison"]', 'states', {
|
|
'invisible': ~Eval('comparison', False),
|
|
}),
|
|
]
|
|
|
|
|
|
class IncomeStatementContext(ModelView):
|
|
__name__ = 'account.income_statement.context'
|
|
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
|
|
required=True,
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
])
|
|
start_period = fields.Many2One('account.period', 'Start Period',
|
|
domain=[
|
|
('fiscalyear', '=', Eval('fiscalyear', -1)),
|
|
('start_date', '<=', (Eval('end_period'), 'start_date'))
|
|
],
|
|
states={
|
|
'invisible': Eval('from_date', False) | Eval('to_date', False),
|
|
})
|
|
end_period = fields.Many2One('account.period', 'End Period',
|
|
domain=[
|
|
('fiscalyear', '=', Eval('fiscalyear', -1)),
|
|
('start_date', '>=', (Eval('start_period'), 'start_date')),
|
|
],
|
|
states={
|
|
'invisible': Eval('from_date', False) | Eval('to_date', False),
|
|
})
|
|
from_date = fields.Date("From Date",
|
|
domain=[
|
|
If(Eval('to_date') & Eval('from_date'),
|
|
('from_date', '<=', Eval('to_date')),
|
|
()),
|
|
],
|
|
states={
|
|
'invisible': (
|
|
Eval('start_period', False) | Eval('end_period', False)),
|
|
})
|
|
to_date = fields.Date("To Date",
|
|
domain=[
|
|
If(Eval('from_date') & Eval('to_date'),
|
|
('to_date', '>=', Eval('from_date')),
|
|
()),
|
|
],
|
|
states={
|
|
'invisible': (
|
|
Eval('start_period', False) | Eval('end_period', False)),
|
|
})
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
posted = fields.Boolean('Posted Move', help="Only include posted moves.")
|
|
comparison = fields.Boolean('Comparison')
|
|
fiscalyear_cmp = fields.Many2One('account.fiscalyear', 'Fiscal Year',
|
|
states={
|
|
'required': Eval('comparison', False),
|
|
'invisible': ~Eval('comparison', False),
|
|
},
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
])
|
|
start_period_cmp = fields.Many2One('account.period', 'Start Period',
|
|
domain=[
|
|
('fiscalyear', '=', Eval('fiscalyear_cmp', -1)),
|
|
('start_date', '<=', (Eval('end_period_cmp'), 'start_date'))
|
|
],
|
|
states={
|
|
'invisible': (
|
|
~Eval('comparison', False)
|
|
| Eval('from_date_cmp', False) | Eval('to_date_cmp', False)),
|
|
})
|
|
end_period_cmp = fields.Many2One('account.period', 'End Period',
|
|
domain=[
|
|
('fiscalyear', '=', Eval('fiscalyear_cmp', -1)),
|
|
('start_date', '>=', (Eval('start_period_cmp'), 'start_date')),
|
|
],
|
|
states={
|
|
'invisible': (
|
|
~Eval('comparison', False)
|
|
| Eval('from_date_cmp', False) | Eval('to_date_cmp', False)),
|
|
})
|
|
from_date_cmp = fields.Date("From Date",
|
|
domain=[
|
|
If(Eval('to_date_cmp') & Eval('from_date_cmp'),
|
|
('from_date_cmp', '<=', Eval('to_date_cmp')),
|
|
()),
|
|
],
|
|
states={
|
|
'invisible': (
|
|
~Eval('comparison', False)
|
|
| Eval('start_period_cmp', False)
|
|
| Eval('end_period_cmp', False)),
|
|
})
|
|
to_date_cmp = fields.Date("To Date",
|
|
domain=[
|
|
If(Eval('from_date_cmp') & Eval('to_date_cmp'),
|
|
('to_date_cmp', '>=', Eval('from_date_cmp')),
|
|
()),
|
|
],
|
|
states={
|
|
'invisible': (
|
|
~Eval('comparison', False)
|
|
| Eval('start_period_cmp', False)
|
|
| Eval('end_period_cmp', False)),
|
|
})
|
|
|
|
@classmethod
|
|
def default_fiscalyear(cls):
|
|
pool = Pool()
|
|
FiscalYear = pool.get('account.fiscalyear')
|
|
try:
|
|
fiscalyear = FiscalYear.find(
|
|
cls.default_company(), test_state=False)
|
|
except FiscalYearNotFoundError:
|
|
return None
|
|
return fiscalyear.id
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@staticmethod
|
|
def default_posted():
|
|
return False
|
|
|
|
@classmethod
|
|
def default_comparison(cls):
|
|
return False
|
|
|
|
@fields.depends('company', 'fiscalyear', methods=['on_change_fiscalyear'])
|
|
def on_change_company(self):
|
|
pool = Pool()
|
|
FiscalYear = pool.get('account.fiscalyear')
|
|
if not self.company:
|
|
self.fiscalyear = None
|
|
self.on_change_fiscalyear()
|
|
self.fiscalyear_cmp = None
|
|
self.on_change_fiscalyear_cmp()
|
|
elif not self.fiscalyear or self.fiscalyear.company != self.company:
|
|
try:
|
|
self.fiscalyear = FiscalYear.find(
|
|
self.company, test_state=False)
|
|
self.on_change_fiscalyear()
|
|
except FiscalYearNotFoundError:
|
|
pass
|
|
|
|
@fields.depends('fiscalyear', 'start_period', 'end_period')
|
|
def on_change_fiscalyear(self):
|
|
if (self.start_period
|
|
and self.start_period.fiscalyear != self.fiscalyear):
|
|
self.start_period = None
|
|
if (self.end_period
|
|
and self.end_period.fiscalyear != self.fiscalyear):
|
|
self.end_period = None
|
|
|
|
@fields.depends('start_period')
|
|
def on_change_start_period(self):
|
|
if self.start_period:
|
|
self.from_date = self.to_date = None
|
|
|
|
@fields.depends('end_period')
|
|
def on_change_end_period(self):
|
|
if self.end_period:
|
|
self.from_date = self.to_date = None
|
|
|
|
@fields.depends('from_date')
|
|
def on_change_from_date(self):
|
|
if self.from_date:
|
|
self.start_period = self.end_period = None
|
|
|
|
@fields.depends('to_date')
|
|
def on_change_to_date(self):
|
|
if self.to_date:
|
|
self.start_period = self.end_period = None
|
|
|
|
@fields.depends('fiscalyear_cmp', 'start_period_cmp', 'end_period_cmp')
|
|
def on_change_fiscalyear_cmp(self):
|
|
if (self.start_period_cmp
|
|
and self.start_period_cmp.fiscalyear != self.fiscalyear):
|
|
self.start_period_cmp = None
|
|
if (self.end_period_cmp
|
|
and self.end_period_cmp.fiscalyear != self.fiscalyear):
|
|
self.end_period_cmp = None
|
|
|
|
@fields.depends('start_period_cmp')
|
|
def on_change_start_period_cmp(self):
|
|
if self.start_period_cmp:
|
|
self.from_date_cmp = self.to_date_cmp = None
|
|
|
|
@fields.depends('end_period_cmp')
|
|
def on_change_end_period_cmp(self):
|
|
if self.end_period_cmp:
|
|
self.from_date_cmp = self.to_date_cmp = None
|
|
|
|
@fields.depends('from_date_cmp')
|
|
def on_change_from_date_cmp(self):
|
|
if self.from_date_cmp:
|
|
self.start_period_cmp = self.end_period_cmp = None
|
|
|
|
@fields.depends('to_date_cmp')
|
|
def on_change_to_date_cmp(self):
|
|
if self.to_date_cmp:
|
|
self.start_period_cmp = self.end_period_cmp = None
|
|
|
|
@classmethod
|
|
def view_attributes(cls):
|
|
return super().view_attributes() + [
|
|
('/form/separator[@id="comparison"]', 'states', {
|
|
'invisible': ~Eval('comparison', False),
|
|
}),
|
|
]
|
|
|
|
|
|
class AgedBalanceContext(ModelView):
|
|
__name__ = 'account.aged_balance.context'
|
|
type = fields.Selection([
|
|
('customer', 'Customers'),
|
|
('supplier', 'Suppliers'),
|
|
('customer_supplier', 'Customers and Suppliers'),
|
|
],
|
|
"Type", required=True)
|
|
date = fields.Date('Date', required=True)
|
|
term1 = fields.Integer("First Term", required=True)
|
|
term2 = fields.Integer("Second Term", required=True,
|
|
domain=[
|
|
('term2', '>', Eval('term1', 0)),
|
|
])
|
|
term3 = fields.Integer("Third Term", required=True,
|
|
domain=[
|
|
('term3', '>', Eval('term2', 0)),
|
|
])
|
|
unit = fields.Selection([
|
|
('day', 'Days'),
|
|
('week', "Weeks"),
|
|
('month', 'Months'),
|
|
('year', "Years"),
|
|
], "Unit", required=True, sort=False)
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
posted = fields.Boolean('Posted Move', help="Only include posted moves.")
|
|
|
|
@classmethod
|
|
def default_type(cls):
|
|
return 'customer'
|
|
|
|
@classmethod
|
|
def default_posted(cls):
|
|
return False
|
|
|
|
@classmethod
|
|
def default_date(cls):
|
|
return Pool().get('ir.date').today()
|
|
|
|
@classmethod
|
|
def default_term1(cls):
|
|
return cls._default_terms(cls.default_unit())[0]
|
|
|
|
@classmethod
|
|
def default_term2(cls):
|
|
return cls._default_terms(cls.default_unit())[1]
|
|
|
|
@classmethod
|
|
def default_term3(cls):
|
|
return cls._default_terms(cls.default_unit())[2]
|
|
|
|
@classmethod
|
|
def default_unit(cls):
|
|
return 'day'
|
|
|
|
@fields.depends('unit')
|
|
def on_change_unit(self):
|
|
self.term1, self.term2, self.term3 = self._default_terms(self.unit)
|
|
|
|
@classmethod
|
|
def _default_terms(cls, unit):
|
|
terms = None, None, None
|
|
if unit == 'day':
|
|
terms = 30, 60, 90
|
|
elif unit == 'week':
|
|
terms = 4, 8, 12
|
|
elif unit in {'month', 'year'}:
|
|
terms = 1, 2, 3
|
|
return terms
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
|
|
class AgedBalance(ModelSQL, ModelView):
|
|
__name__ = 'account.aged_balance'
|
|
|
|
party = fields.Many2One(
|
|
'party.party', 'Party',
|
|
context={
|
|
'company': Eval('company', -1),
|
|
},
|
|
depends={'company'})
|
|
company = fields.Many2One('company.company', 'Company')
|
|
term0 = Monetary(
|
|
"Now", currency='currency', digits='currency')
|
|
term1 = Monetary(
|
|
"First Term", currency='currency', digits='currency')
|
|
term2 = Monetary(
|
|
"Second Term", currency='currency', digits='currency')
|
|
term3 = Monetary(
|
|
"Third Term", currency='currency', digits='currency')
|
|
balance = Monetary(
|
|
"Balance", currency='currency', digits='currency')
|
|
currency = fields.Function(fields.Many2One(
|
|
'currency.currency', "Currency"),
|
|
'get_currency', searcher='search_currency')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls._order.insert(0, ('party', 'ASC'))
|
|
|
|
@classmethod
|
|
def table_query(cls):
|
|
pool = Pool()
|
|
context = Transaction().context
|
|
MoveLine = pool.get('account.move.line')
|
|
Move = pool.get('account.move')
|
|
Reconciliation = pool.get('account.move.reconciliation')
|
|
Account = pool.get('account.account')
|
|
Type = pool.get('account.account.type')
|
|
|
|
line = MoveLine.__table__()
|
|
move = Move.__table__()
|
|
reconciliation = Reconciliation.__table__()
|
|
account = Account.__table__()
|
|
type_ = Type.__table__()
|
|
debit_type = Type.__table__()
|
|
credit_type = Type.__table__()
|
|
|
|
company_id = context.get('company')
|
|
date = context.get('date')
|
|
kind = cls.get_kind(type_)
|
|
debit_kind = (
|
|
cls.get_kind(debit_type)
|
|
& (line.debit != 0))
|
|
credit_kind = (
|
|
cls.get_kind(credit_type)
|
|
& (line.credit != 0))
|
|
columns = [
|
|
line.party.as_('id'),
|
|
line.party.as_('party'),
|
|
move.company.as_('company'),
|
|
(Sum(line.debit) - Sum(line.credit)).as_('balance'),
|
|
]
|
|
|
|
terms = cls.get_terms()
|
|
factor = cls.get_unit_factor()
|
|
# Ensure None are before 0 to get the next index pointing to the next
|
|
# value and not a None value
|
|
term_values = sorted(
|
|
list(terms.values()), key=lambda x: ((x is not None), x or 0))
|
|
|
|
line_date = Coalesce(line.maturity_date, move.date)
|
|
for name, value in terms.items():
|
|
if value is None or factor is None or date is None:
|
|
columns.append(Literal(None).as_(name))
|
|
continue
|
|
cond = line_date <= (date - value * factor)
|
|
idx = term_values.index(value)
|
|
if idx + 1 < len(terms):
|
|
cond &= line_date > (
|
|
date - (term_values[idx + 1] or 0) * factor)
|
|
columns.append(
|
|
Sum(Case((cond, line.debit - line.credit), else_=0)).as_(name))
|
|
|
|
return line.join(move, condition=line.move == move.id
|
|
).join(account, condition=line.account == account.id
|
|
).join(type_, condition=account.type == type_.id
|
|
).join(debit_type, 'LEFT',
|
|
condition=account.debit_type == debit_type.id
|
|
).join(credit_type, 'LEFT',
|
|
condition=account.credit_type == credit_type.id
|
|
).join(reconciliation, 'LEFT',
|
|
condition=reconciliation.id == line.reconciliation
|
|
).select(*columns,
|
|
where=(line.party != Null)
|
|
& (kind | debit_kind | credit_kind)
|
|
& ((line.reconciliation == Null)
|
|
| (reconciliation.date > date))
|
|
& (move.date <= date)
|
|
& (account.company == company_id),
|
|
group_by=(line.party, move.company))
|
|
|
|
@classmethod
|
|
def get_terms(cls):
|
|
context = Transaction().context
|
|
return {
|
|
'term0': 0,
|
|
'term1': context.get('term1'),
|
|
'term2': context.get('term2'),
|
|
'term3': context.get('term3'),
|
|
}
|
|
|
|
@classmethod
|
|
def get_unit_factor(cls):
|
|
context = Transaction().context
|
|
return {
|
|
'year': relativedelta(years=1),
|
|
'month': relativedelta(months=1),
|
|
'week': relativedelta(weeks=1),
|
|
'day': relativedelta(days=1)
|
|
}.get(context.get('unit', 'day'))
|
|
|
|
@classmethod
|
|
def get_kind(cls, account_type):
|
|
context = Transaction().context
|
|
type_ = context.get('type', 'customer')
|
|
if type_ == 'customer_supplier':
|
|
return account_type.payable | account_type.receivable
|
|
elif type_ == 'supplier':
|
|
return account_type.payable
|
|
elif type_ == 'customer':
|
|
return account_type.receivable
|
|
else:
|
|
return Literal(False)
|
|
|
|
def get_currency(self, name):
|
|
return self.company.currency.id
|
|
|
|
@classmethod
|
|
def search_currency(cls, name, clause):
|
|
return [('company.' + clause[0], *clause[1:])]
|
|
|
|
|
|
class AgedBalanceReport(Report):
|
|
__name__ = 'account.aged_balance'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
pool = Pool()
|
|
Company = pool.get('company.company')
|
|
Context = pool.get('account.aged_balance.context')
|
|
AgedBalance = pool.get('account.aged_balance')
|
|
context = Transaction().context
|
|
|
|
report_context = super().get_context(records, header, data)
|
|
|
|
context_fields = Context.fields_get(['type', 'unit'])
|
|
|
|
report_context['company'] = Company(context['company'])
|
|
report_context['date'] = context['date']
|
|
report_context['type'] = dict(
|
|
context_fields['type']['selection'])[context['type']]
|
|
report_context['unit'] = dict(
|
|
context_fields['unit']['selection'])[context['unit']]
|
|
report_context.update(AgedBalance.get_terms())
|
|
report_context['sum'] = cls.sum
|
|
return report_context
|
|
|
|
@classmethod
|
|
def sum(cls, records, field):
|
|
return sum((getattr(r, field) for r in records), Decimal('0'))
|
|
|
|
|
|
class CreateChartStart(ModelView):
|
|
__name__ = 'account.create_chart.start'
|
|
|
|
|
|
class CreateChartAccount(ModelView):
|
|
__name__ = 'account.create_chart.account'
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
account_template = fields.Many2One('account.account.template',
|
|
'Account Template', required=True, domain=[('parent', '=', None)])
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
|
|
class CreateChartProperties(ModelView):
|
|
__name__ = 'account.create_chart.properties'
|
|
company = fields.Many2One('company.company', 'Company')
|
|
account_receivable = fields.Many2One('account.account',
|
|
'Default Receivable Account',
|
|
domain=[
|
|
('closed', '!=', True),
|
|
('type.receivable', '=', True),
|
|
('party_required', '=', True),
|
|
('company', '=', Eval('company', -1)),
|
|
])
|
|
account_payable = fields.Many2One('account.account',
|
|
'Default Payable Account',
|
|
domain=[
|
|
('closed', '!=', True),
|
|
('type.payable', '=', True),
|
|
('party_required', '=', True),
|
|
('company', '=', Eval('company', -1)),
|
|
])
|
|
|
|
|
|
class CreateChart(Wizard):
|
|
__name__ = 'account.create_chart'
|
|
start = StateView('account.create_chart.start',
|
|
'account.create_chart_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('OK', 'account', 'tryton-ok', default=True),
|
|
])
|
|
account = StateView('account.create_chart.account',
|
|
'account.create_chart_account_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Create', 'create_account', 'tryton-ok', default=True),
|
|
])
|
|
create_account = StateTransition()
|
|
properties = StateView('account.create_chart.properties',
|
|
'account.create_chart_properties_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Create', 'create_properties', 'tryton-ok', default=True),
|
|
])
|
|
create_properties = StateTransition()
|
|
|
|
def transition_create_account(self):
|
|
pool = Pool()
|
|
TaxCodeTemplate = pool.get('account.tax.code.template')
|
|
TaxCodeLineTemplate = pool.get('account.tax.code.line.template')
|
|
TaxTemplate = pool.get('account.tax.template')
|
|
TaxRuleTemplate = pool.get('account.tax.rule.template')
|
|
TaxRuleLineTemplate = \
|
|
pool.get('account.tax.rule.line.template')
|
|
Config = pool.get('ir.configuration')
|
|
Account = pool.get('account.account')
|
|
Warning = pool.get('res.user.warning')
|
|
transaction = Transaction()
|
|
|
|
company = self.account.company
|
|
accounts = Account.search([('company', '=', company.id)], limit=1)
|
|
if accounts:
|
|
key = 'duplicated_chart.%d' % company.id
|
|
if Warning.check(key):
|
|
raise ChartWarning(key,
|
|
gettext('account.msg_account_chart_exists',
|
|
company=company.rec_name))
|
|
|
|
with transaction.set_context(language=Config.get_language(),
|
|
company=company.id):
|
|
account_template = self.account.account_template
|
|
|
|
# Create account types
|
|
template2type = {}
|
|
account_template.type.create_type(
|
|
company.id,
|
|
template2type=template2type)
|
|
|
|
# Create accounts
|
|
template2account = {}
|
|
account_template.create_account(
|
|
company.id,
|
|
template2account=template2account,
|
|
template2type=template2type)
|
|
|
|
# Create taxes
|
|
template2tax = {}
|
|
TaxTemplate.create_tax(
|
|
account_template.id, company.id,
|
|
template2account=template2account,
|
|
template2tax=template2tax)
|
|
|
|
# Create tax codes
|
|
template2tax_code = {}
|
|
TaxCodeTemplate.create_tax_code(
|
|
account_template.id, company.id,
|
|
template2tax_code=template2tax_code)
|
|
|
|
# Create tax code lines
|
|
template2tax_code_line = {}
|
|
TaxCodeLineTemplate.create_tax_code_line(
|
|
account_template.id,
|
|
template2tax=template2tax,
|
|
template2tax_code=template2tax_code,
|
|
template2tax_code_line=template2tax_code_line)
|
|
|
|
# Update taxes and replaced_by on accounts
|
|
account_template.update_account2(template2account, template2tax)
|
|
|
|
# Create tax rules
|
|
template2rule = {}
|
|
TaxRuleTemplate.create_rule(
|
|
account_template.id, company.id,
|
|
template2rule=template2rule)
|
|
|
|
# Create tax rule lines
|
|
template2rule_line = {}
|
|
TaxRuleLineTemplate.create_rule_line(
|
|
account_template.id, template2tax, template2rule,
|
|
template2rule_line=template2rule_line)
|
|
return 'properties'
|
|
|
|
def get_account(self, template_id):
|
|
pool = Pool()
|
|
Account = pool.get('account.account')
|
|
ModelData = pool.get('ir.model.data')
|
|
template_id = ModelData.get_id(template_id)
|
|
account, = Account.search([
|
|
('template', '=', template_id),
|
|
('company', '=', self.account.company.id),
|
|
], limit=1)
|
|
return account.id
|
|
|
|
def default_properties(self, fields):
|
|
pool = Pool()
|
|
Account = pool.get('account.account')
|
|
|
|
defaults = {
|
|
'company': self.account.company.id,
|
|
}
|
|
|
|
receivable_accounts = Account.search([
|
|
('type.receivable', '=', True),
|
|
('company', '=', self.account.company.id),
|
|
], limit=2)
|
|
payable_accounts = Account.search([
|
|
('type.payable', '=', True),
|
|
('company', '=', self.account.company.id),
|
|
], limit=2)
|
|
|
|
if len(receivable_accounts) == 1:
|
|
defaults['account_receivable'] = receivable_accounts[0].id
|
|
else:
|
|
defaults['account_receivable'] = None
|
|
if len(payable_accounts) == 1:
|
|
defaults['account_payable'] = payable_accounts[0].id
|
|
else:
|
|
defaults['account_payable'] = None
|
|
|
|
return defaults
|
|
|
|
def transition_create_properties(self):
|
|
pool = Pool()
|
|
Configuration = pool.get('account.configuration')
|
|
|
|
with Transaction().set_context(company=self.properties.company.id):
|
|
account_receivable = self.properties.account_receivable
|
|
account_payable = self.properties.account_payable
|
|
config = Configuration(1)
|
|
config.default_account_receivable = account_receivable
|
|
config.default_account_payable = account_payable
|
|
config.save()
|
|
return 'end'
|
|
|
|
|
|
class UpdateChartStart(ModelView):
|
|
__name__ = 'account.update_chart.start'
|
|
account = fields.Many2One(
|
|
'account.account', "Root Account", required=True,
|
|
domain=[
|
|
('parent', '=', None),
|
|
('template', '!=', None),
|
|
])
|
|
|
|
|
|
class UpdateChartSucceed(ModelView):
|
|
__name__ = 'account.update_chart.succeed'
|
|
|
|
|
|
class UpdateChart(Wizard):
|
|
__name__ = 'account.update_chart'
|
|
start = StateView('account.update_chart.start',
|
|
'account.update_chart_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Update', 'update', 'tryton-ok', default=True),
|
|
])
|
|
update = StateTransition()
|
|
succeed = StateView('account.update_chart.succeed',
|
|
'account.update_chart_succeed_view_form', [
|
|
Button('OK', 'end', 'tryton-ok', default=True),
|
|
])
|
|
|
|
@check_access
|
|
def default_start(self, fields):
|
|
pool = Pool()
|
|
Account = pool.get('account.account')
|
|
|
|
defaults = {}
|
|
charts = Account.search([
|
|
('parent', '=', None),
|
|
('template', '!=', None),
|
|
], limit=2)
|
|
if len(charts) == 1:
|
|
defaults['account'] = charts[0].id
|
|
return defaults
|
|
|
|
@inactive_records
|
|
def transition_update(self):
|
|
pool = Pool()
|
|
Account = pool.get('account.account')
|
|
TaxCode = pool.get('account.tax.code')
|
|
TaxCodeTemplate = pool.get('account.tax.code.template')
|
|
TaxCodeLine = pool.get('account.tax.code.line')
|
|
TaxCodeLineTemplate = pool.get('account.tax.code.line.template')
|
|
Tax = pool.get('account.tax')
|
|
TaxTemplate = pool.get('account.tax.template')
|
|
TaxRule = pool.get('account.tax.rule')
|
|
TaxRuleTemplate = pool.get('account.tax.rule.template')
|
|
TaxRuleLine = pool.get('account.tax.rule.line')
|
|
TaxRuleLineTemplate = \
|
|
pool.get('account.tax.rule.line.template')
|
|
|
|
# re-browse to have inactive context
|
|
account = Account(self.start.account.id)
|
|
company = account.company
|
|
|
|
template2type = {}
|
|
# Update account types
|
|
account.type.update_type(template2type=template2type)
|
|
# Create missing account types
|
|
if account.type.template:
|
|
account.type.template.create_type(
|
|
company.id,
|
|
template2type=template2type)
|
|
# Update again to set new parent
|
|
account.type.update_type(template2type=template2type)
|
|
|
|
# Update accounts
|
|
template2account = {}
|
|
account.update_account(template2account=template2account,
|
|
template2type=template2type)
|
|
# Create missing accounts
|
|
if account.template:
|
|
account.template.create_account(
|
|
company.id,
|
|
template2account=template2account,
|
|
template2type=template2type)
|
|
|
|
# Update taxes
|
|
template2tax = {}
|
|
Tax.update_tax(
|
|
company.id,
|
|
template2account=template2account,
|
|
template2tax=template2tax)
|
|
# Create missing taxes
|
|
if account.template:
|
|
TaxTemplate.create_tax(
|
|
account.template.id, account.company.id,
|
|
template2account=template2account,
|
|
template2tax=template2tax)
|
|
# Update again to set new parent
|
|
Tax.update_tax(
|
|
company.id,
|
|
template2account=template2account,
|
|
template2tax=template2tax)
|
|
|
|
# Update tax codes
|
|
template2tax_code = {}
|
|
TaxCode.update_tax_code(
|
|
company.id,
|
|
template2tax_code=template2tax_code)
|
|
# Create missing tax codes
|
|
if account.template:
|
|
TaxCodeTemplate.create_tax_code(
|
|
account.template.id, company.id,
|
|
template2tax_code=template2tax_code)
|
|
# Update again to set new parent
|
|
TaxCode.update_tax_code(
|
|
company.id,
|
|
template2tax_code=template2tax_code)
|
|
|
|
# Update tax code lines
|
|
template2tax_code_line = {}
|
|
TaxCodeLine.update_tax_code_line(
|
|
company.id,
|
|
template2tax=template2tax,
|
|
template2tax_code=template2tax_code,
|
|
template2tax_code_line=template2tax_code_line)
|
|
# Create missing tax code lines
|
|
if account.template:
|
|
TaxCodeLineTemplate.create_tax_code_line(
|
|
account.template.id,
|
|
template2tax=template2tax,
|
|
template2tax_code=template2tax_code,
|
|
template2tax_code_line=template2tax_code_line)
|
|
|
|
# Update parent, taxes and replaced_by on accounts
|
|
account.update_account2(template2account, template2tax)
|
|
|
|
# Update tax rules
|
|
template2rule = {}
|
|
TaxRule.update_rule(company.id, template2rule=template2rule)
|
|
# Create missing tax rules
|
|
if account.template:
|
|
TaxRuleTemplate.create_rule(
|
|
account.template.id, account.company.id,
|
|
template2rule=template2rule)
|
|
|
|
# Update tax rule lines
|
|
template2rule_line = {}
|
|
TaxRuleLine.update_rule_line(
|
|
company.id, template2tax, template2rule,
|
|
template2rule_line=template2rule_line)
|
|
# Create missing tax rule lines
|
|
if account.template:
|
|
TaxRuleLineTemplate.create_rule_line(
|
|
account.template.id, template2tax, template2rule,
|
|
template2rule_line=template2rule_line)
|
|
return 'succeed'
|