2157 lines
78 KiB
Python
2157 lines
78 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 datetime
|
|
import operator
|
|
from itertools import izip, groupby
|
|
from sql import Column, Literal
|
|
from sql.aggregate import Sum
|
|
from sql.conditionals import Coalesce
|
|
|
|
from trytond.model import ModelView, ModelSQL, fields
|
|
from trytond.wizard import Wizard, StateView, StateAction, StateTransition, \
|
|
Button
|
|
from trytond.report import Report
|
|
from trytond.tools import reduce_ids
|
|
from trytond.pyson import Eval, PYSONEncoder, Date
|
|
from trytond.transaction import Transaction
|
|
from trytond.pool import Pool
|
|
from trytond import backend
|
|
|
|
__all__ = ['TypeTemplate', 'Type', 'OpenType', 'AccountTemplate', 'Account',
|
|
'AccountDeferral', 'OpenChartAccountStart', 'OpenChartAccount',
|
|
'PrintGeneralLedgerStart', 'PrintGeneralLedger', 'GeneralLedger',
|
|
'PrintTrialBalanceStart', 'PrintTrialBalance', 'TrialBalance',
|
|
'OpenBalanceSheetStart', 'OpenBalanceSheet',
|
|
'OpenIncomeStatementStart', 'OpenIncomeStatement',
|
|
'CreateChartStart', 'CreateChartAccount', 'CreateChartProperties',
|
|
'CreateChart', 'UpdateChartStart', 'UpdateChartSucceed', 'UpdateChart',
|
|
'OpenThirdPartyBalanceStart', 'OpenThirdPartyBalance', 'ThirdPartyBalance',
|
|
'OpenAgedBalanceStart', 'OpenAgedBalance', 'AgedBalance']
|
|
|
|
|
|
class TypeTemplate(ModelSQL, ModelView):
|
|
'Account Type Template'
|
|
__name__ = 'account.account.type.template'
|
|
name = fields.Char('Name', required=True, translate=True)
|
|
parent = fields.Many2One('account.account.type.template', 'Parent',
|
|
ondelete="RESTRICT")
|
|
childs = fields.One2Many('account.account.type.template', 'parent',
|
|
'Children')
|
|
sequence = fields.Integer('Sequence')
|
|
balance_sheet = fields.Boolean('Balance Sheet')
|
|
income_statement = fields.Boolean('Income Statement')
|
|
display_balance = fields.Selection([
|
|
('debit-credit', 'Debit - Credit'),
|
|
('credit-debit', 'Credit - Debit'),
|
|
], 'Display Balance', required=True)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(TypeTemplate, cls).__setup__()
|
|
cls._order.insert(0, ('sequence', 'ASC'))
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
TableHandler = backend.get('TableHandler')
|
|
cursor = Transaction().cursor
|
|
table = TableHandler(cursor, cls, module_name)
|
|
|
|
super(TypeTemplate, cls).__register__(module_name)
|
|
|
|
# Migration from 2.4: drop required on sequence
|
|
table.not_null_action('sequence', action='remove')
|
|
|
|
@classmethod
|
|
def validate(cls, records):
|
|
super(TypeTemplate, cls).validate(records)
|
|
cls.check_recursion(records, rec_name='name')
|
|
|
|
@staticmethod
|
|
def order_sequence(tables):
|
|
table, _ = tables[None]
|
|
return [table.sequence == None, table.sequence]
|
|
|
|
@staticmethod
|
|
def default_balance_sheet():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_income_statement():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_display_balance():
|
|
return 'debit-credit'
|
|
|
|
def get_rec_name(self, name):
|
|
if self.parent:
|
|
return self.parent.get_rec_name(name) + '\\' + self.name
|
|
else:
|
|
return self.name
|
|
|
|
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.balance_sheet != self.balance_sheet:
|
|
res['balance_sheet'] = self.balance_sheet
|
|
if not type or type.income_statement != self.income_statement:
|
|
res['income_statement'] = self.income_statement
|
|
if not type or type.display_balance != self.display_balance:
|
|
res['display_balance'] = self.display_balance
|
|
if not type or type.template != self:
|
|
res['template'] = self.id
|
|
return res
|
|
|
|
def create_type(self, company_id, template2type=None, parent_id=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.
|
|
Return the id of the type created
|
|
'''
|
|
pool = Pool()
|
|
Type = pool.get('account.account.type')
|
|
Lang = pool.get('ir.lang')
|
|
Config = pool.get('ir.configuration')
|
|
|
|
if template2type is None:
|
|
template2type = {}
|
|
|
|
if self.id not in template2type:
|
|
vals = self._get_type_value()
|
|
vals['company'] = company_id
|
|
vals['parent'] = parent_id
|
|
|
|
new_type, = Type.create([vals])
|
|
|
|
prev_lang = self._context.get('language') or Config.get_language()
|
|
prev_data = {}
|
|
for field_name, field in self._fields.iteritems():
|
|
if getattr(field, 'translate', False):
|
|
prev_data[field_name] = getattr(self, field_name)
|
|
for lang in Lang.get_translatable_languages():
|
|
if lang == prev_lang:
|
|
continue
|
|
with Transaction().set_context(language=lang):
|
|
template = self.__class__(self.id)
|
|
data = {}
|
|
for field_name, field in template._fields.iteritems():
|
|
if (getattr(field, 'translate', False)
|
|
and (getattr(template, field_name) !=
|
|
prev_data[field_name])):
|
|
data[field_name] = getattr(template, field_name)
|
|
if data:
|
|
Type.write([new_type], data)
|
|
template2type[self.id] = new_type.id
|
|
new_id = template2type[self.id]
|
|
|
|
new_childs = []
|
|
for child in self.childs:
|
|
new_childs.append(child.create_type(company_id,
|
|
template2type=template2type, parent_id=new_id))
|
|
return new_id
|
|
|
|
|
|
class Type(ModelSQL, ModelView):
|
|
'Account Type'
|
|
__name__ = 'account.account.type'
|
|
name = fields.Char('Name', size=None, required=True, translate=True)
|
|
parent = fields.Many2One('account.account.type', 'Parent',
|
|
ondelete="RESTRICT", domain=[
|
|
('company', '=', Eval('company')),
|
|
], depends=['company'])
|
|
childs = fields.One2Many('account.account.type', 'parent', 'Children',
|
|
domain=[
|
|
('company', '=', Eval('company')),
|
|
], depends=['company'])
|
|
sequence = fields.Integer('Sequence',
|
|
help='Use to order the account type')
|
|
currency_digits = fields.Function(fields.Integer('Currency Digits'),
|
|
'get_currency_digits')
|
|
amount = fields.Function(fields.Numeric('Amount',
|
|
digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']),
|
|
'get_amount')
|
|
balance_sheet = fields.Boolean('Balance Sheet')
|
|
income_statement = fields.Boolean('Income Statement')
|
|
display_balance = fields.Selection([
|
|
('debit-credit', 'Debit - Credit'),
|
|
('credit-debit', 'Credit - Debit'),
|
|
], 'Display Balance', required=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True,
|
|
ondelete="RESTRICT")
|
|
template = fields.Many2One('account.account.type.template', 'Template')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Type, cls).__setup__()
|
|
cls._order.insert(0, ('sequence', 'ASC'))
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
TableHandler = backend.get('TableHandler')
|
|
cursor = Transaction().cursor
|
|
table = TableHandler(cursor, cls, module_name)
|
|
|
|
super(Type, cls).__register__(module_name)
|
|
|
|
# Migration from 2.4: drop required on sequence
|
|
table.not_null_action('sequence', action='remove')
|
|
|
|
@classmethod
|
|
def validate(cls, types):
|
|
super(Type, cls).validate(types)
|
|
cls.check_recursion(types, rec_name='name')
|
|
|
|
@staticmethod
|
|
def order_sequence(tables):
|
|
table, _ = tables[None]
|
|
return [table.sequence == None, table.sequence]
|
|
|
|
@staticmethod
|
|
def default_balance_sheet():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_income_statement():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_display_balance():
|
|
return 'debit-credit'
|
|
|
|
def get_currency_digits(self, name):
|
|
return self.company.currency.digits
|
|
|
|
@classmethod
|
|
def get_amount(cls, types, name):
|
|
pool = Pool()
|
|
Account = pool.get('account.account')
|
|
|
|
res = {}
|
|
for type_ in types:
|
|
res[type_.id] = Decimal('0.0')
|
|
|
|
childs = cls.search([
|
|
('parent', 'child_of', [t.id for t in types]),
|
|
])
|
|
type_sum = {}
|
|
for type_ in childs:
|
|
type_sum[type_.id] = Decimal('0.0')
|
|
|
|
accounts = Account.search([
|
|
('type', 'in', [t.id for t in childs]),
|
|
('kind', '!=', 'view'),
|
|
])
|
|
for account in accounts:
|
|
type_sum[account.type.id] += (account.debit - account.credit)
|
|
|
|
for type_ in types:
|
|
childs = cls.search([
|
|
('parent', 'child_of', [type_.id]),
|
|
])
|
|
for child in childs:
|
|
res[type_.id] += type_sum[child.id]
|
|
res[type_.id] = type_.company.currency.round(res[type_.id])
|
|
if type_.display_balance == 'credit-debit':
|
|
res[type_.id] = - res[type_.id]
|
|
return res
|
|
|
|
def get_rec_name(self, name):
|
|
if self.parent:
|
|
return self.parent.get_rec_name(name) + '\\' + self.name
|
|
else:
|
|
return self.name
|
|
|
|
@classmethod
|
|
def delete(cls, types):
|
|
types = cls.search([
|
|
('parent', 'child_of', [t.id for t in types]),
|
|
])
|
|
super(Type, cls).delete(types)
|
|
|
|
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
|
|
'''
|
|
pool = Pool()
|
|
Lang = pool.get('ir.lang')
|
|
Config = pool.get('ir.configuration')
|
|
|
|
if template2type is None:
|
|
template2type = {}
|
|
|
|
if self.template:
|
|
vals = self.template._get_type_value(type=self)
|
|
if vals:
|
|
self.write([self], vals)
|
|
|
|
prev_lang = self._context.get('language') or Config.get_language()
|
|
prev_data = {}
|
|
for field_name, field in self.template._fields.iteritems():
|
|
if getattr(field, 'translate', False):
|
|
prev_data[field_name] = getattr(self.template, field_name)
|
|
for lang in Lang.get_translatable_languages():
|
|
if lang == prev_lang:
|
|
continue
|
|
with Transaction().set_context(language=lang):
|
|
type_ = self.__class__(self.id)
|
|
data = {}
|
|
for field_name, field in (
|
|
type_.template._fields.iteritems()):
|
|
if (getattr(field, 'translate', False)
|
|
and (getattr(type_.template, field_name) !=
|
|
prev_data[field_name])):
|
|
data[field_name] = getattr(type_.template,
|
|
field_name)
|
|
if data:
|
|
self.write([type_], data)
|
|
template2type[self.template.id] = self.id
|
|
|
|
for child in self.childs:
|
|
child.update_type(template2type=template2type)
|
|
|
|
|
|
class OpenType(Wizard):
|
|
'Open Type'
|
|
__name__ = 'account.account.open_type'
|
|
start_state = 'open_'
|
|
open_ = StateAction('account.act_account_list2')
|
|
|
|
def do_open_(self, action):
|
|
action['pyson_domain'] = PYSONEncoder().encode([
|
|
('type', '=', Transaction().context['active_id']),
|
|
('kind', '!=', 'view'),
|
|
])
|
|
action['pyson_context'] = PYSONEncoder().encode({
|
|
'date': Transaction().context.get('date'),
|
|
'posted': Transaction().context.get('posted'),
|
|
'cumulate': Transaction().context.get('cumulate'),
|
|
})
|
|
return action, {}
|
|
|
|
|
|
class AccountTemplate(ModelSQL, ModelView):
|
|
'Account Template'
|
|
__name__ = 'account.account.template'
|
|
name = fields.Char('Name', size=None, required=True, translate=True,
|
|
select=True)
|
|
code = fields.Char('Code', size=None, select=True)
|
|
type = fields.Many2One('account.account.type.template', 'Type',
|
|
ondelete="RESTRICT",
|
|
states={
|
|
'invisible': Eval('kind') == 'view',
|
|
'required': Eval('kind') != 'view',
|
|
}, depends=['kind'])
|
|
parent = fields.Many2One('account.account.template', 'Parent', select=True,
|
|
ondelete="RESTRICT")
|
|
childs = fields.One2Many('account.account.template', 'parent', 'Children')
|
|
reconcile = fields.Boolean('Reconcile',
|
|
states={
|
|
'invisible': Eval('kind') == 'view',
|
|
}, depends=['kind'])
|
|
kind = fields.Selection([
|
|
('other', 'Other'),
|
|
('payable', 'Payable'),
|
|
('revenue', 'Revenue'),
|
|
('receivable', 'Receivable'),
|
|
('expense', 'Expense'),
|
|
('stock', 'Stock'),
|
|
('view', 'View'),
|
|
], 'Kind', required=True)
|
|
deferral = fields.Boolean('Deferral', states={
|
|
'invisible': Eval('kind') == 'view',
|
|
}, depends=['kind'])
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(AccountTemplate, cls).__setup__()
|
|
cls._order.insert(0, ('code', 'ASC'))
|
|
cls._order.insert(1, ('name', 'ASC'))
|
|
|
|
@classmethod
|
|
def validate(cls, templates):
|
|
super(AccountTemplate, cls).validate(templates)
|
|
cls.check_recursion(templates)
|
|
|
|
@staticmethod
|
|
def default_kind():
|
|
return 'view'
|
|
|
|
@staticmethod
|
|
def default_reconcile():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_deferral():
|
|
return True
|
|
|
|
def get_rec_name(self, name):
|
|
if self.code:
|
|
return self.code + ' - ' + self.name
|
|
else:
|
|
return self.name
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
return ['OR',
|
|
('code',) + tuple(clause[1:]),
|
|
(cls._rec_name,) + tuple(clause[1:]),
|
|
]
|
|
|
|
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.kind != self.kind:
|
|
res['kind'] = self.kind
|
|
if not account or account.reconcile != self.reconcile:
|
|
res['reconcile'] = self.reconcile
|
|
if not account or account.deferral != self.deferral:
|
|
res['deferral'] = self.deferral
|
|
if not account or account.template != self:
|
|
res['template'] = self.id
|
|
return res
|
|
|
|
def create_account(self, company_id, template2account=None,
|
|
template2type=None, parent_id=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.
|
|
Return the id of the account created
|
|
'''
|
|
pool = Pool()
|
|
Account = pool.get('account.account')
|
|
Lang = pool.get('ir.lang')
|
|
Config = pool.get('ir.configuration')
|
|
|
|
if template2account is None:
|
|
template2account = {}
|
|
|
|
if template2type is None:
|
|
template2type = {}
|
|
|
|
if self.id not in template2account:
|
|
vals = self._get_account_value()
|
|
vals['company'] = company_id
|
|
vals['parent'] = parent_id
|
|
vals['type'] = (template2type.get(self.type.id) if self.type
|
|
else None)
|
|
|
|
new_account, = Account.create([vals])
|
|
|
|
prev_lang = self._context.get('language') or Config.get_language()
|
|
prev_data = {}
|
|
for field_name, field in self._fields.iteritems():
|
|
if getattr(field, 'translate', False):
|
|
prev_data[field_name] = getattr(self, field_name)
|
|
for lang in Lang.get_translatable_languages():
|
|
if lang == prev_lang:
|
|
continue
|
|
with Transaction().set_context(language=lang):
|
|
template = self.__class__(self.id)
|
|
data = {}
|
|
for field_name, field in self._fields.iteritems():
|
|
if (getattr(field, 'translate', False)
|
|
and (getattr(template, field_name) !=
|
|
prev_data[field_name])):
|
|
data[field_name] = getattr(template, field_name)
|
|
if data:
|
|
Account.write([new_account], data)
|
|
template2account[self.id] = new_account.id
|
|
new_id = template2account[self.id]
|
|
|
|
new_childs = []
|
|
for child in self.childs:
|
|
new_childs.append(child.create_account(company_id,
|
|
template2account=template2account, template2type=template2type,
|
|
parent_id=new_id))
|
|
return new_id
|
|
|
|
def update_account_taxes(self, template2account, template2tax,
|
|
template_done=None):
|
|
'''
|
|
Update recursively account taxes 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 = []
|
|
|
|
if self.id not in template_done:
|
|
if self.taxes:
|
|
Account.write([Account(template2account[self.id])], {
|
|
'taxes': [
|
|
('add', template2tax[x.id]) for x in self.taxes],
|
|
})
|
|
template_done.append(self.id)
|
|
|
|
for child in self.childs:
|
|
child.update_account_taxes(template2account, template2tax,
|
|
template_done=template_done)
|
|
|
|
|
|
class Account(ModelSQL, ModelView):
|
|
'Account'
|
|
__name__ = 'account.account'
|
|
name = fields.Char('Name', size=None, required=True, translate=True,
|
|
select=True)
|
|
code = fields.Char('Code', size=None, select=True)
|
|
active = fields.Boolean('Active', select=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True,
|
|
ondelete="RESTRICT")
|
|
currency = fields.Function(fields.Many2One('currency.currency',
|
|
'Currency'), 'get_currency')
|
|
currency_digits = fields.Function(fields.Integer('Currency Digits'),
|
|
'get_currency_digits')
|
|
second_currency = fields.Many2One('currency.currency',
|
|
'Secondary Currency', help='Force all moves for this account \n'
|
|
'to have this secondary currency.', ondelete="RESTRICT")
|
|
type = fields.Many2One('account.account.type', 'Type', ondelete="RESTRICT",
|
|
states={
|
|
'invisible': Eval('kind') == 'view',
|
|
'required': Eval('kind') != 'view',
|
|
},
|
|
domain=[
|
|
('company', '=', Eval('company')),
|
|
], depends=['kind', 'company'])
|
|
parent = fields.Many2One('account.account', 'Parent', select=True,
|
|
left="left", right="right", ondelete="RESTRICT",
|
|
domain=[('company', '=', Eval('company'))],
|
|
depends=['company'])
|
|
left = fields.Integer('Left', required=True, select=True)
|
|
right = fields.Integer('Right', required=True, select=True)
|
|
childs = fields.One2Many('account.account', 'parent', 'Children')
|
|
balance = fields.Function(fields.Numeric('Balance',
|
|
digits=(16, Eval('currency_digits', 2)), 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')
|
|
reconcile = fields.Boolean('Reconcile',
|
|
help='Allow move lines of this account \nto be reconciled.',
|
|
states={
|
|
'invisible': Eval('kind') == 'view',
|
|
}, depends=['kind'])
|
|
note = fields.Text('Note')
|
|
kind = fields.Selection([
|
|
('other', 'Other'),
|
|
('payable', 'Payable'),
|
|
('revenue', 'Revenue'),
|
|
('receivable', 'Receivable'),
|
|
('expense', 'Expense'),
|
|
('stock', 'Stock'),
|
|
('view', 'View'),
|
|
], 'Kind', required=True)
|
|
deferral = fields.Boolean('Deferral', states={
|
|
'invisible': Eval('kind') == 'view',
|
|
}, depends=['kind'])
|
|
deferrals = fields.One2Many('account.account.deferral', 'account',
|
|
'Deferrals', readonly=True, states={
|
|
'invisible': Eval('kind') == 'view',
|
|
}, depends=['kind'])
|
|
template = fields.Many2One('account.account.template', 'Template')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Account, cls).__setup__()
|
|
cls._error_messages.update({
|
|
'delete_account_containing_move_lines': ('You can not delete '
|
|
'account "%s" because it has move lines.'),
|
|
})
|
|
cls._sql_error_messages.update({
|
|
'parent_fkey': ('You can not delete accounts that have '
|
|
'children.'),
|
|
})
|
|
cls._order.insert(0, ('code', 'ASC'))
|
|
cls._order.insert(1, ('name', 'ASC'))
|
|
|
|
@classmethod
|
|
def validate(cls, accounts):
|
|
super(Account, cls).validate(accounts)
|
|
cls.check_recursion(accounts)
|
|
|
|
@staticmethod
|
|
def default_left():
|
|
return 0
|
|
|
|
@staticmethod
|
|
def default_right():
|
|
return 0
|
|
|
|
@staticmethod
|
|
def default_active():
|
|
return True
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company') or None
|
|
|
|
@staticmethod
|
|
def default_reconcile():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_deferral():
|
|
return True
|
|
|
|
@staticmethod
|
|
def default_kind():
|
|
return 'view'
|
|
|
|
def get_currency(self, name):
|
|
return self.company.currency.id
|
|
|
|
def get_currency_digits(self, name):
|
|
return self.company.currency.digits
|
|
|
|
@classmethod
|
|
def get_balance(cls, accounts, name):
|
|
pool = Pool()
|
|
MoveLine = pool.get('account.move.line')
|
|
FiscalYear = pool.get('account.fiscalyear')
|
|
cursor = Transaction().cursor
|
|
in_max = cursor.IN_MAX
|
|
|
|
table_a = cls.__table__()
|
|
table_c = cls.__table__()
|
|
line = MoveLine.__table__()
|
|
ids = [a.id for a in accounts]
|
|
balances = dict((i, 0) for i in ids)
|
|
line_query, fiscalyear_ids = MoveLine.query_get(line)
|
|
for i in range(0, len(ids), in_max):
|
|
sub_ids = ids[i:i + in_max]
|
|
red_sql = reduce_ids(table_a.id, sub_ids)
|
|
cursor.execute(*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
|
|
).select(
|
|
table_a.id,
|
|
Sum(Coalesce(line.debit, 0) - Coalesce(line.credit, 0)),
|
|
where=red_sql & line_query & table_c.active,
|
|
group_by=table_a.id))
|
|
result = cursor.fetchall()
|
|
balances.update(dict(result))
|
|
|
|
# SQLite uses float for SUM
|
|
for account_id, balance in balances.iteritems():
|
|
if isinstance(balance, Decimal):
|
|
break
|
|
balances[account_id] = Decimal(str(balance))
|
|
|
|
for account in accounts:
|
|
balances[account.id] = account.company.currency.round(
|
|
balances[account.id])
|
|
|
|
fiscalyears = FiscalYear.browse(fiscalyear_ids)
|
|
func = lambda accounts, names: \
|
|
{names[0]: cls.get_balance(accounts, names[0])}
|
|
return cls._cumulate(fiscalyears, accounts, {name: balances},
|
|
func)[name]
|
|
|
|
@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')
|
|
cursor = Transaction().cursor
|
|
in_max = cursor.IN_MAX
|
|
|
|
result = {}
|
|
ids = [a.id for a in accounts]
|
|
for name in names:
|
|
if name not in ('credit', 'debit'):
|
|
raise Exception('Bad argument')
|
|
result[name] = dict((i, 0) for i in ids)
|
|
|
|
table = cls.__table__()
|
|
line = MoveLine.__table__()
|
|
line_query, fiscalyear_ids = MoveLine.query_get(line)
|
|
columns = [table.id]
|
|
for name in names:
|
|
columns.append(Sum(Coalesce(Column(line, name), 0)))
|
|
for i in range(0, len(ids), in_max):
|
|
sub_ids = ids[i:i + in_max]
|
|
red_sql = reduce_ids(table.id, sub_ids)
|
|
cursor.execute(*table.join(line, 'LEFT',
|
|
condition=line.account == table.id
|
|
).select(*columns,
|
|
where=red_sql & line_query,
|
|
group_by=table.id))
|
|
for row in cursor.fetchall():
|
|
account_id = row[0]
|
|
for i, name in enumerate(names, 1):
|
|
# SQLite uses float for SUM
|
|
if not isinstance(row[i], Decimal):
|
|
result[name][account_id] = Decimal(str(row[i]))
|
|
else:
|
|
result[name][account_id] = row[i]
|
|
for account in accounts:
|
|
for name in names:
|
|
result[name][account.id] = account.company.currency.round(
|
|
result[name][account.id])
|
|
|
|
if not Transaction().context.get('cumulate'):
|
|
return result
|
|
else:
|
|
fiscalyears = FiscalYear.browse(fiscalyear_ids)
|
|
return cls._cumulate(fiscalyears, accounts, result,
|
|
cls.get_credit_debit)
|
|
|
|
@classmethod
|
|
def _cumulate(cls, fiscalyears, accounts, values, func):
|
|
"""
|
|
Cumulate previous fiscalyear values into values
|
|
func is the method to compute values
|
|
"""
|
|
pool = Pool()
|
|
FiscalYear = pool.get('account.fiscalyear')
|
|
Deferral = pool.get('account.account.deferral')
|
|
in_max = Transaction().cursor.IN_MAX
|
|
names = values.keys()
|
|
|
|
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 == 'close':
|
|
id2deferral = {}
|
|
ids = [a.id for a in accounts]
|
|
for i in range(0, len(ids), in_max):
|
|
sub_ids = ids[i:i + in_max]
|
|
deferrals = Deferral.search([
|
|
('fiscalyear', '=', fiscalyear.id),
|
|
('account', 'in', sub_ids),
|
|
])
|
|
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):
|
|
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
|
|
|
|
def get_rec_name(self, name):
|
|
if self.code:
|
|
return self.code + ' - ' + self.name
|
|
else:
|
|
return self.name
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
return ['OR',
|
|
('code',) + tuple(clause[1:]),
|
|
(cls._rec_name,) + tuple(clause[1:]),
|
|
]
|
|
|
|
@classmethod
|
|
def copy(cls, accounts, default=None):
|
|
if default is None:
|
|
default = {}
|
|
default['left'] = 0
|
|
default['right'] = 0
|
|
default.setdefault('deferrals', [])
|
|
new_accounts = super(Account, cls).copy(accounts, default=default)
|
|
cls._rebuild_tree('parent', None, 0)
|
|
return new_accounts
|
|
|
|
@classmethod
|
|
def write(cls, *args):
|
|
pool = Pool()
|
|
MoveLine = pool.get('account.move.line')
|
|
actions = iter(args)
|
|
args = []
|
|
for accounts, values in zip(actions, actions):
|
|
if not values.get('active', True):
|
|
childs = cls.search([
|
|
('parent', 'child_of', [a.id for a in accounts]),
|
|
])
|
|
if MoveLine.search([
|
|
('account', 'in', [a.id for a in childs]),
|
|
]):
|
|
values = values.copy()
|
|
del values['active']
|
|
args.extend((accounts, values))
|
|
super(Account, cls).write(*args)
|
|
|
|
@classmethod
|
|
def delete(cls, accounts):
|
|
MoveLine = Pool().get('account.move.line')
|
|
childs = cls.search([
|
|
('parent', 'child_of', [a.id for a in accounts]),
|
|
])
|
|
lines = MoveLine.search([
|
|
('account', 'in', [a.id for a in childs]),
|
|
])
|
|
if lines:
|
|
cls.raise_user_error('delete_account_containing_move_lines', (
|
|
lines[0].account.rec_name,))
|
|
super(Account, cls).delete(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.
|
|
'''
|
|
pool = Pool()
|
|
Lang = pool.get('ir.lang')
|
|
Config = pool.get('ir.configuration')
|
|
|
|
if template2account is None:
|
|
template2account = {}
|
|
|
|
if template2type is None:
|
|
template2type = {}
|
|
|
|
if self.template:
|
|
vals = self.template._get_account_value(account=self)
|
|
current_type = self.type.id if self.type else None
|
|
template_type = (template2type.get(self.template.type.id)
|
|
if self.template.type else None)
|
|
if current_type != template2type:
|
|
vals['type'] = template_type
|
|
if vals:
|
|
self.write([self], vals)
|
|
|
|
prev_lang = self._context.get('language') or Config.get_language()
|
|
prev_data = {}
|
|
for field_name, field in self.template._fields.iteritems():
|
|
if getattr(field, 'translate', False):
|
|
prev_data[field_name] = getattr(self.template, field_name)
|
|
for lang in Lang.get_translatable_languages():
|
|
if lang == prev_lang:
|
|
continue
|
|
with Transaction().set_context(language=lang):
|
|
account = self.__class__(self.id)
|
|
data = {}
|
|
for field_name, field in (
|
|
account.template._fields.iteritems()):
|
|
if (getattr(field, 'translate', False)
|
|
and (getattr(account.template, field_name) !=
|
|
prev_data[field_name])):
|
|
data[field_name] = getattr(account.template,
|
|
field_name)
|
|
if data:
|
|
self.write([account], data)
|
|
template2account[self.template.id] = self.id
|
|
|
|
for child in self.childs:
|
|
child.update_account(template2account=template2account,
|
|
template2type=template2type)
|
|
|
|
def update_account_taxes(self, template2account, template2tax):
|
|
'''
|
|
Update recursively account taxes 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 = {}
|
|
|
|
if self.template:
|
|
if self.template.taxes:
|
|
tax_ids = [template2tax[x.id] for x in self.template.taxes
|
|
if x.id in template2tax]
|
|
old_tax_ids = [x.id for x in self.taxes]
|
|
for tax_id in tax_ids:
|
|
if tax_id not in old_tax_ids:
|
|
self.write([self], {
|
|
'taxes': [
|
|
('add', template2tax[x.id])
|
|
for x in self.template.taxes
|
|
if x.id in template2tax],
|
|
})
|
|
break
|
|
|
|
for child in self.childs:
|
|
child.update_account_taxes(template2account, template2tax)
|
|
|
|
|
|
class AccountDeferral(ModelSQL, ModelView):
|
|
'''
|
|
Account Deferral
|
|
|
|
It is used to deferral the debit/credit of account by fiscal year.
|
|
'''
|
|
__name__ = 'account.account.deferral'
|
|
account = fields.Many2One('account.account', 'Account', required=True,
|
|
select=True)
|
|
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
|
|
required=True, select=True)
|
|
debit = fields.Numeric('Debit', digits=(16, Eval('currency_digits', 2)),
|
|
required=True, depends=['currency_digits'])
|
|
credit = fields.Numeric('Credit', digits=(16, Eval('currency_digits', 2)),
|
|
required=True, depends=['currency_digits'])
|
|
balance = fields.Function(fields.Numeric('Balance',
|
|
digits=(16, Eval('currency_digits', 2)),
|
|
depends=['currency_digits']), 'get_balance')
|
|
currency_digits = fields.Function(fields.Integer('Currency Digits'),
|
|
'get_currency_digits')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(AccountDeferral, cls).__setup__()
|
|
cls._sql_constraints += [
|
|
('deferral_uniq', 'UNIQUE(account, fiscalyear)',
|
|
'Deferral must be unique by account and fiscal year'),
|
|
]
|
|
cls._error_messages.update({
|
|
'write_deferral': 'You can not modify Account Deferral records',
|
|
})
|
|
|
|
def get_balance(self, name):
|
|
return self.debit - self.credit
|
|
|
|
def get_currency_digits(self, name):
|
|
return self.account.currency_digits
|
|
|
|
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):
|
|
return ['OR',
|
|
('account.rec_name',) + tuple(clause[1:]),
|
|
('fiscalyear.rec_name',) + tuple(clause[1:]),
|
|
]
|
|
|
|
@classmethod
|
|
def write(cls, deferrals, values, *args):
|
|
cls.raise_user_error('write_deferral')
|
|
|
|
|
|
class OpenChartAccountStart(ModelView):
|
|
'Open Chart of Accounts'
|
|
__name__ = 'account.open_chart.start'
|
|
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
|
|
help='Leave empty for all open fiscal year')
|
|
posted = fields.Boolean('Posted Moves', help='Show posted moves only')
|
|
|
|
@staticmethod
|
|
def default_posted():
|
|
return False
|
|
|
|
|
|
class OpenChartAccount(Wizard):
|
|
'Open Chart of Accounts'
|
|
__name__ = 'account.open_chart'
|
|
start = StateView('account.open_chart.start',
|
|
'account.open_chart_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Open', 'open_', 'tryton-ok', default=True),
|
|
])
|
|
open_ = StateAction('account.act_account_tree2')
|
|
|
|
def do_open_(self, action):
|
|
action['pyson_context'] = PYSONEncoder().encode({
|
|
'fiscalyear': (self.start.fiscalyear.id
|
|
if self.start.fiscalyear else None),
|
|
'posted': self.start.posted,
|
|
})
|
|
if self.start.fiscalyear:
|
|
action['name'] += ' - %s' % self.start.fiscalyear.rec_name
|
|
if self.start.posted:
|
|
action['name'] += '*'
|
|
return action, {}
|
|
|
|
def transition_open_(self):
|
|
return 'end'
|
|
|
|
|
|
class PrintGeneralLedgerStart(ModelView):
|
|
'Print General Ledger'
|
|
__name__ = 'account.print_general_ledger.start'
|
|
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
|
|
required=True)
|
|
start_period = fields.Many2One('account.period', 'Start Period',
|
|
domain=[
|
|
('fiscalyear', '=', Eval('fiscalyear')),
|
|
('start_date', '<=', (Eval('end_period'), 'start_date')),
|
|
], depends=['fiscalyear', 'end_period'])
|
|
end_period = fields.Many2One('account.period', 'End Period',
|
|
domain=[
|
|
('fiscalyear', '=', Eval('fiscalyear')),
|
|
('start_date', '>=', (Eval('start_period'), 'start_date'))
|
|
],
|
|
depends=['fiscalyear', 'start_period'])
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
posted = fields.Boolean('Posted Move', help='Show only posted move')
|
|
empty_account = fields.Boolean('Empty Account',
|
|
help='With account without move')
|
|
|
|
@staticmethod
|
|
def default_fiscalyear():
|
|
FiscalYear = Pool().get('account.fiscalyear')
|
|
return FiscalYear.find(
|
|
Transaction().context.get('company'), exception=False)
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@staticmethod
|
|
def default_posted():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_empty_account():
|
|
return False
|
|
|
|
@fields.depends('fiscalyear')
|
|
def on_change_fiscalyear(self):
|
|
return {
|
|
'start_period': None,
|
|
'end_period': None,
|
|
}
|
|
|
|
|
|
class PrintGeneralLedger(Wizard):
|
|
'Print General Ledger'
|
|
__name__ = 'account.print_general_ledger'
|
|
start = StateView('account.print_general_ledger.start',
|
|
'account.print_general_ledger_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Print', 'print_', 'tryton-print', default=True),
|
|
])
|
|
print_ = StateAction('account.report_general_ledger')
|
|
|
|
def do_print_(self, action):
|
|
if self.start.start_period:
|
|
start_period = self.start.start_period.id
|
|
else:
|
|
start_period = None
|
|
if self.start.end_period:
|
|
end_period = self.start.end_period.id
|
|
else:
|
|
end_period = None
|
|
data = {
|
|
'company': self.start.company.id,
|
|
'fiscalyear': self.start.fiscalyear.id,
|
|
'start_period': start_period,
|
|
'end_period': end_period,
|
|
'posted': self.start.posted,
|
|
'empty_account': self.start.empty_account,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class GeneralLedger(Report):
|
|
__name__ = 'account.general_ledger'
|
|
|
|
@classmethod
|
|
def parse(cls, report, objects, data, localcontext):
|
|
pool = Pool()
|
|
Account = pool.get('account.account')
|
|
Period = pool.get('account.period')
|
|
Company = pool.get('company.company')
|
|
|
|
company = Company(data['company'])
|
|
|
|
accounts = Account.search([
|
|
('company', '=', data['company']),
|
|
('kind', '!=', 'view'),
|
|
], order=[('code', 'ASC'), ('id', 'ASC')])
|
|
|
|
start_period_ids = [0]
|
|
start_periods = []
|
|
if data['start_period']:
|
|
start_period = Period(data['start_period'])
|
|
start_periods = Period.search([
|
|
('fiscalyear', '=', data['fiscalyear']),
|
|
('end_date', '<=', start_period.start_date),
|
|
])
|
|
start_period_ids = [p.id for p in start_periods]
|
|
|
|
with Transaction().set_context(
|
|
fiscalyear=data['fiscalyear'],
|
|
periods=start_period_ids,
|
|
posted=data['posted']):
|
|
start_accounts = Account.browse(accounts)
|
|
id2start_account = {}
|
|
for account in start_accounts:
|
|
id2start_account[account.id] = account
|
|
|
|
end_period_ids = []
|
|
if data['end_period']:
|
|
end_period = Period(data['end_period'])
|
|
end_periods = Period.search([
|
|
('fiscalyear', '=', data['fiscalyear']),
|
|
('end_date', '<=', end_period.start_date),
|
|
])
|
|
if end_period not in end_periods:
|
|
end_periods.append(end_period)
|
|
else:
|
|
end_periods = Period.search([
|
|
('fiscalyear', '=', data['fiscalyear']),
|
|
])
|
|
end_period_ids = [p.id for p in end_periods]
|
|
|
|
with Transaction().set_context(
|
|
fiscalyear=data['fiscalyear'],
|
|
periods=end_period_ids,
|
|
posted=data['posted']):
|
|
end_accounts = Account.browse(accounts)
|
|
id2end_account = {}
|
|
for account in end_accounts:
|
|
id2end_account[account.id] = account
|
|
|
|
periods = end_periods
|
|
periods.sort(lambda x, y: cmp(x.start_date, y.start_date))
|
|
localcontext['start_period'] = periods[0]
|
|
periods.sort(lambda x, y: cmp(x.end_date, y.end_date))
|
|
localcontext['end_period'] = periods[-1]
|
|
|
|
if not data['empty_account']:
|
|
account2lines = dict(cls.get_lines(accounts,
|
|
end_periods, data['posted']))
|
|
accounts = Account.browse([a.id for a in accounts
|
|
if a in account2lines])
|
|
|
|
account_id2lines = cls.lines(accounts,
|
|
list(set(end_periods).difference(set(start_periods))),
|
|
data['posted'])
|
|
|
|
localcontext['accounts'] = accounts
|
|
localcontext['id2start_account'] = id2start_account
|
|
localcontext['id2end_account'] = id2end_account
|
|
localcontext['digits'] = company.currency.digits
|
|
localcontext['lines'] = lambda account_id: account_id2lines[account_id]
|
|
localcontext['company'] = company
|
|
|
|
return super(GeneralLedger, cls).parse(report, objects, data,
|
|
localcontext)
|
|
|
|
@classmethod
|
|
def get_lines(cls, accounts, periods, posted):
|
|
MoveLine = Pool().get('account.move.line')
|
|
clause = [
|
|
('account', 'in', [a.id for a in accounts]),
|
|
('period', 'in', [p.id for p in periods]),
|
|
('state', '!=', 'draft'),
|
|
]
|
|
if posted:
|
|
clause.append(('move.state', '=', 'posted'))
|
|
lines = MoveLine.search(clause,
|
|
order=[
|
|
('account', 'ASC'),
|
|
('date', 'ASC'),
|
|
])
|
|
return groupby(lines, operator.attrgetter('account'))
|
|
|
|
@classmethod
|
|
def lines(cls, accounts, periods, posted):
|
|
Move = Pool().get('account.move')
|
|
res = dict((a.id, []) for a in accounts)
|
|
account2lines = cls.get_lines(accounts, periods, posted)
|
|
|
|
state_selections = dict(Move.fields_get(
|
|
fields_names=['state'])['state']['selection'])
|
|
|
|
for account, lines in account2lines:
|
|
balance = Decimal('0.0')
|
|
for line in lines:
|
|
balance += line.debit - line.credit
|
|
res[account.id].append({
|
|
'date': line.date,
|
|
'move': line.move.rec_name,
|
|
'debit': line.debit,
|
|
'credit': line.credit,
|
|
'balance': balance,
|
|
'description': '\n'.join(
|
|
(line.move.description or '',
|
|
line.description or '')).strip(),
|
|
'origin': (line.move.origin.rec_name
|
|
if line.move.origin else ''),
|
|
'state': state_selections.get(line.move.state,
|
|
line.move.state),
|
|
})
|
|
return res
|
|
|
|
|
|
class PrintTrialBalanceStart(ModelView):
|
|
'Print Trial Balance'
|
|
__name__ = 'account.print_trial_balance.start'
|
|
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
|
|
required=True)
|
|
start_period = fields.Many2One('account.period', 'Start Period',
|
|
domain=[
|
|
('fiscalyear', '=', Eval('fiscalyear')),
|
|
('start_date', '<=', (Eval('end_period'), 'start_date'))
|
|
],
|
|
depends=['end_period', 'fiscalyear'])
|
|
end_period = fields.Many2One('account.period', 'End Period',
|
|
domain=[
|
|
('fiscalyear', '=', Eval('fiscalyear')),
|
|
('start_date', '>=', (Eval('start_period'), 'start_date'))
|
|
],
|
|
depends=['start_period', 'fiscalyear'])
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
posted = fields.Boolean('Posted Move', help='Show only posted move')
|
|
empty_account = fields.Boolean('Empty Account',
|
|
help='With account without move')
|
|
|
|
@staticmethod
|
|
def default_fiscalyear():
|
|
FiscalYear = Pool().get('account.fiscalyear')
|
|
return FiscalYear.find(
|
|
Transaction().context.get('company'), exception=False)
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@staticmethod
|
|
def default_posted():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_empty_account():
|
|
return False
|
|
|
|
@fields.depends('fiscalyear')
|
|
def on_change_fiscalyear(self):
|
|
return {
|
|
'start_period': None,
|
|
'end_period': None,
|
|
}
|
|
|
|
|
|
class PrintTrialBalance(Wizard):
|
|
'Print Trial Balance'
|
|
__name__ = 'account.print_trial_balance'
|
|
start = StateView('account.print_trial_balance.start',
|
|
'account.print_trial_balance_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Print', 'print_', 'tryton-print', default=True),
|
|
])
|
|
print_ = StateAction('account.report_trial_balance')
|
|
|
|
def do_print_(self, action):
|
|
if self.start.start_period:
|
|
start_period = self.start.start_period.id
|
|
else:
|
|
start_period = None
|
|
if self.start.end_period:
|
|
end_period = self.start.end_period.id
|
|
else:
|
|
end_period = None
|
|
data = {
|
|
'company': self.start.company.id,
|
|
'fiscalyear': self.start.fiscalyear.id,
|
|
'start_period': start_period,
|
|
'end_period': end_period,
|
|
'posted': self.start.posted,
|
|
'empty_account': self.start.empty_account,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class TrialBalance(Report):
|
|
__name__ = 'account.trial_balance'
|
|
|
|
@classmethod
|
|
def parse(cls, report, objects, data, localcontext):
|
|
pool = Pool()
|
|
Account = pool.get('account.account')
|
|
Period = pool.get('account.period')
|
|
Company = pool.get('company.company')
|
|
|
|
company = Company(data['company'])
|
|
|
|
accounts = Account.search([
|
|
('company', '=', data['company']),
|
|
('kind', '!=', 'view'),
|
|
])
|
|
|
|
start_periods = []
|
|
if data['start_period']:
|
|
start_period = Period(data['start_period'])
|
|
start_periods = Period.search([
|
|
('fiscalyear', '=', data['fiscalyear']),
|
|
('end_date', '<=', start_period.start_date),
|
|
])
|
|
|
|
if data['end_period']:
|
|
end_period = Period(data['end_period'])
|
|
end_periods = Period.search([
|
|
('fiscalyear', '=', data['fiscalyear']),
|
|
('end_date', '<=', end_period.start_date),
|
|
])
|
|
end_periods = list(set(end_periods).difference(
|
|
set(start_periods)))
|
|
if end_period not in end_periods:
|
|
end_periods.append(end_period)
|
|
else:
|
|
end_periods = Period.search([
|
|
('fiscalyear', '=', data['fiscalyear']),
|
|
])
|
|
end_periods = list(set(end_periods).difference(
|
|
set(start_periods)))
|
|
|
|
start_period_ids = [p.id for p in start_periods] or [0]
|
|
end_period_ids = [p.id for p in end_periods]
|
|
|
|
with Transaction().set_context(
|
|
fiscalyear=data['fiscalyear'],
|
|
periods=start_period_ids,
|
|
posted=data['posted']):
|
|
start_accounts = Account.browse(accounts)
|
|
|
|
with Transaction().set_context(
|
|
fiscalyear=None,
|
|
periods=end_period_ids,
|
|
posted=data['posted']):
|
|
in_accounts = Account.browse(accounts)
|
|
|
|
with Transaction().set_context(
|
|
fiscalyear=data['fiscalyear'],
|
|
periods=start_period_ids + end_period_ids,
|
|
posted=data['posted']):
|
|
end_accounts = Account.browse(accounts)
|
|
|
|
to_remove = set()
|
|
if not data['empty_account']:
|
|
for account in in_accounts:
|
|
if account.debit == Decimal('0.0') \
|
|
and account.credit == Decimal('0.0'):
|
|
to_remove.add(account)
|
|
|
|
accounts = []
|
|
for start_account, in_account, end_account in izip(
|
|
start_accounts, in_accounts, end_accounts):
|
|
if in_account in to_remove:
|
|
continue
|
|
accounts.append({
|
|
'code': start_account.code,
|
|
'name': start_account.name,
|
|
'start_balance': start_account.balance,
|
|
'debit': in_account.debit,
|
|
'credit': in_account.credit,
|
|
'end_balance': end_account.balance,
|
|
})
|
|
|
|
periods = end_periods
|
|
|
|
localcontext['accounts'] = accounts
|
|
periods.sort(key=operator.attrgetter('start_date'))
|
|
localcontext['start_period'] = periods[0]
|
|
periods.sort(key=operator.attrgetter('end_date'))
|
|
localcontext['end_period'] = periods[-1]
|
|
localcontext['company'] = company
|
|
localcontext['digits'] = company.currency.digits
|
|
localcontext['sum'] = lambda accounts, field: cls.sum(accounts, field)
|
|
|
|
return super(TrialBalance, cls).parse(report, objects, data,
|
|
localcontext)
|
|
|
|
@classmethod
|
|
def sum(cls, accounts, field):
|
|
amount = Decimal('0.0')
|
|
for account in accounts:
|
|
amount += account[field]
|
|
return amount
|
|
|
|
|
|
class OpenBalanceSheetStart(ModelView):
|
|
'Open Balance Sheet'
|
|
__name__ = 'account.open_balance_sheet.start'
|
|
date = fields.Date('Date', required=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
posted = fields.Boolean('Posted Move', help='Show only posted move')
|
|
|
|
@staticmethod
|
|
def default_date():
|
|
Date_ = Pool().get('ir.date')
|
|
return Date_.today()
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@staticmethod
|
|
def default_posted():
|
|
return False
|
|
|
|
|
|
class OpenBalanceSheet(Wizard):
|
|
'Open Balance Sheet'
|
|
__name__ = 'account.open_balance_sheet'
|
|
start = StateView('account.open_balance_sheet.start',
|
|
'account.open_balance_sheet_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Open', 'open_', 'tryton-ok', default=True),
|
|
])
|
|
open_ = StateAction('account.act_account_balance_sheet_tree')
|
|
|
|
def do_open_(self, action):
|
|
pool = Pool()
|
|
Lang = pool.get('ir.lang')
|
|
|
|
company = self.start.company
|
|
lang, = Lang.search([
|
|
('code', '=', Transaction().language),
|
|
])
|
|
|
|
date = Lang.strftime(self.start.date, lang.code, lang.date)
|
|
|
|
action['pyson_context'] = PYSONEncoder().encode({
|
|
'date': Date(self.start.date.year,
|
|
self.start.date.month,
|
|
self.start.date.day),
|
|
'posted': self.start.posted,
|
|
'company': company.id,
|
|
'cumulate': True,
|
|
})
|
|
action['name'] += ' %s - %s' % (date, company.rec_name)
|
|
return action, {}
|
|
|
|
def transition_open_(self):
|
|
return 'end'
|
|
|
|
|
|
class OpenIncomeStatementStart(ModelView):
|
|
'Open Income Statement'
|
|
__name__ = 'account.open_income_statement.start'
|
|
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
|
|
required=True)
|
|
start_period = fields.Many2One('account.period', 'Start Period',
|
|
domain=[
|
|
('fiscalyear', '=', Eval('fiscalyear')),
|
|
('start_date', '<=', (Eval('end_period'), 'start_date'))
|
|
],
|
|
depends=['end_period', 'fiscalyear'])
|
|
end_period = fields.Many2One('account.period', 'End Period',
|
|
domain=[
|
|
('fiscalyear', '=', Eval('fiscalyear')),
|
|
('start_date', '>=', (Eval('start_period'), 'start_date')),
|
|
],
|
|
depends=['start_period', 'fiscalyear'])
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
posted = fields.Boolean('Posted Move', help='Show only posted move')
|
|
|
|
@staticmethod
|
|
def default_fiscalyear():
|
|
FiscalYear = Pool().get('account.fiscalyear')
|
|
return FiscalYear.find(
|
|
Transaction().context.get('company'), exception=False)
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@staticmethod
|
|
def default_posted():
|
|
return False
|
|
|
|
@fields.depends('fiscalyear')
|
|
def on_change_fiscalyear(self):
|
|
return {
|
|
'start_period': None,
|
|
'end_period': None,
|
|
}
|
|
|
|
|
|
class OpenIncomeStatement(Wizard):
|
|
'Open Income Statement'
|
|
__name__ = 'account.open_income_statement'
|
|
start = StateView('account.open_income_statement.start',
|
|
'account.open_income_statement_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Open', 'open_', 'tryton-ok', default=True),
|
|
])
|
|
open_ = StateAction('account.act_account_income_statement_tree')
|
|
|
|
def do_open_(self, action):
|
|
pool = Pool()
|
|
Period = pool.get('account.period')
|
|
|
|
start_periods = []
|
|
if self.start.start_period:
|
|
start_periods = Period.search([
|
|
('fiscalyear', '=', self.start.fiscalyear.id),
|
|
('end_date', '<=', self.start.start_period.start_date),
|
|
])
|
|
|
|
end_periods = []
|
|
if self.start.end_period:
|
|
end_periods = Period.search([
|
|
('fiscalyear', '=', self.start.fiscalyear.id),
|
|
('end_date', '<=', self.start.end_period.start_date),
|
|
])
|
|
end_periods = list(set(end_periods).difference(
|
|
set(start_periods)))
|
|
if self.start.end_period not in end_periods:
|
|
end_periods.append(self.start.end_period)
|
|
else:
|
|
end_periods = Period.search([
|
|
('fiscalyear', '=', self.start.fiscalyear.id),
|
|
])
|
|
end_periods = list(set(end_periods).difference(
|
|
set(start_periods)))
|
|
|
|
action['pyson_context'] = PYSONEncoder().encode({
|
|
'periods': [p.id for p in end_periods],
|
|
'posted': self.start.posted,
|
|
'company': self.start.company.id,
|
|
})
|
|
return action, {}
|
|
|
|
|
|
class CreateChartStart(ModelView):
|
|
'Create Chart'
|
|
__name__ = 'account.create_chart.start'
|
|
|
|
|
|
class CreateChartAccount(ModelView):
|
|
'Create Chart'
|
|
__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):
|
|
'Create Chart'
|
|
__name__ = 'account.create_chart.properties'
|
|
company = fields.Many2One('company.company', 'Company')
|
|
account_receivable = fields.Many2One('account.account',
|
|
'Default Receivable Account',
|
|
domain=[
|
|
('kind', '=', 'receivable'),
|
|
('company', '=', Eval('company')),
|
|
],
|
|
depends=['company'])
|
|
account_payable = fields.Many2One('account.account',
|
|
'Default Payable Account',
|
|
domain=[
|
|
('kind', '=', 'payable'),
|
|
('company', '=', Eval('company')),
|
|
],
|
|
depends=['company'])
|
|
|
|
|
|
class CreateChart(Wizard):
|
|
'Create Chart'
|
|
__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')
|
|
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')
|
|
|
|
with Transaction().set_context(language=Config.get_language(),
|
|
company=self.account.company.id):
|
|
account_template = self.account.account_template
|
|
|
|
# Create account types
|
|
template2type = {}
|
|
account_template.type.create_type(self.account.company.id,
|
|
template2type=template2type)
|
|
|
|
# Create accounts
|
|
template2account = {}
|
|
account_template.create_account(self.account.company.id,
|
|
template2account=template2account, template2type=template2type)
|
|
|
|
# Create tax codes
|
|
template2tax_code = {}
|
|
tax_code_templates = TaxCodeTemplate.search([
|
|
('account', '=', account_template.id),
|
|
('parent', '=', None),
|
|
])
|
|
for tax_code_template in tax_code_templates:
|
|
tax_code_template.create_tax_code(self.account.company.id,
|
|
template2tax_code=template2tax_code)
|
|
|
|
# Create taxes
|
|
template2tax = {}
|
|
tax_templates = TaxTemplate.search([
|
|
('account', '=', account_template.id),
|
|
('parent', '=', None),
|
|
])
|
|
for tax_template in tax_templates:
|
|
tax_template.create_tax(self.account.company.id,
|
|
template2tax_code=template2tax_code,
|
|
template2account=template2account,
|
|
template2tax=template2tax)
|
|
|
|
# Update taxes on accounts
|
|
account_template.update_account_taxes(template2account,
|
|
template2tax)
|
|
|
|
# Create tax rules
|
|
template2rule = {}
|
|
tax_rule_templates = TaxRuleTemplate.search([
|
|
('account', '=', account_template.id),
|
|
])
|
|
for tax_rule_template in tax_rule_templates:
|
|
tax_rule_template.create_rule(self.account.company.id,
|
|
template2rule=template2rule)
|
|
|
|
# Create tax rule lines
|
|
template2rule_line = {}
|
|
tax_rule_line_templates = TaxRuleLineTemplate.search([
|
|
('rule.account', '=', account_template.id),
|
|
])
|
|
for tax_rule_line_template in tax_rule_line_templates:
|
|
tax_rule_line_template.create_rule_line(template2tax,
|
|
template2rule, template2rule_line=template2rule_line)
|
|
return 'properties'
|
|
|
|
def default_properties(self, fields):
|
|
return {
|
|
'company': self.account.company.id,
|
|
}
|
|
|
|
def transition_create_properties(self):
|
|
pool = Pool()
|
|
Property = pool.get('ir.property')
|
|
ModelField = pool.get('ir.model.field')
|
|
|
|
with Transaction().set_context(company=self.properties.company.id):
|
|
account_receivable_field, = ModelField.search([
|
|
('model.model', '=', 'party.party'),
|
|
('name', '=', 'account_receivable'),
|
|
], limit=1)
|
|
properties = Property.search([
|
|
('field', '=', account_receivable_field.id),
|
|
('res', '=', None),
|
|
('company', '=', self.properties.company.id),
|
|
])
|
|
with Transaction().set_user(0):
|
|
Property.delete(properties)
|
|
if self.properties.account_receivable:
|
|
Property.create([{
|
|
'field': account_receivable_field.id,
|
|
'value': str(
|
|
self.properties.account_receivable),
|
|
'company': self.properties.company.id,
|
|
}])
|
|
|
|
account_payable_field, = ModelField.search([
|
|
('model.model', '=', 'party.party'),
|
|
('name', '=', 'account_payable'),
|
|
], limit=1)
|
|
properties = Property.search([
|
|
('field', '=', account_payable_field.id),
|
|
('res', '=', None),
|
|
('company', '=', self.properties.company.id),
|
|
])
|
|
with Transaction().set_user(0):
|
|
Property.delete(properties)
|
|
if self.properties.account_payable:
|
|
Property.create([{
|
|
'field': account_payable_field.id,
|
|
'value': str(self.properties.account_payable),
|
|
'company': self.properties.company.id,
|
|
}])
|
|
return 'end'
|
|
|
|
|
|
class UpdateChartStart(ModelView):
|
|
'Update Chart'
|
|
__name__ = 'account.update_chart.start'
|
|
account = fields.Many2One('account.account', 'Root Account',
|
|
required=True, domain=[('parent', '=', None)])
|
|
|
|
|
|
class UpdateChartSucceed(ModelView):
|
|
'Update Chart'
|
|
__name__ = 'account.update_chart.succeed'
|
|
|
|
|
|
class UpdateChart(Wizard):
|
|
'Update Chart'
|
|
__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),
|
|
])
|
|
|
|
def transition_update(self):
|
|
pool = Pool()
|
|
TaxCode = pool.get('account.tax.code')
|
|
TaxCodeTemplate = pool.get('account.tax.code.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')
|
|
|
|
account = self.start.account
|
|
|
|
# Update account types
|
|
template2type = {}
|
|
account.type.update_type(template2type=template2type)
|
|
# Create missing account types
|
|
if account.type.template:
|
|
account.type.template.create_type(account.company.id,
|
|
template2type=template2type)
|
|
|
|
# Update accounts
|
|
template2account = {}
|
|
account.update_account(template2account=template2account,
|
|
template2type=template2type)
|
|
# Create missing accounts
|
|
if account.template:
|
|
account.template.create_account(account.company.id,
|
|
template2account=template2account, template2type=template2type)
|
|
|
|
# Update tax codes
|
|
template2tax_code = {}
|
|
tax_codes = TaxCode.search([
|
|
('company', '=', account.company.id),
|
|
('parent', '=', None),
|
|
])
|
|
for tax_code in tax_codes:
|
|
tax_code.update_tax_code(template2tax_code=template2tax_code)
|
|
# Create missing tax codes
|
|
if account.template:
|
|
tax_code_templates = TaxCodeTemplate.search([
|
|
('account', '=', account.template.id),
|
|
('parent', '=', None),
|
|
])
|
|
for tax_code_template in tax_code_templates:
|
|
tax_code_template.create_tax_code(account.company.id,
|
|
template2tax_code=template2tax_code)
|
|
|
|
# Update taxes
|
|
template2tax = {}
|
|
taxes = Tax.search([
|
|
('company', '=', account.company.id),
|
|
('parent', '=', None),
|
|
])
|
|
for tax in taxes:
|
|
tax.update_tax(template2tax_code=template2tax_code,
|
|
template2account=template2account,
|
|
template2tax=template2tax)
|
|
# Create missing taxes
|
|
if account.template:
|
|
tax_templates = TaxTemplate.search([
|
|
('account', '=', account.template.id),
|
|
('parent', '=', None),
|
|
])
|
|
for tax_template in tax_templates:
|
|
tax_template.create_tax(account.company.id,
|
|
template2tax_code=template2tax_code,
|
|
template2account=template2account,
|
|
template2tax=template2tax)
|
|
|
|
# Update taxes on accounts
|
|
account.update_account_taxes(template2account, template2tax)
|
|
|
|
# Update tax rules
|
|
template2rule = {}
|
|
tax_rules = TaxRule.search([
|
|
('company', '=', account.company.id),
|
|
])
|
|
for tax_rule in tax_rules:
|
|
tax_rule.update_rule(template2rule=template2rule)
|
|
# Create missing tax rules
|
|
if account.template:
|
|
tax_rule_templates = TaxRuleTemplate.search([
|
|
('account', '=', account.template.id),
|
|
])
|
|
for tax_rule_template in tax_rule_templates:
|
|
tax_rule_template.create_rule(account.company.id,
|
|
template2rule=template2rule)
|
|
|
|
# Update tax rule lines
|
|
template2rule_line = {}
|
|
tax_rule_lines = TaxRuleLine.search([
|
|
('rule.company', '=', account.company.id),
|
|
])
|
|
for tax_rule_line in tax_rule_lines:
|
|
tax_rule_line.update_rule_line(template2tax, template2rule,
|
|
template2rule_line=template2rule_line)
|
|
# Create missing tax rule lines
|
|
if account.template:
|
|
tax_rule_line_templates = TaxRuleLineTemplate.search([
|
|
('rule.account', '=', account.template.id),
|
|
])
|
|
for tax_rule_line_template in tax_rule_line_templates:
|
|
tax_rule_line_template.create_rule_line(template2tax,
|
|
template2rule, template2rule_line=template2rule_line)
|
|
return 'succeed'
|
|
|
|
|
|
class OpenThirdPartyBalanceStart(ModelView):
|
|
'Open Third Party Balance'
|
|
__name__ = 'account.open_third_party_balance.start'
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
|
|
required=True)
|
|
posted = fields.Boolean('Posted Move', help='Show only posted move')
|
|
|
|
@staticmethod
|
|
def default_fiscalyear():
|
|
Fiscalyear = Pool().get('account.fiscalyear')
|
|
return Fiscalyear.find(
|
|
Transaction().context.get('company'), exception=False)
|
|
|
|
@staticmethod
|
|
def default_posted():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
|
|
class OpenThirdPartyBalance(Wizard):
|
|
'Open Third Party Balance'
|
|
__name__ = 'account.open_third_party_balance'
|
|
start = StateView('account.open_third_party_balance.start',
|
|
'account.open_balance_sheet_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Print', 'print_', 'tryton-print', default=True),
|
|
])
|
|
print_ = StateAction('account.report_third_party_balance')
|
|
|
|
def do_print_(self, action):
|
|
data = {
|
|
'company': self.start.company.id,
|
|
'fiscalyear': self.start.fiscalyear.id,
|
|
'posted': self.start.posted,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class ThirdPartyBalance(Report):
|
|
__name__ = 'account.third_party_balance'
|
|
|
|
@classmethod
|
|
def parse(cls, report, objects, data, localcontext):
|
|
pool = Pool()
|
|
Party = pool.get('party.party')
|
|
MoveLine = pool.get('account.move.line')
|
|
Move = pool.get('account.move')
|
|
Account = pool.get('account.account')
|
|
Company = pool.get('company.company')
|
|
Date = pool.get('ir.date')
|
|
cursor = Transaction().cursor
|
|
|
|
line = MoveLine.__table__()
|
|
move = Move.__table__()
|
|
account = Account.__table__()
|
|
|
|
company = Company(data['company'])
|
|
localcontext['company'] = company
|
|
localcontext['digits'] = company.currency.digits
|
|
localcontext['fiscalyear'] = data['fiscalyear']
|
|
with Transaction().set_context(context=localcontext):
|
|
line_query, _ = MoveLine.query_get(line)
|
|
if data['posted']:
|
|
posted_clause = move.state == 'posted'
|
|
else:
|
|
posted_clause = Literal(True)
|
|
|
|
cursor.execute(*line.join(move, condition=line.move == move.id
|
|
).join(account, condition=line.account == account.id
|
|
).select(line.party, Sum(line.debit), Sum(line.credit),
|
|
where=(line.party != None)
|
|
& account.active
|
|
& account.kind.in_(('payable', 'receivable'))
|
|
& (account.company == data['company'])
|
|
& ((line.maturity_date <= Date.today())
|
|
| (line.maturity_date == None))
|
|
& line_query & posted_clause,
|
|
group_by=line.party,
|
|
having=(Sum(line.debit) != 0) | (Sum(line.credit) != 0)))
|
|
|
|
res = cursor.fetchall()
|
|
id2party = {}
|
|
for party in Party.browse([x[0] for x in res]):
|
|
id2party[party.id] = party
|
|
objects = [{
|
|
'name': id2party[x[0]].rec_name,
|
|
'debit': x[1],
|
|
'credit': x[2],
|
|
'solde': x[1] - x[2],
|
|
} for x in res]
|
|
objects.sort(lambda x, y: cmp(x['name'], y['name']))
|
|
localcontext['total_debit'] = sum((x['debit'] for x in objects))
|
|
localcontext['total_credit'] = sum((x['credit'] for x in objects))
|
|
localcontext['total_solde'] = sum((x['solde'] for x in objects))
|
|
|
|
return super(ThirdPartyBalance, cls).parse(report, objects, data,
|
|
localcontext)
|
|
|
|
|
|
class OpenAgedBalanceStart(ModelView):
|
|
'Open Aged Balance'
|
|
__name__ = 'account.open_aged_balance.start'
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
balance_type = fields.Selection(
|
|
[('customer', 'Customer'), ('supplier', 'Supplier'), ('both', 'Both')],
|
|
"Type", required=True)
|
|
term1 = fields.Integer("First Term", required=True)
|
|
term2 = fields.Integer("Second Term", required=True)
|
|
term3 = fields.Integer("Third Term", required=True)
|
|
unit = fields.Selection(
|
|
[('day', 'Day'), ('month', 'Month')], "Unit", required=True)
|
|
posted = fields.Boolean('Posted Move', help='Show only posted move')
|
|
|
|
@staticmethod
|
|
def default_balance_type():
|
|
return "customer"
|
|
|
|
@staticmethod
|
|
def default_posted():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_term1():
|
|
return 30
|
|
|
|
@staticmethod
|
|
def default_term2():
|
|
return 60
|
|
|
|
@staticmethod
|
|
def default_term3():
|
|
return 90
|
|
|
|
@staticmethod
|
|
def default_unit():
|
|
return 'day'
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
|
|
class OpenAgedBalance(Wizard):
|
|
'Open Aged Party Balance'
|
|
__name__ = 'account.open_aged_balance'
|
|
start = StateView('account.open_aged_balance.start',
|
|
'account.open_aged_balance_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Print', 'print_', 'tryton-print', default=True),
|
|
])
|
|
print_ = StateAction('account.report_aged_balance')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(OpenAgedBalance, cls).__setup__()
|
|
cls._error_messages.update({
|
|
'warning': 'Warning',
|
|
'term_overlap_desc': 'You cannot define overlapping terms',
|
|
})
|
|
|
|
def do_print_(self, action):
|
|
if not (self.start.term1 < self.start.term2 < self.start.term3):
|
|
self.raise_user_error(error="warning",
|
|
error_description="term_overlap_desc")
|
|
data = {
|
|
'company': self.start.company.id,
|
|
'term1': self.start.term1,
|
|
'term2': self.start.term2,
|
|
'term3': self.start.term3,
|
|
'unit': self.start.unit,
|
|
'posted': self.start.posted,
|
|
'balance_type': self.start.balance_type,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class AgedBalance(Report):
|
|
__name__ = 'account.aged_balance'
|
|
|
|
@classmethod
|
|
def parse(cls, report, objects, data, localcontext):
|
|
pool = Pool()
|
|
Party = pool.get('party.party')
|
|
MoveLine = pool.get('account.move.line')
|
|
Move = pool.get('account.move')
|
|
Account = pool.get('account.account')
|
|
Company = pool.get('company.company')
|
|
Date = pool.get('ir.date')
|
|
cursor = Transaction().cursor
|
|
|
|
line = MoveLine.__table__()
|
|
move = Move.__table__()
|
|
account = Account.__table__()
|
|
|
|
company = Company(data['company'])
|
|
localcontext['digits'] = company.currency.digits
|
|
localcontext['posted'] = data['posted']
|
|
with Transaction().set_context(context=localcontext):
|
|
line_query, _ = MoveLine.query_get(line)
|
|
|
|
terms = (data['term1'], data['term2'], data['term3'])
|
|
if data['unit'] == 'month':
|
|
coef = datetime.timedelta(days=30)
|
|
else:
|
|
coef = datetime.timedelta(days=1)
|
|
|
|
kind = {
|
|
'both': ('payable', 'receivable'),
|
|
'supplier': ('payable',),
|
|
'customer': ('receivable',),
|
|
}[data['balance_type']]
|
|
|
|
res = {}
|
|
for position, term in enumerate(terms):
|
|
term_query = line.maturity_date <= (Date.today() - term * coef)
|
|
if position != 2:
|
|
term_query &= line.maturity_date > (
|
|
Date.today() - terms[position + 1] * coef)
|
|
|
|
cursor.execute(*line.join(move, condition=line.move == move.id
|
|
).join(account, condition=line.account == account.id
|
|
).select(line.party, Sum(line.debit) - Sum(line.credit),
|
|
where=(line.party != None)
|
|
& account.active
|
|
& account.kind.in_(kind)
|
|
& (line.reconciliation == None)
|
|
& (account.company == data['company'])
|
|
& term_query & line_query,
|
|
group_by=line.party,
|
|
having=(Sum(line.debit) - Sum(line.credit)) != 0))
|
|
for party, solde in cursor.fetchall():
|
|
if party in res:
|
|
res[party][position] = solde
|
|
else:
|
|
res[party] = [(i[0] == position) and solde
|
|
or Decimal("0.0") for i in enumerate(terms)]
|
|
parties = Party.search([
|
|
('id', 'in', [k for k in res.iterkeys()]),
|
|
])
|
|
|
|
localcontext['main_title'] = data['balance_type']
|
|
localcontext['unit'] = data['unit']
|
|
for i in range(3):
|
|
localcontext['total' + str(i)] = sum(
|
|
(v[i] for v in res.itervalues()))
|
|
localcontext['term' + str(i)] = terms[i]
|
|
|
|
localcontext['company'] = company
|
|
localcontext['parties'] = [{
|
|
'name': p.rec_name,
|
|
'amount0': res[p.id][0],
|
|
'amount1': res[p.id][1],
|
|
'amount2': res[p.id][2],
|
|
} for p in parties]
|
|
|
|
return super(AgedBalance, cls).parse(report, objects, data,
|
|
localcontext)
|