Files
tryton/account.py
T
2014-01-26 14:28:58 +01:00

403 lines
15 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.
from decimal import Decimal
import copy
from sql import Column
from sql.aggregate import Sum
from sql.conditionals import Coalesce
from trytond.model import ModelView, ModelSQL, fields
from trytond.wizard import Wizard, StateView, StateAction, Button
from trytond.pyson import Eval, PYSONEncoder
from trytond.transaction import Transaction
from trytond.pool import Pool
__all__ = ['Account', 'OpenChartAccountStart', 'OpenChartAccount',
'AccountSelection', 'AccountAccountSelection']
class Account(ModelSQL, ModelView):
'Analytic Account'
__name__ = 'analytic_account.account'
name = fields.Char('Name', required=True, translate=True, select=True)
code = fields.Char('Code', select=True)
active = fields.Boolean('Active', select=True)
company = fields.Many2One('company.company', 'Company')
currency = fields.Many2One('currency.currency', 'Currency', required=True)
currency_digits = fields.Function(fields.Integer('Currency Digits'),
'on_change_with_currency_digits')
type = fields.Selection([
('root', 'Root'),
('view', 'View'),
('normal', 'Normal'),
], 'Type', required=True)
root = fields.Many2One('analytic_account.account', 'Root', select=True,
domain=[('parent', '=', None)],
states={
'invisible': Eval('type') == 'root',
'required': Eval('type') != 'root',
},
depends=['type'])
parent = fields.Many2One('analytic_account.account', 'Parent', select=True,
domain=[('parent', 'child_of', Eval('root'))],
states={
'invisible': Eval('type') == 'root',
'required': Eval('type') != 'root',
},
depends=['root', 'type'])
childs = fields.One2Many('analytic_account.account', 'parent', 'Children')
balance = fields.Function(fields.Numeric('Balance',
digits=(16, Eval('currency_digits', 1)), depends=['currency_digits']),
'get_balance')
credit = fields.Function(fields.Numeric('Credit',
digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']),
'get_credit_debit')
debit = fields.Function(fields.Numeric('Debit',
digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']),
'get_credit_debit')
state = fields.Selection([
('draft', 'Draft'),
('opened', 'Opened'),
('closed', 'Closed'),
], 'State', required=True)
note = fields.Text('Note')
display_balance = fields.Selection([
('debit-credit', 'Debit - Credit'),
('credit-debit', 'Credit - Debit'),
], 'Display Balance', required=True)
mandatory = fields.Boolean('Mandatory', states={
'invisible': Eval('type') != 'root',
},
depends=['type'])
@classmethod
def __setup__(cls):
super(Account, cls).__setup__()
cls._order.insert(0, ('code', 'ASC'))
@staticmethod
def default_active():
return True
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_currency():
Company = Pool().get('company.company')
if Transaction().context.get('company'):
company = Company(Transaction().context['company'])
return company.currency.id
@staticmethod
def default_type():
return 'normal'
@staticmethod
def default_state():
return 'draft'
@staticmethod
def default_display_balance():
return 'credit-debit'
@staticmethod
def default_mandatory():
return False
@classmethod
def validate(cls, accounts):
super(Account, cls).validate(accounts)
cls.check_recursion(accounts)
@fields.depends('currency')
def on_change_with_currency_digits(self, name=None):
if self.currency:
return self.currency.digits
return 2
@classmethod
def get_balance(cls, accounts, name):
res = {}
pool = Pool()
Line = pool.get('analytic_account.line')
MoveLine = pool.get('account.move.line')
Account = pool.get('account.account')
Company = pool.get('company.company')
Currency = pool.get('currency.currency')
cursor = Transaction().cursor
table = cls.__table__()
line = Line.__table__()
move_line = MoveLine.__table__()
a_account = Account.__table__()
company = Company.__table__()
ids = [a.id for a in accounts]
childs = cls.search([('parent', 'child_of', ids)])
all_ids = {}.fromkeys(ids + [c.id for c in childs]).keys()
id2account = {}
all_accounts = cls.browse(all_ids)
for account in all_accounts:
id2account[account.id] = account
line_query = Line.query_get(line)
cursor.execute(*table.join(line, 'LEFT',
condition=table.id == line.account
).join(move_line, 'LEFT',
condition=move_line.id == line.move_line
).join(a_account, 'LEFT',
condition=a_account.id == move_line.account
).join(company, 'LEFT',
condition=company.id == a_account.company
).select(table.id,
Sum(Coalesce(line.debit, 0) - Coalesce(line.credit, 0)),
company.currency,
where=(table.type != 'view')
& table.id.in_(all_ids)
& table.active & line_query,
group_by=(table.id, company.currency)))
account_sum = {}
id2currency = {}
for account_id, sum, currency_id in cursor.fetchall():
account_sum.setdefault(account_id, Decimal('0.0'))
if currency_id != id2account[account_id].currency.id:
currency = None
if currency_id in id2currency:
currency = id2currency[currency_id]
else:
currency = Currency(currency_id)
id2currency[currency.id] = currency
account_sum[account_id] += Currency.compute(currency, sum,
id2account[account_id].currency, round=True)
else:
account_sum[account_id] += \
id2account[account_id].currency.round(sum)
for account_id in ids:
res.setdefault(account_id, Decimal('0.0'))
childs = cls.search([
('parent', 'child_of', [account_id]),
])
to_currency = id2account[account_id].currency
for child in childs:
from_currency = id2account[child.id].currency
res[account_id] += Currency.compute(from_currency,
account_sum.get(child.id, Decimal('0.0')), to_currency,
round=True)
res[account_id] = to_currency.round(res[account_id])
if id2account[account_id].display_balance == 'credit-debit':
res[account_id] = - res[account_id]
return res
@classmethod
def get_credit_debit(cls, accounts, name):
res = {}
pool = Pool()
Line = pool.get('analytic_account.line')
MoveLine = pool.get('account.move.line')
Account = pool.get('account.account')
Company = pool.get('company.company')
Currency = pool.get('currency.currency')
cursor = Transaction().cursor
table = cls.__table__()
line = Line.__table__()
move_line = MoveLine.__table__()
a_account = Account.__table__()
company = Company.__table__()
if name not in ('credit', 'debit'):
raise Exception('Bad argument')
id2account = {}
ids = [a.id for a in accounts]
for account in accounts:
res[account.id] = Decimal('0.0')
id2account[account.id] = account
line_query = Line.query_get(line)
cursor.execute(*table.join(line, 'LEFT',
condition=table.id == line.account
).join(move_line, 'LEFT',
condition=move_line.id == line.move_line
).join(a_account, 'LEFT',
condition=a_account.id == move_line.account
).join(company, 'LEFT',
condition=company.id == a_account.company
).select(table.id,
Sum(Coalesce(Column(line, name), 0)),
company.currency,
where=(table.type != 'view')
& table.id.in_(ids)
& table.active & line_query,
group_by=(table.id, company.currency)))
id2currency = {}
for account_id, sum, currency_id in cursor.fetchall():
if currency_id != id2account[account_id].currency.id:
currency = None
if currency_id in id2currency:
currency = id2currency[currency_id]
else:
currency = Currency(currency_id)
id2currency[currency.id] = currency
res[account_id] += Currency.compute(currency, sum,
id2account[account_id].currency, round=True)
else:
res[account_id] += id2account[account_id].currency.round(sum)
return res
def get_rec_name(self, name):
if self.code:
return self.code + ' - ' + unicode(self.name)
else:
return unicode(self.name)
@classmethod
def search_rec_name(cls, name, clause):
accounts = cls.search([('code',) + tuple(clause[1:])], limit=1)
if accounts:
return [('code',) + tuple(clause[1:])]
else:
return [(cls._rec_name,) + tuple(clause[1:])]
@classmethod
def convert_view(cls, tree):
res = tree.xpath('//field[@name=\'analytic_accounts\']')
if not res:
return
element_accounts = res[0]
root_accounts = cls.search([
('parent', '=', None),
])
if not root_accounts:
element_accounts.getparent().getparent().remove(
element_accounts.getparent())
return
for account in root_accounts:
newelement = copy.copy(element_accounts)
newelement.tag = 'label'
newelement.set('name', 'analytic_account_' + str(account.id))
element_accounts.addprevious(newelement)
newelement = copy.copy(element_accounts)
newelement.set('name', 'analytic_account_' + str(account.id))
element_accounts.addprevious(newelement)
parent = element_accounts.getparent()
parent.remove(element_accounts)
@classmethod
def analytic_accounts_fields_get(cls, field, fields_names=None,
states=None, required_states=None):
res = {}
if fields_names is None:
fields_names = []
if states is None:
states = {}
encoder = PYSONEncoder()
root_accounts = cls.search([
('parent', '=', None),
])
for account in root_accounts:
name = 'analytic_account_' + str(account.id)
if name in fields_names or not fields_names:
res[name] = field.copy()
field_states = states.copy()
if account.mandatory:
if required_states:
field_states['required'] = required_states
else:
field_states['required'] = True
res[name]['states'] = encoder.encode(field_states)
res[name]['string'] = account.name
res[name]['relation'] = cls.__name__
res[name]['domain'] = PYSONEncoder().encode([
('root', '=', account.id),
('type', '=', 'normal')])
return res
class OpenChartAccountStart(ModelView):
'Open Chart of Accounts'
__name__ = 'analytic_account.open_chart.start'
start_date = fields.Date('Start Date')
end_date = fields.Date('End Date')
class OpenChartAccount(Wizard):
'Open Chart of Accounts'
__name__ = 'analytic_account.open_chart'
start = StateView('analytic_account.open_chart.start',
'analytic_account.open_chart_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Open', 'open_', 'tryton-ok', default=True),
])
open_ = StateAction('analytic_account.act_account_tree2')
def do_open_(self, action):
action['pyson_context'] = PYSONEncoder().encode({
'start_date': self.start.start_date,
'end_date': self.start.end_date,
})
return action, {}
def transition_open_(self):
return 'end'
class AccountSelection(ModelSQL, ModelView):
'Analytic Account Selection'
__name__ = 'analytic_account.account.selection'
accounts = fields.Many2Many(
'analytic_account.account-analytic_account.account.selection',
'selection', 'account', 'Accounts')
@classmethod
def __setup__(cls):
super(AccountSelection, cls).__setup__()
cls._error_messages.update({
'root_account': ('Can not have many accounts with the same '
'root or a missing mandatory root account on "%s".'),
})
@classmethod
def validate(cls, selections):
super(AccountSelection, cls).validate(selections)
cls.check_root(selections)
@classmethod
def check_root(cls, selections):
"Check Root"
Account = Pool().get('analytic_account.account')
root_accounts = Account.search([
('parent', '=', None),
])
for selection in selections:
roots = []
for account in selection.accounts:
if account.root.id in roots:
cls.raise_user_error('root_account', (account.rec_name,))
roots.append(account.root.id)
if Transaction().user: # Root can by pass
for account in root_accounts:
if account.mandatory:
if not account.id in roots:
cls.raise_user_error('root_account',
(account.rec_name,))
class AccountAccountSelection(ModelSQL):
'Analytic Account - Analytic Account Selection'
__name__ = 'analytic_account.account-analytic_account.account.selection'
_table = 'analytic_account_account_selection_rel'
selection = fields.Many2One('analytic_account.account.selection',
'Selection', ondelete='CASCADE', required=True, select=True)
account = fields.Many2One('analytic_account.account', 'Account',
ondelete='RESTRICT', required=True, select=True)