Files
tryton/modules/account/journal.py
T

336 lines
12 KiB
Python
Raw Normal View History

2014-12-02 12:54:29 +01:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
2015-09-01 23:57:00 +02:00
from decimal import Decimal
from sql.aggregate import Sum
2018-12-29 14:20:28 +01:00
from trytond import backend
from trytond.i18n import gettext
2018-03-26 14:08:06 +02:00
from trytond.model import (
ModelView, ModelSQL, Workflow, DeactivableMixin, fields, Unique)
2018-12-29 14:20:28 +01:00
from trytond.model.exceptions import AccessError
2021-03-11 23:00:09 +01:00
from trytond.pyson import Eval, Bool, Id
2010-08-19 22:02:00 +02:00
from trytond.transaction import Transaction
2011-07-03 13:18:40 +02:00
from trytond.pool import Pool
from trytond.tools import reduce_ids, grouped_slice, lstrip_wildcard
2017-04-01 00:25:49 +02:00
from trytond.tools.multivalue import migrate_property
from trytond.modules.company.model import (
CompanyMultiValueMixin, CompanyValueMixin)
2008-03-17 18:32:06 +01:00
STATES = {
2011-08-21 11:20:29 +02:00
'readonly': Eval('state') == 'close',
2008-03-17 18:32:06 +01:00
}
DEPENDS = ['state']
2008-03-13 23:22:59 +01:00
2018-03-26 14:08:06 +02:00
class Journal(
DeactivableMixin, ModelSQL, ModelView, CompanyMultiValueMixin):
2008-03-13 23:22:59 +01:00
'Journal'
2012-09-24 22:17:29 +02:00
__name__ = 'account.journal'
2008-03-13 23:22:59 +01:00
name = fields.Char('Name', size=None, required=True, translate=True)
code = fields.Char('Code', size=None)
2018-05-30 14:12:08 +02:00
type = fields.Selection([
('general', "General"),
('revenue', "Revenue"),
('expense', "Expense"),
('cash', "Cash"),
('situation', "Situation"),
('write-off', "Write-Off"),
], 'Type', required=True)
2017-04-01 00:25:49 +02:00
sequence = fields.MultiValue(fields.Many2One(
'ir.sequence', "Sequence",
domain=[
2021-03-11 23:00:09 +01:00
('sequence_type', '=',
Id('account', 'sequence_type_account_journal')),
2017-04-01 00:25:49 +02:00
('company', 'in', [
Eval('context', {}).get('company', -1), None]),
],
2011-08-21 11:20:29 +02:00
states={
2013-10-09 18:25:04 +02:00
'required': Bool(Eval('context', {}).get('company', -1)),
2011-08-21 11:20:29 +02:00
}))
2017-04-01 00:25:49 +02:00
sequences = fields.One2Many(
'account.journal.sequence', 'journal', "Sequences")
2015-09-01 23:57:00 +02:00
debit = fields.Function(fields.Numeric('Debit',
digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits']), 'get_debit_credit_balance')
credit = fields.Function(fields.Numeric('Credit',
digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits']), 'get_debit_credit_balance')
balance = fields.Function(fields.Numeric('Balance',
digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits']), 'get_debit_credit_balance')
currency_digits = fields.Function(fields.Integer('Currency Digits'),
'get_currency_digits')
2008-03-13 23:22:59 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def __setup__(cls):
super(Journal, cls).__setup__()
cls._order.insert(0, ('name', 'ASC'))
2008-04-22 17:47:52 +02:00
@classmethod
def default_sequence(cls, **pattern):
2012-03-27 10:43:03 +02:00
return None
2008-03-13 23:22:59 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def search_rec_name(cls, name, clause):
2015-04-05 23:39:57 +02:00
if clause[1].startswith('!') or clause[1].startswith('not '):
bool_op = 'AND'
else:
bool_op = 'OR'
code_value = clause[2]
if clause[1].endswith('like'):
code_value = lstrip_wildcard(clause[2])
2015-04-05 23:39:57 +02:00
return [bool_op,
('code', clause[1], code_value) + tuple(clause[3:]),
2014-01-31 14:06:56 +01:00
(cls._rec_name,) + tuple(clause[1:]),
]
2008-03-17 18:32:06 +01:00
2015-09-01 23:57:00 +02:00
@classmethod
def get_currency_digits(cls, journals, name):
pool = Pool()
Company = pool.get('company.company')
company_id = Transaction().context.get('company')
if company_id:
company = Company(company_id)
digits = company.currency.digits
else:
digits = 2
return dict.fromkeys([j.id for j in journals], digits)
@classmethod
def get_debit_credit_balance(cls, journals, names):
pool = Pool()
MoveLine = pool.get('account.move.line')
Move = pool.get('account.move')
Account = pool.get('account.account')
2019-03-24 18:00:01 +01:00
AccountType = pool.get('account.account.type')
Company = pool.get('company.company')
2015-09-01 23:57:00 +02:00
context = Transaction().context
cursor = Transaction().connection.cursor()
2015-09-01 23:57:00 +02:00
result = {}
ids = [j.id for j in journals]
for name in ['debit', 'credit', 'balance']:
result[name] = dict.fromkeys(ids, 0)
company_id = Transaction().context.get('company')
if not company_id:
return result
company = Company(company_id)
2015-09-01 23:57:00 +02:00
line = MoveLine.__table__()
move = Move.__table__()
account = Account.__table__()
2019-03-24 18:00:01 +01:00
account_type = AccountType.__table__()
where = ((move.date >= context.get('start_date'))
& (move.date <= context.get('end_date'))
2019-03-24 18:00:01 +01:00
& ~account_type.receivable
& ~account_type.payable
& (move.company == company.id))
2015-09-01 23:57:00 +02:00
for sub_journals in grouped_slice(journals):
sub_journals = list(sub_journals)
red_sql = reduce_ids(move.journal, [j.id for j in sub_journals])
query = line.join(move, 'LEFT', condition=line.move == move.id
).join(account, 'LEFT', condition=line.account == account.id
2019-03-24 18:00:01 +01:00
).join(account_type, 'LEFT',
condition=account.type == account_type.id
2015-09-01 23:57:00 +02:00
).select(move.journal, Sum(line.debit), Sum(line.credit),
where=where & red_sql,
2015-09-01 23:57:00 +02:00
group_by=move.journal)
cursor.execute(*query)
for journal_id, debit, credit in cursor:
2015-09-01 23:57:00 +02:00
# SQLite uses float for SUM
if not isinstance(debit, Decimal):
debit = Decimal(str(debit))
if not isinstance(credit, Decimal):
credit = Decimal(str(credit))
result['debit'][journal_id] = company.currency.round(debit)
result['credit'][journal_id] = company.currency.round(credit)
result['balance'][journal_id] = company.currency.round(
debit - credit)
2015-09-01 23:57:00 +02:00
return result
2017-04-01 00:25:49 +02:00
class JournalSequence(ModelSQL, CompanyValueMixin):
"Journal Sequence"
__name__ = 'account.journal.sequence'
journal = fields.Many2One(
'account.journal', "Journal", ondelete='CASCADE', select=True,
context={
'company': Eval('company', -1),
},
depends=['company'])
2017-04-01 00:25:49 +02:00
sequence = fields.Many2One(
'ir.sequence', "Sequence",
domain=[
2021-03-11 23:00:09 +01:00
('sequence_type', '=',
Id('account', 'sequence_type_account_journal')),
2017-04-01 00:25:49 +02:00
('company', 'in', [Eval('company', -1), None]),
],
depends=['company'])
@classmethod
def __register__(cls, module_name):
2019-12-06 17:33:46 +01:00
exist = backend.TableHandler.table_exist(cls._table)
2017-04-01 00:25:49 +02:00
super(JournalSequence, cls).__register__(module_name)
if not exist:
cls._migrate_property([], [], [])
@classmethod
def _migrate_property(cls, field_names, value_names, fields):
field_names.append('sequence')
value_names.append('sequence')
fields.append('company')
migrate_property(
'account.journal', field_names, cls, value_names,
parent='journal', fields=fields)
2016-05-20 15:34:29 +02:00
class JournalCashContext(ModelView):
'Journal Cash Context'
__name__ = 'account.journal.open_cash.context'
2015-09-01 23:57:00 +02:00
start_date = fields.Date('Start Date', required=True)
end_date = fields.Date('End Date', required=True)
@classmethod
def default_start_date(cls):
return Pool().get('ir.date').today()
default_end_date = default_start_date
2008-03-17 18:32:06 +01:00
2018-03-26 14:08:06 +02:00
class JournalPeriod(
DeactivableMixin, Workflow, ModelSQL, ModelView):
2008-03-17 18:32:06 +01:00
'Journal - Period'
2012-09-24 22:17:29 +02:00
__name__ = 'account.journal.period'
2008-03-17 18:32:06 +01:00
journal = fields.Many2One('account.journal', 'Journal', required=True,
ondelete='CASCADE', states=STATES, depends=DEPENDS)
2008-03-17 18:32:06 +01:00
period = fields.Many2One('account.period', 'Period', required=True,
ondelete='CASCADE', states=STATES, depends=DEPENDS)
2010-03-11 16:45:27 +01:00
icon = fields.Function(fields.Char('Icon'), 'get_icon')
2008-03-17 18:32:06 +01:00
state = fields.Selection([
('open', 'Open'),
('close', 'Close'),
], 'State', readonly=True, required=True)
2012-09-24 22:17:29 +02:00
@classmethod
def __setup__(cls):
super(JournalPeriod, cls).__setup__()
t = cls.__table__()
2012-09-24 22:17:29 +02:00
cls._sql_constraints += [
('journal_period_uniq', Unique(t, t.journal, t.period),
2018-12-29 14:20:28 +01:00
'account.msg_journal_period_unique'),
2012-09-24 22:17:29 +02:00
]
cls._transitions |= set((
('open', 'close'),
('close', 'open'),
))
cls._buttons.update({
'close': {
'invisible': Eval('state') != 'open',
2018-02-19 11:36:13 +01:00
'depends': ['state'],
},
'reopen': {
'invisible': Eval('state') != 'close',
2018-02-19 11:36:13 +01:00
'depends': ['state'],
},
})
2018-03-26 14:08:06 +02:00
cls.active.states = STATES
cls.active.depends = DEPENDS
2008-03-17 18:32:06 +01:00
@classmethod
def __register__(cls, module_name):
super(JournalPeriod, cls).__register__(module_name)
2018-07-24 22:01:07 +02:00
table = cls.__table_handler__(cls, module_name)
# Migration from 4.2: remove name column
table.drop_column('name')
2012-09-24 22:17:29 +02:00
@staticmethod
def default_state():
2008-03-17 18:32:06 +01:00
return 'open'
def get_rec_name(self, name):
return '%s - %s' % (self.journal.rec_name, self.period.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,
[('journal.rec_name',) + tuple(clause[1:])],
[('period.rec_name',) + tuple(clause[1:])],
]
2012-09-24 22:17:29 +02:00
def get_icon(self, name):
return {
'open': 'tryton-account-open',
'close': 'tryton-account-close',
}.get(self.state)
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def _check(cls, periods):
Move = Pool().get('account.move')
for period in periods:
moves = Move.search([
('journal', '=', period.journal.id),
('period', '=', period.period.id),
], limit=1)
if moves:
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account.msg_modify_delete_journal_period_moves',
journal_period=period.rec_name))
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
2013-01-06 22:18:13 +01:00
def create(cls, vlist):
2012-09-24 22:17:29 +02:00
Period = Pool().get('account.period')
2013-01-06 22:18:13 +01:00
for vals in vlist:
if vals.get('period'):
period = Period(vals['period'])
if period.state != 'open':
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account'
'.msg_create_journal_period_closed_period',
period=period.rec_name))
2013-01-06 22:18:13 +01:00
return super(JournalPeriod, cls).create(vlist)
2012-09-24 22:17:29 +02:00
@classmethod
2013-12-19 14:44:33 +01:00
def write(cls, *args):
actions = iter(args)
for journal_periods, values in zip(actions, actions):
if (values != {'state': 'close'}
and values != {'state': 'open'}):
cls._check(journal_periods)
if values.get('state') == 'open':
for journal_period in journal_periods:
if journal_period.period.state != 'open':
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account'
'.msg_open_journal_period_closed_period',
journal_period=journal_period.rec_name,
period=journal_period.period.rec_name))
2013-12-19 14:44:33 +01:00
super(JournalPeriod, cls).write(*args)
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def delete(cls, periods):
cls._check(periods)
super(JournalPeriod, cls).delete(periods)
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
@ModelView.button
@Workflow.transition('close')
2012-09-24 22:17:29 +02:00
def close(cls, periods):
2008-09-19 15:00:26 +02:00
'''
Close journal - period
'''
pass
2008-09-19 15:00:26 +02:00
2012-09-24 22:17:29 +02:00
@classmethod
@ModelView.button
@Workflow.transition('open')
def reopen(cls, periods):
2012-01-02 17:54:33 +01:00
"Open journal - period"
pass