Files
tryton/move.py
T

2038 lines
78 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.
2008-03-17 18:32:06 +01:00
from decimal import Decimal
import datetime
2014-09-19 16:24:48 +02:00
from itertools import groupby, combinations
2013-02-23 14:09:33 +01:00
from operator import itemgetter
2014-05-09 23:32:17 +02:00
from collections import defaultdict
from sql import Null
2013-09-06 22:33:47 +02:00
from sql.aggregate import Sum
2014-09-19 16:24:48 +02:00
from sql.conditionals import Coalesce, Case
2010-08-19 22:02:00 +02:00
from trytond.model import ModelView, ModelSQL, fields
2012-01-02 17:54:33 +01:00
from trytond.wizard import Wizard, StateTransition, StateView, StateAction, \
Button
2010-08-19 22:02:00 +02:00
from trytond.report import Report
2013-09-06 22:33:47 +02:00
from trytond import backend
from trytond.pyson import Eval, Bool, PYSONEncoder
2010-08-19 22:02:00 +02:00
from trytond.transaction import Transaction
2012-09-24 22:17:29 +02:00
from trytond.pool import Pool, PoolMeta
from trytond.rpc import RPC
2014-07-17 18:56:39 +02:00
from trytond.tools import reduce_ids, grouped_slice
2014-09-19 16:24:48 +02:00
from trytond.config import config
2012-09-24 22:17:29 +02:00
2013-05-21 19:09:37 +02:00
__all__ = ['Move', 'Reconciliation', 'Line', 'OpenJournalAsk',
2014-09-19 16:24:48 +02:00
'OpenJournal', 'OpenAccount',
'ReconcileLinesWriteOff', 'ReconcileLines',
'UnreconcileLines',
'Reconcile', 'ReconcileShow',
2014-11-10 23:35:54 +01:00
'CancelMoves', 'CancelMovesDefault',
2014-05-09 23:32:17 +02:00
'FiscalYearLine', 'FiscalYear2',
2012-09-24 22:17:29 +02:00
'PrintGeneralJournalStart', 'PrintGeneralJournal', 'GeneralJournal']
__metaclass__ = PoolMeta
2008-03-17 18:32:06 +01:00
_MOVE_STATES = {
2011-08-21 11:20:29 +02:00
'readonly': Eval('state') == 'posted',
}
_MOVE_DEPENDS = ['state']
2008-03-17 18:32:06 +01:00
_LINE_STATES = {
2011-08-21 11:20:29 +02:00
'readonly': Eval('state') == 'valid',
}
_LINE_DEPENDS = ['state']
2008-03-17 18:32:06 +01:00
2009-02-28 16:57:10 +01:00
class Move(ModelSQL, ModelView):
2008-03-17 18:32:06 +01:00
'Account Move'
2012-09-24 22:17:29 +02:00
__name__ = 'account.move'
2012-09-29 15:33:21 +02:00
_rec_name = 'number'
number = fields.Char('Number', required=True, readonly=True)
post_number = fields.Char('Post Number', readonly=True,
help='Also known as Folio Number')
2014-12-03 16:13:08 +01:00
company = fields.Many2One('company.company', 'Company', required=True)
2008-03-17 18:32:06 +01:00
period = fields.Many2One('account.period', 'Period', required=True,
2014-12-03 16:13:08 +01:00
domain=[
('company', '=', Eval('company', -1)),
],
states=_MOVE_STATES, depends=_MOVE_DEPENDS + ['company'], select=True)
2008-03-17 18:32:06 +01:00
journal = fields.Many2One('account.journal', 'Journal', required=True,
states=_MOVE_STATES, depends=_MOVE_DEPENDS)
date = fields.Date('Effective Date', required=True, states=_MOVE_STATES,
2014-01-26 14:28:46 +01:00
depends=_MOVE_DEPENDS)
2008-03-28 19:49:16 +01:00
post_date = fields.Date('Post Date', readonly=True)
2012-09-29 15:33:21 +02:00
description = fields.Char('Description', states=_MOVE_STATES,
depends=_MOVE_DEPENDS)
origin = fields.Reference('Origin', selection='get_origin',
states=_MOVE_STATES, depends=_MOVE_DEPENDS)
2008-03-17 18:32:06 +01:00
state = fields.Selection([
('draft', 'Draft'),
('posted', 'Posted'),
2012-03-05 10:52:56 +01:00
], 'State', required=True, readonly=True, select=True)
2008-03-17 18:32:06 +01:00
lines = fields.One2Many('account.move.line', 'move', 'Lines',
2014-12-03 16:13:08 +01:00
domain=[
('account.company', '=', Eval('company', -1)),
],
states=_MOVE_STATES, depends=_MOVE_DEPENDS + ['company'],
2010-01-20 10:56:33 +01:00
context={
'journal': Eval('journal'),
'period': Eval('period'),
'date': Eval('date'),
})
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def __setup__(cls):
super(Move, cls).__setup__()
cls._check_modify_exclude = ['state']
cls._order.insert(0, ('date', 'DESC'))
2012-09-29 15:33:21 +02:00
cls._order.insert(1, ('number', 'DESC'))
2012-09-24 22:17:29 +02:00
cls._error_messages.update({
'post_empty_move': ('You can not post move "%s" because it is '
'empty.'),
'post_unbalanced_move': ('You can not post move "%s" because '
'it is an unbalanced.'),
'draft_posted_move_journal': ('You can not set posted move '
'"%(move)s" to draft in journal "%(journal)s".'),
'modify_posted_move': ('You can not modify move "%s" because '
'it is already posted.'),
'date_outside_period': ('You can not create move "%(move)s" '
'because it\'s date is outside its period.'),
'draft_closed_period': ('You can not set to draft move '
'"%(move)s" because period "%(period)s" is closed.'),
2014-05-09 23:32:17 +02:00
'period_cancel': (
'The period of move "%s" is closed.\n'
'Use the current period?'),
})
2012-09-24 22:17:29 +02:00
cls._buttons.update({
2012-03-25 00:17:23 +01:00
'post': {
'invisible': Eval('state') == 'posted',
},
'draft': {
'invisible': Eval('state') == 'draft',
},
})
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def __register__(cls, module_name):
2013-09-06 22:33:47 +02:00
TableHandler = backend.get('TableHandler')
2012-09-29 15:33:21 +02:00
cursor = Transaction().cursor
table = TableHandler(cursor, cls, module_name)
2014-12-03 16:13:08 +01:00
sql_table = cls.__table__()
pool = Pool()
Period = pool.get('account.period')
period = Period.__table__()
FiscalYear = pool.get('account.fiscalyear')
fiscalyear = FiscalYear.__table__()
2012-09-29 15:33:21 +02:00
# Migration from 2.4:
# - name renamed into number
# - reference renamed into post_number
if table.column_exist('name'):
table.column_rename('name', 'number')
if table.column_exist('reference'):
table.column_rename('reference', 'post_number')
2014-12-03 16:13:08 +01:00
created_company = not table.column_exist('company')
2012-09-24 22:17:29 +02:00
super(Move, cls).__register__(module_name)
2009-02-02 13:09:08 +01:00
2014-12-03 16:13:08 +01:00
# Migration from 3.4: new company field
if created_company:
# Don't use UPDATE FROM because SQLite nor MySQL support it.
value = period.join(fiscalyear,
condition=period.fiscalyear == fiscalyear.id).select(
fiscalyear.company,
where=period.id == sql_table.period)
cursor.execute(*sql_table.update([sql_table.company], [value]))
2012-09-24 22:17:29 +02:00
table = TableHandler(cursor, cls, module_name)
2009-03-06 00:45:18 +01:00
table.index_action(['journal', 'period'], 'add')
2008-03-17 18:32:06 +01:00
2010-01-23 11:40:35 +01:00
# Add index on create_date
table.index_action('create_date', action='add')
2014-12-03 16:13:08 +01:00
@staticmethod
def default_company():
return Transaction().context.get('company')
2012-09-24 22:17:29 +02:00
@staticmethod
def default_period():
Period = Pool().get('account.period')
return Period.find(Transaction().context.get('company'),
exception=False)
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@staticmethod
def default_state():
2008-03-17 18:32:06 +01:00
return 'draft'
2012-09-24 22:17:29 +02:00
@classmethod
def default_date(cls):
pool = Pool()
Period = pool.get('account.period')
Date = pool.get('ir.date')
period_id = cls.default_period()
2009-10-10 13:31:39 +02:00
if period_id:
2012-09-24 22:17:29 +02:00
period = Period(period_id)
2009-10-10 13:31:39 +02:00
return period.start_date
2012-09-24 22:17:29 +02:00
return Date.today()
2008-03-17 18:32:06 +01:00
2014-01-26 14:28:46 +01:00
@fields.depends('period', 'journal', 'date')
2012-09-24 22:17:29 +02:00
def on_change_with_date(self):
Line = Pool().get('account.move.line')
date = self.date
lines = Line.search([
('journal', '=', self.journal),
('period', '=', self.period),
], order=[('id', 'DESC')], limit=1)
if lines:
date = lines[0].date
elif self.period:
date = self.period.start_date
return date
2009-10-10 13:31:39 +02:00
2012-09-29 15:33:21 +02:00
@classmethod
def _get_origin(cls):
'Return list of Model names for origin Reference'
2014-05-09 23:32:17 +02:00
return ['account.fiscalyear', 'account.move']
2012-09-29 15:33:21 +02:00
@classmethod
def get_origin(cls):
Model = Pool().get('ir.model')
models = cls._get_origin()
models = Model.search([
('model', 'in', models),
])
return [('', '')] + [(m.model, m.name) for m in models]
@classmethod
def validate(cls, moves):
super(Move, cls).validate(moves)
for move in moves:
move.check_date()
2012-09-24 22:17:29 +02:00
def check_date(self):
if (self.date < self.period.start_date
or self.date > self.period.end_date):
self.raise_user_error('date_outside_period', {
'move': self.rec_name,
})
2012-09-24 22:17:29 +02:00
@classmethod
def check_modify(cls, moves):
2012-03-02 07:46:19 +00:00
'Check posted moves for modifications.'
2012-09-24 22:17:29 +02:00
for move in moves:
2012-03-02 07:46:19 +00:00
if move.state == 'posted':
cls.raise_user_error('modify_posted_move', (move.rec_name,))
2012-03-02 07:46:19 +00:00
2012-09-24 22:17:29 +02:00
@classmethod
def search_rec_name(cls, name, clause):
2014-01-31 14:06:56 +01:00
return ['OR',
('post_number',) + tuple(clause[1:]),
(cls._rec_name,) + tuple(clause[1:]),
]
2012-09-24 22:17:29 +02:00
@classmethod
2013-12-19 14:44:33 +01:00
def write(cls, *args):
actions = iter(args)
all_moves = []
args = []
for moves, values in zip(actions, actions):
keys = values.keys()
for key in cls._check_modify_exclude:
if key in keys:
keys.remove(key)
if len(keys):
cls.check_modify(moves)
args.extend((moves, values))
all_moves.extend(moves)
super(Move, cls).write(*args)
cls.validate_move(all_moves)
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):
2011-07-03 13:18:40 +02:00
pool = Pool()
2012-09-24 22:17:29 +02:00
Sequence = pool.get('ir.sequence')
Journal = pool.get('account.journal')
2013-01-06 22:18:13 +01:00
vlist = [x.copy() for x in vlist]
for vals in vlist:
if not vals.get('number'):
journal_id = (vals.get('journal')
or Transaction().context.get('journal'))
if journal_id:
journal = Journal(journal_id)
vals['number'] = Sequence.get_id(journal.sequence.id)
2013-01-06 22:18:13 +01:00
moves = super(Move, cls).create(vlist)
cls.validate_move(moves)
2013-01-06 22:18:13 +01:00
return moves
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def delete(cls, moves):
MoveLine = Pool().get('account.move.line')
cls.check_modify(moves)
MoveLine.delete([l for m in moves for l in m.lines])
super(Move, cls).delete(moves)
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def copy(cls, moves, default=None):
Line = Pool().get('account.move.line')
2008-12-05 13:25:33 +01:00
if default is None:
default = {}
2008-12-05 13:25:33 +01:00
default = default.copy()
2012-09-29 15:33:21 +02:00
default['number'] = None
default['post_number'] = None
2012-09-24 22:17:29 +02:00
default['state'] = cls.default_state()
2012-03-27 10:43:03 +02:00
default['post_date'] = None
default['lines'] = None
2012-09-24 22:17:29 +02:00
new_moves = []
for move in moves:
new_move, = super(Move, cls).copy([move], default=default)
Line.copy(move.lines, default={
'move': new_move.id,
})
new_moves.append(new_move)
return new_moves
2008-12-05 13:25:33 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def validate_move(cls, moves):
2008-03-17 18:32:06 +01:00
'''
2013-05-21 19:09:37 +02:00
Validate balanced move
2008-03-17 18:32:06 +01:00
'''
2012-09-24 22:17:29 +02:00
pool = Pool()
MoveLine = pool.get('account.move.line')
2013-09-06 22:33:47 +02:00
line = MoveLine.__table__()
2013-02-23 14:09:33 +01:00
cursor = Transaction().cursor
amounts = {}
move2draft_lines = {}
2014-07-17 18:56:39 +02:00
for sub_move_ids in grouped_slice([m.id for m in moves]):
2013-09-06 22:33:47 +02:00
red_sql = reduce_ids(line.move, sub_move_ids)
2013-02-23 14:09:33 +01:00
2013-09-06 22:33:47 +02:00
cursor.execute(*line.select(line.move,
Sum(line.debit - line.credit),
where=red_sql,
group_by=line.move))
2013-02-23 14:09:33 +01:00
amounts.update(dict(cursor.fetchall()))
2013-09-06 22:33:47 +02:00
cursor.execute(*line.select(line.move, line.id,
where=red_sql & (line.state == 'draft'),
order_by=line.move))
move2draft_lines.update(dict((k, [j[1] for j in g])
2013-02-23 14:09:33 +01:00
for k, g in groupby(cursor.fetchall(), itemgetter(0))))
valid_moves = []
draft_moves = []
2012-09-24 22:17:29 +02:00
for move in moves:
2013-02-23 14:09:33 +01:00
if move.id not in amounts:
2008-03-17 18:32:06 +01:00
continue
2013-02-23 14:09:33 +01:00
amount = amounts[move.id]
2013-02-24 10:15:11 +01:00
# SQLite uses float for SUM
if not isinstance(amount, Decimal):
amount = Decimal(amount)
draft_lines = MoveLine.browse(move2draft_lines.get(move.id, []))
2014-12-03 16:13:08 +01:00
if not move.company.currency.is_zero(amount):
2013-05-21 19:09:37 +02:00
draft_moves.append(move.id)
2008-03-17 18:32:06 +01:00
continue
if not draft_lines:
continue
2013-02-23 14:09:33 +01:00
valid_moves.append(move.id)
for move_ids, state in (
(valid_moves, 'valid'),
(draft_moves, 'draft'),
):
if move_ids:
2014-07-17 18:56:39 +02:00
for sub_ids in grouped_slice(move_ids):
2013-09-06 22:33:47 +02:00
red_sql = reduce_ids(line.move, sub_ids)
2013-02-23 14:09:33 +01:00
# Use SQL to prevent double validate loop
2013-09-06 22:33:47 +02:00
cursor.execute(*line.update(
columns=[line.state],
values=[state],
where=red_sql))
2008-03-17 18:32:06 +01:00
2014-05-09 23:32:17 +02:00
def _cancel_default(self):
'Return default dictionary to cancel move'
pool = Pool()
Date = pool.get('ir.date')
Period = pool.get('account.period')
default = {
'origin': str(self),
}
if self.period.state == 'close':
self.raise_user_warning('%s.cancel' % self,
'period_cancel', self.rec_name)
date = Date.today()
period_id = Period.find(self.company.id, date=date)
default.update({
'date': date,
'period': period_id,
})
return default
2014-11-10 23:35:54 +01:00
def cancel(self, default=None):
2014-05-09 23:32:17 +02:00
'Return a cancel move'
2014-11-10 23:35:54 +01:00
if default is None:
default = {}
default.update(self._cancel_default())
cancel_move, = self.copy([self], default=default)
2014-05-09 23:32:17 +02:00
for line in cancel_move.lines:
line.debit *= -1
line.credit *= -1
for tax_line in line.tax_lines:
tax_line.amount *= -1
2014-11-01 11:53:42 +01:00
line.tax_lines = line.tax_lines # Force tax_lines changing
cancel_move.lines = cancel_move.lines # Force lines changing
cancel_move.save()
2014-05-09 23:32:17 +02:00
return cancel_move
2012-09-24 22:17:29 +02:00
@classmethod
2012-03-25 00:17:23 +01:00
@ModelView.button
2012-09-24 22:17:29 +02:00
def post(cls, moves):
2011-07-03 13:18:40 +02:00
pool = Pool()
2012-09-24 22:17:29 +02:00
Sequence = pool.get('ir.sequence')
Date = pool.get('ir.date')
Line = pool.get('account.move.line')
2008-04-10 11:22:16 +02:00
2008-03-17 18:32:06 +01:00
for move in moves:
amount = Decimal('0.0')
2008-03-28 19:49:16 +01:00
if not move.lines:
cls.raise_user_error('post_empty_move', (move.rec_name,))
2008-03-17 18:32:06 +01:00
company = None
for line in move.lines:
amount += line.debit - line.credit
if not company:
company = line.account.company
2012-09-24 22:17:29 +02:00
if not company.currency.is_zero(amount):
cls.raise_user_error('post_unbalanced_move', (move.rec_name,))
2008-03-17 18:32:06 +01:00
for move in moves:
values = {
2008-03-17 18:32:06 +01:00
'state': 'posted',
}
if not move.post_number:
values['post_date'] = Date.today()
values['post_number'] = Sequence.get_id(
2013-01-22 16:14:24 +01:00
move.period.post_move_sequence_used.id)
cls.write([move], values)
2008-03-17 18:32:06 +01:00
2014-09-19 11:49:46 +02:00
keyfunc = lambda l: (l.party, l.account)
to_reconcile = [l for l in move.lines
if ((l.debit == l.credit == Decimal('0'))
and l.account.reconcile)]
2014-09-19 11:49:46 +02:00
to_reconcile = sorted(to_reconcile, key=keyfunc)
for _, zero_lines in groupby(to_reconcile, keyfunc):
Line.reconcile(list(zero_lines))
2012-09-24 22:17:29 +02:00
@classmethod
2012-03-25 00:17:23 +01:00
@ModelView.button
2012-09-24 22:17:29 +02:00
def draft(cls, moves):
for move in moves:
2008-03-17 18:32:06 +01:00
if not move.journal.update_posted:
cls.raise_user_error('draft_posted_move_journal', {
'move': move.rec_name,
'journal': move.journal.rec_name,
})
if move.period.state == 'close':
cls.raise_user_error('draft_closed_period', {
'move': move.rec_name,
'period': move.period.rec_name,
})
cls.write(moves, {
2008-03-17 18:32:06 +01:00
'state': 'draft',
2010-08-19 22:02:00 +02:00
})
2008-03-17 18:32:06 +01:00
2009-02-28 16:57:10 +01:00
class Reconciliation(ModelSQL, ModelView):
2008-03-19 22:13:51 +01:00
'Account Move Reconciliation Lines'
2012-09-24 22:17:29 +02:00
__name__ = 'account.move.reconciliation'
2008-03-19 22:13:51 +01:00
name = fields.Char('Name', size=None, required=True)
lines = fields.One2Many('account.move.line', 'reconciliation',
'Lines')
2012-09-24 22:17:29 +02:00
@classmethod
def __setup__(cls):
super(Reconciliation, cls).__setup__()
cls._error_messages.update({
'modify': 'You can not modify a reconciliation.',
'reconciliation_line_not_valid': ('You can not reconcile line '
'"%s" because it is not in valid state.'),
'reconciliation_different_accounts': ('You can not reconcile '
'line "%(line)s" because it\'s account "%(account1)s" is '
'different from "%(account2)s".'),
2013-10-05 12:16:23 +02:00
'reconciliation_account_no_reconcile': (
'You can not reconcile '
'line "%(line)s" because it\'s account "%(account)s" is '
'configured as not reconcilable.'),
'reconciliation_different_parties': ('You can not reconcile '
'line "%(line)s" because it\'s party "%(party1)s" is '
'different from "%(party2)s".'),
'reconciliation_unbalanced': ('You can not create a '
'reconciliation where debit "%(debit)s" and credit '
'"%(credit)s" differ.'),
})
2008-03-19 22:13:51 +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
Sequence = Pool().get('ir.sequence')
2013-01-06 22:18:13 +01:00
vlist = [x.copy() for x in vlist]
for vals in vlist:
if 'name' not in vals:
vals['name'] = Sequence.get('account.move.reconciliation')
2013-01-06 22:18:13 +01:00
return super(Reconciliation, cls).create(vlist)
2008-03-19 22:13:51 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
2013-12-19 14:44:33 +01:00
def write(cls, moves, values, *args):
2012-09-24 22:17:29 +02:00
cls.raise_user_error('modify')
@classmethod
def validate(cls, reconciliations):
super(Reconciliation, cls).validate(reconciliations)
cls.check_lines(reconciliations)
2012-09-24 22:17:29 +02:00
@classmethod
def check_lines(cls, reconciliations):
Lang = Pool().get('ir.lang')
2012-09-24 22:17:29 +02:00
for reconciliation in reconciliations:
debit = Decimal('0.0')
credit = Decimal('0.0')
2008-03-19 22:13:51 +01:00
account = None
if reconciliation.lines:
party = reconciliation.lines[0].party
2008-03-19 22:13:51 +01:00
for line in reconciliation.lines:
if line.state != 'valid':
cls.raise_user_error('reconciliation_line_not_valid',
(line.rec_name,))
debit += line.debit
credit += line.credit
2008-03-19 22:13:51 +01:00
if not account:
account = line.account
elif account.id != line.account.id:
cls.raise_user_error('reconciliation_different_accounts', {
'line': line.rec_name,
'account1': line.account.rec_name,
'account2': account.rec_name,
})
if not account.reconcile:
cls.raise_user_error('reconciliation_account_no_reconcile',
{
'line': line.rec_name,
'account': line.account.rec_name,
})
if line.party != party:
cls.raise_user_error('reconciliation_different_parties', {
'line': line.rec_name,
'party1': line.party.rec_name,
'party2': party.rec_name,
})
if not account.company.currency.is_zero(debit - credit):
language = Transaction().language
languages = Lang.search([('code', '=', language)])
if not languages:
languages = Lang.search([('code', '=', 'en_US')])
language = languages[0]
debit = Lang.currency(
language, debit, account.company.currency)
credit = Lang.currency(
language, credit, account.company.currency)
cls.raise_user_error('reconciliation_unbalanced', {
'debit': debit,
'credit': credit,
})
2008-03-19 22:13:51 +01:00
2009-02-28 16:57:10 +01:00
class Line(ModelSQL, ModelView):
2008-03-17 18:32:06 +01:00
'Account Move Line'
2012-09-24 22:17:29 +02:00
__name__ = 'account.move.line'
2010-01-20 10:56:33 +01:00
debit = fields.Numeric('Debit', digits=(16, Eval('currency_digits', 2)),
required=True,
depends=['currency_digits', 'credit', 'tax_lines', 'journal'])
2010-01-20 10:56:33 +01:00
credit = fields.Numeric('Credit', digits=(16, Eval('currency_digits', 2)),
required=True,
depends=['currency_digits', 'debit', 'tax_lines', 'journal'])
2008-03-17 18:32:06 +01:00
account = fields.Many2One('account.account', 'Account', required=True,
domain=[('kind', '!=', 'view')],
2014-01-26 14:28:46 +01:00
select=True)
2012-03-05 10:52:56 +01:00
move = fields.Many2One('account.move', 'Move', select=True, required=True,
2011-08-04 16:57:14 +02:00
states={
'required': False,
2011-08-21 11:20:29 +02:00
'readonly': Eval('state') == 'valid',
2011-08-04 16:57:14 +02:00
},
depends=['state'])
2010-03-11 16:45:27 +01:00
journal = fields.Function(fields.Many2One('account.journal', 'Journal'),
'get_move_field', setter='set_move_field',
searcher='search_move_field')
period = fields.Function(fields.Many2One('account.period', 'Period'),
'get_move_field', setter='set_move_field',
searcher='search_move_field')
date = fields.Function(fields.Date('Effective Date', required=True),
'get_move_field', setter='set_move_field',
searcher='search_move_field')
2012-09-29 15:33:21 +02:00
origin = fields.Function(fields.Reference('Origin',
selection='get_origin'),
'get_move_field', searcher='search_move_field')
description = fields.Char('Description')
2014-02-03 18:12:16 +01:00
move_description = fields.Function(fields.Char('Move Description'),
'get_move_field', setter='set_move_field',
searcher='search_move_field')
2008-03-17 18:32:06 +01:00
amount_second_currency = fields.Numeric('Amount Second Currency',
2014-12-02 12:54:29 +01:00
digits=(16, Eval('second_currency_digits', 2)),
help='The amount expressed in a second currency',
states={
'required': Bool(Eval('second_currency')),
},
depends=['second_currency_digits', 'second_currency'])
2008-05-09 17:24:55 +02:00
second_currency = fields.Many2One('currency.currency', 'Second Currency',
help='The second currency',
states={
'required': Bool(Eval('amount_second_currency')),
},
depends=['amount_second_currency'])
party = fields.Many2One('party.party', 'Party', select=True,
states={
'required': Eval('party_required', False),
'invisible': ~Eval('party_required', False),
},
depends=['party_required'], ondelete='RESTRICT')
2014-12-02 12:54:29 +01:00
party_required = fields.Function(fields.Boolean('Party Required'),
'on_change_with_party_required')
maturity_date = fields.Date('Maturity Date',
2013-02-16 18:54:35 +01:00
help='This field is used for payable and receivable lines. \n'
'You can put the limit date for the payment.')
2008-03-17 18:32:06 +01:00
state = fields.Selection([
('draft', 'Draft'),
('valid', 'Valid'),
2012-03-05 10:52:56 +01:00
], 'State', readonly=True, required=True, select=True)
2008-03-19 22:13:51 +01:00
reconciliation = fields.Many2One('account.move.reconciliation',
2012-03-05 10:52:56 +01:00
'Reconciliation', readonly=True, ondelete='SET NULL', select=True)
2008-03-23 23:05:12 +01:00
tax_lines = fields.One2Many('account.tax.line', 'move_line', 'Tax Lines')
2010-03-11 16:45:27 +01:00
move_state = fields.Function(fields.Selection([
('draft', 'Draft'),
('posted', 'Posted'),
], 'Move State'), 'get_move_field', searcher='search_move_field')
currency_digits = fields.Function(fields.Integer('Currency Digits'),
'get_currency_digits')
second_currency_digits = fields.Function(fields.Integer(
'Second Currency Digits'), 'get_currency_digits')
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def __setup__(cls):
super(Line, cls).__setup__()
2014-09-21 12:12:26 +02:00
cls._check_modify_exclude = {'reconciliation'}
cls._reconciliation_modify_disallow = {
'account', 'debit', 'credit', 'party',
}
2012-09-24 22:17:29 +02:00
cls._sql_constraints += [
2008-03-17 18:32:06 +01:00
('credit_debit',
'CHECK(credit * debit = 0.0)',
'Wrong credit/debit values.'),
2012-09-24 22:17:29 +02:00
]
cls.__rpc__.update({
'on_write': RPC(instantiate=0),
2012-09-29 15:33:21 +02:00
'get_origin': RPC(),
2012-09-24 22:17:29 +02:00
})
cls._order[0] = ('id', 'DESC')
cls._error_messages.update({
'add_modify_closed_journal_period': ('You can not '
'add/modify lines in closed journal period "%s".'),
'modify_posted_move': ('You can not modify lines of move "%s" '
'because it is already posted.'),
2013-10-05 12:16:23 +02:00
'modify_reconciled': ('You can not modify line "%s" because '
'it is reconciled.'),
'no_journal': ('Move line cannot be created because there is '
'no journal defined.'),
'move_view_account': ('You can not create a move line with '
'account "%s" because it is a view account.'),
2013-10-05 12:16:23 +02:00
'move_inactive_account': ('You can not create a move line '
'with account "%s" because it is inactive.'),
'already_reconciled': 'Line "%s" (%d) already reconciled.',
'party_required': 'Party is required on line "%s"',
'party_set': 'Party must not be set on line "%s"',
})
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def __register__(cls, module_name):
2013-09-06 22:33:47 +02:00
TableHandler = backend.get('TableHandler')
2012-09-29 15:33:21 +02:00
cursor = Transaction().cursor
table = TableHandler(cursor, cls, module_name)
# Migration from 2.4: reference renamed into description
if table.column_exist('reference'):
table.column_rename('reference', 'description')
2012-09-24 22:17:29 +02:00
super(Line, cls).__register__(module_name)
2012-09-24 22:17:29 +02:00
table = TableHandler(cursor, cls, module_name)
2010-03-12 14:14:53 +01:00
# Index for General Ledger
table.index_action(['move', 'account'], 'add')
# Migration from 1.2
table.not_null_action('blocked', action='remove')
# Migration from 2.4: remove name, active
2012-09-29 15:33:21 +02:00
table.not_null_action('name', action='remove')
table.not_null_action('active', action='remove')
table.index_action('active', action='remove')
2012-09-29 15:33:21 +02:00
2012-09-24 22:17:29 +02:00
@classmethod
def default_date(cls):
2008-03-17 18:32:06 +01:00
'''
Return the date of the last line for journal, period
or the starting date of the period
or today
'''
2012-09-24 22:17:29 +02:00
pool = Pool()
Period = pool.get('account.period')
Date = pool.get('ir.date')
2008-10-08 12:17:47 +02:00
2012-09-24 22:17:29 +02:00
date = Date.today()
lines = cls.search([
('journal', '=', Transaction().context.get('journal')),
('period', '=', Transaction().context.get('period')),
], order=[('id', 'DESC')], limit=1)
if lines:
date = lines[0].date
2010-08-19 22:02:00 +02:00
elif Transaction().context.get('period'):
2012-09-24 22:17:29 +02:00
period = Period(Transaction().context['period'])
date = period.start_date
2010-08-19 22:02:00 +02:00
if Transaction().context.get('date'):
2012-09-24 22:17:29 +02:00
date = Transaction().context['date']
return date
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@staticmethod
def default_state():
2008-03-17 18:32:06 +01:00
return 'draft'
2012-09-24 22:17:29 +02:00
@staticmethod
def default_currency_digits():
2009-04-02 13:31:32 +02:00
return 2
2012-09-24 22:17:29 +02:00
@staticmethod
def default_debit():
return Decimal(0)
2012-09-24 22:17:29 +02:00
@staticmethod
def default_credit():
return Decimal(0)
2012-09-24 22:17:29 +02:00
@classmethod
2014-06-08 00:34:16 +02:00
def default_get(cls, fields, with_rec_name=True):
2011-07-03 13:18:40 +02:00
pool = Pool()
2012-09-24 22:17:29 +02:00
Move = pool.get('account.move')
Tax = pool.get('account.tax')
Account = pool.get('account.account')
TaxCode = pool.get('account.tax.code')
values = super(Line, cls).default_get(fields,
2014-06-08 00:34:16 +02:00
with_rec_name=with_rec_name)
2008-03-17 18:32:06 +01:00
if 'move' not in fields:
2014-12-02 12:54:29 +01:00
# Not manual entry
2008-06-06 11:22:06 +02:00
if 'date' in values:
values = values.copy()
del values['date']
2008-03-17 18:32:06 +01:00
return values
2010-08-19 22:02:00 +02:00
if (Transaction().context.get('journal')
and Transaction().context.get('period')):
2012-09-24 22:17:29 +02:00
lines = cls.search([
2010-08-19 22:02:00 +02:00
('move.journal', '=', Transaction().context['journal']),
('move.period', '=', Transaction().context['period']),
('create_uid', '=', Transaction().user),
2008-03-17 18:32:06 +01:00
('state', '=', 'draft'),
2010-08-19 22:02:00 +02:00
], order=[('id', 'DESC')], limit=1)
2012-09-24 22:17:29 +02:00
if not lines:
2008-03-17 18:32:06 +01:00
return values
2012-12-16 23:34:18 +01:00
move = lines[0].move
values['move'] = move.id
values['move.rec_name'] = move.rec_name
2008-03-17 18:32:06 +01:00
if 'move' not in values:
return values
2012-09-24 22:17:29 +02:00
move = Move(values['move'])
2008-03-17 18:32:06 +01:00
total = Decimal('0.0')
taxes = {}
no_code_taxes = []
2008-03-17 18:32:06 +01:00
for line in move.lines:
total += line.debit - line.credit
2012-12-16 23:34:18 +01:00
if line.party and 'party' in fields and 'party' not in values:
values['party'] = line.party.id
values['party.rec_name'] = line.party.rec_name
if move.journal.type in ('expense', 'revenue'):
line_code_taxes = [x.code.id for x in line.tax_lines]
for tax in line.account.taxes:
if move.journal.type == 'revenue':
if line.debit:
2013-04-20 17:03:33 +02:00
base_id = (tax.credit_note_base_code.id
if tax.credit_note_base_code else None)
code_id = (tax.credit_note_tax_code.id
if tax.credit_note_tax_code else None)
account_id = (tax.credit_note_account.id
if tax.credit_note_account else None)
else:
2013-04-20 17:03:33 +02:00
base_id = (tax.invoice_base_code.id
if tax.invoice_base_code else None)
code_id = (tax.invoice_tax_code.id
if tax.invoice_tax_code else None)
account_id = (tax.invoice_account.id
if tax.invoice_account else None)
else:
if line.debit:
2013-04-20 17:03:33 +02:00
base_id = (tax.invoice_base_code.id
if tax.invoice_base_code else None)
code_id = (tax.invoice_tax_code.id
if tax.invoice_tax_code else None)
account_id = (tax.invoice_account.id
if tax.invoice_account else None)
else:
2013-04-20 17:03:33 +02:00
base_id = (tax.credit_note_base_code.id
if tax.credit_note_base_code else None)
code_id = (tax.credit_note_tax_code.id
if tax.credit_note_tax_code else None)
account_id = (tax.credit_note_account.id
if tax.credit_note_account else None)
if base_id in line_code_taxes or not base_id:
2012-03-27 10:43:03 +02:00
taxes.setdefault((account_id, code_id, tax.id), None)
for tax_line in line.tax_lines:
2013-02-16 18:54:35 +01:00
taxes[
(line.account.id, tax_line.code.id, tax_line.tax.id)
] = True
if not line.tax_lines:
no_code_taxes.append(line.account.id)
for no_code_account_id in no_code_taxes:
for (account_id, code_id, tax_id), test in \
taxes.iteritems():
if (not test
and not code_id
and no_code_account_id == account_id):
taxes[(account_id, code_id, tax_id)] = True
2008-03-17 18:32:06 +01:00
if 'account' in fields:
2012-12-16 23:34:18 +01:00
account = None
if total >= Decimal('0.0'):
2012-12-16 23:34:18 +01:00
if move.journal.credit_account:
account = move.journal.credit_account
2008-03-17 18:32:06 +01:00
else:
2012-12-16 23:34:18 +01:00
if move.journal.debit_account:
account = move.journal.debit_account
if account:
values['account'] = account.id
values['account.rec_name'] = account.rec_name
else:
values['account'] = None
2008-03-17 18:32:06 +01:00
if ('debit' in fields) or ('credit' in fields):
values['debit'] = total < 0 and - total or Decimal(0)
values['credit'] = total > 0 and total or Decimal(0)
if move.journal.type in ('expense', 'revenue'):
for account_id, code_id, tax_id in taxes:
if taxes[(account_id, code_id, tax_id)]:
continue
for line in move.lines:
if move.journal.type == 'revenue':
if line.debit:
2008-10-03 17:11:53 +02:00
key = 'credit_note'
else:
key = 'invoice'
else:
if line.debit:
key = 'invoice'
else:
2008-10-03 17:11:53 +02:00
key = 'credit_note'
line_amount = Decimal('0.0')
tax_amount = Decimal('0.0')
2012-09-24 22:17:29 +02:00
for tax_line in Tax.compute(line.account.taxes,
2010-08-19 22:02:00 +02:00
line.debit or line.credit, 1):
2013-04-20 17:03:33 +02:00
tax_account = getattr(tax_line['tax'],
key + '_account')
tax_code = getattr(tax_line['tax'], key + '_tax_code')
if ((tax_account.id if tax_account
else line.account.id) == account_id
and (tax_code.id if tax_code else None
2013-02-16 18:54:35 +01:00
== code_id)
and tax_line['tax'].id == tax_id):
if line.debit:
line_amount += tax_line['amount']
else:
line_amount -= tax_line['amount']
tax_amount += tax_line['amount'] * \
2013-02-16 18:54:35 +01:00
tax_line['tax'][key + '_tax_sign']
2012-09-24 22:17:29 +02:00
line_amount = line.account.company.currency.round(
line_amount)
tax_amount = line.account.company.currency.round(
tax_amount)
if ('debit' in fields):
values['debit'] = line_amount > Decimal('0.0') \
2013-02-16 18:54:35 +01:00
and line_amount or Decimal('0.0')
if ('credit' in fields):
values['credit'] = line_amount < Decimal('0.0') \
2013-02-16 18:54:35 +01:00
and - line_amount or Decimal('0.0')
2013-04-20 17:03:33 +02:00
if 'account' in fields and account_id:
values['account'] = account_id
2012-09-24 22:17:29 +02:00
values['account.rec_name'] = Account(
account_id).rec_name
if 'tax_lines' in fields and code_id:
values['tax_lines'] = [
{
'amount': tax_amount,
'currency_digits': line.currency_digits,
'code': code_id,
2012-09-24 22:17:29 +02:00
'code.rec_name': TaxCode(code_id).rec_name,
'tax': tax_id,
2012-09-24 22:17:29 +02:00
'tax.rec_name': Tax(tax_id).rec_name,
},
]
2008-03-17 18:32:06 +01:00
return values
2012-09-24 22:17:29 +02:00
@classmethod
def get_currency_digits(cls, lines, names):
digits = {}
for line in lines:
2008-09-22 18:23:05 +02:00
for name in names:
2012-09-24 22:17:29 +02:00
digits.setdefault(name, {})
digits[name].setdefault(line.id, 2)
2008-09-22 18:23:05 +02:00
if name == 'currency_digits':
2012-09-24 22:17:29 +02:00
digits[name][line.id] = line.account.currency_digits
2008-09-22 18:23:05 +02:00
elif name == 'second_currency_digits':
2012-04-03 20:06:15 +02:00
second_currency = line.account.second_currency
if second_currency:
2012-09-24 22:17:29 +02:00
digits[name][line.id] = second_currency.digits
return digits
2008-09-22 18:23:05 +02:00
2012-09-29 15:33:21 +02:00
@classmethod
def get_origin(cls):
Move = Pool().get('account.move')
return Move.get_origin()
2014-01-26 14:28:46 +01:00
@fields.depends('account', 'debit', 'credit', 'tax_lines', 'journal',
'move')
2012-09-24 22:17:29 +02:00
def on_change_debit(self):
Journal = Pool().get('account.journal')
if self.journal or Transaction().context.get('journal'):
journal = self.journal or Journal(Transaction().context['journal'])
if journal.type in ('expense', 'revenue'):
2014-10-29 20:39:03 +01:00
self._compute_tax_lines(journal.type)
2012-09-24 22:17:29 +02:00
if self.debit:
2014-10-29 20:39:03 +01:00
self.credit = Decimal('0.0')
2014-01-26 14:28:46 +01:00
@fields.depends('account', 'debit', 'credit', 'tax_lines', 'journal',
'move')
2012-09-24 22:17:29 +02:00
def on_change_credit(self):
Journal = Pool().get('account.journal')
if self.journal or Transaction().context.get('journal'):
journal = self.journal or Journal(Transaction().context['journal'])
if journal.type in ('expense', 'revenue'):
2014-10-29 20:39:03 +01:00
self._compute_tax_lines(journal.type)
2012-09-24 22:17:29 +02:00
if self.credit:
2014-10-29 20:39:03 +01:00
self.debit = Decimal('0.0')
2014-01-26 14:28:46 +01:00
@fields.depends('account', 'debit', 'credit', 'tax_lines', 'journal',
'move')
2012-09-24 22:17:29 +02:00
def on_change_account(self):
Journal = Pool().get('account.journal')
2008-09-22 18:23:05 +02:00
2010-08-19 22:02:00 +02:00
if Transaction().context.get('journal'):
2012-09-24 22:17:29 +02:00
journal = Journal(Transaction().context['journal'])
if journal.type in ('expense', 'revenue'):
2014-10-29 20:39:03 +01:00
self._compute_tax_lines(journal.type)
2012-09-24 22:17:29 +02:00
if self.account:
2014-10-29 20:39:03 +01:00
self.currency_digits = self.account.currency_digits
2012-09-24 22:17:29 +02:00
if self.account.second_currency:
2014-10-29 20:39:03 +01:00
self.second_currency_digits = \
2012-09-24 22:17:29 +02:00
self.account.second_currency.digits
if not self.account.party_required:
self.party = None
@fields.depends('account')
def on_change_with_party_required(self, name=None):
if self.account:
return self.account.party_required
return False
2012-09-24 22:17:29 +02:00
def _compute_tax_lines(self, journal_type):
2011-07-03 13:18:40 +02:00
pool = Pool()
2012-09-24 22:17:29 +02:00
Tax = pool.get('account.tax')
TaxLine = pool.get('account.tax.line')
2012-09-24 22:17:29 +02:00
if self.move:
2014-12-02 12:54:29 +01:00
# Only for first line
2014-10-29 20:39:03 +01:00
return
tax_lines = []
2012-09-24 22:17:29 +02:00
if self.account:
debit = self.debit or Decimal('0.0')
2013-02-16 18:54:35 +01:00
credit = self.credit or Decimal('0.0')
2012-09-24 22:17:29 +02:00
for tax in self.account.taxes:
if journal_type == 'revenue':
if debit:
2008-10-03 17:11:53 +02:00
key = 'credit_note'
else:
key = 'invoice'
else:
if debit:
key = 'invoice'
else:
2008-10-03 17:11:53 +02:00
key = 'credit_note'
base_amounts = {}
2012-09-24 22:17:29 +02:00
for tax_line in Tax.compute(self.account.taxes,
2010-08-19 22:02:00 +02:00
debit or credit, 1):
2013-04-20 17:03:33 +02:00
code = getattr(tax_line['tax'], key + '_base_code')
code_id = code.id if code else None
if not code_id:
continue
tax_id = tax_line['tax'].id
base_amounts.setdefault((code_id, tax_id), Decimal('0.0'))
base_amounts[code_id, tax_id] += tax_line['base'] * \
2013-04-20 17:03:33 +02:00
getattr(tax_line['tax'], key + '_tax_sign')
for code_id, tax_id in base_amounts:
if not base_amounts[code_id, tax_id]:
continue
2014-10-29 20:39:03 +01:00
tax_line = TaxLine(**TaxLine.default_get(
TaxLine._fields.keys()))
tax_line.amount = base_amounts[code_id, tax_id],
tax_line.currency_digits = self.account.currency_digits
tax_line.code = code_id
tax_line.tax = tax_id
tax_lines.append(tax_line)
self.tax_lines = tax_lines
2014-01-26 14:28:46 +01:00
@fields.depends('move', 'party', 'account', 'debit', 'credit', 'journal')
2012-09-24 22:17:29 +02:00
def on_change_party(self):
Journal = Pool().get('account.journal')
2010-08-19 22:02:00 +02:00
cursor = Transaction().cursor
2012-09-24 22:17:29 +02:00
if (not self.party) or self.account:
2014-10-29 20:39:03 +01:00
return
2012-09-24 22:17:29 +02:00
if not self.party.account_receivable \
or not self.party.account_payable:
2014-10-29 20:39:03 +01:00
return
2012-09-24 22:17:29 +02:00
if self.party and (not self.debit) and (not self.credit):
2013-09-06 22:33:47 +02:00
type_name = self.__class__.debit.sql_type().base
table = self.__table__()
column = Coalesce(Sum(Coalesce(table.debit, 0)
- Coalesce(table.credit, 0)), 0).cast(type_name)
where = ((table.reconciliation == Null)
2013-09-06 22:33:47 +02:00
& (table.party == self.party.id))
cursor.execute(*table.select(column,
where=where
& (table.account == self.party.account_receivable.id)))
2008-03-19 22:13:51 +01:00
amount = cursor.fetchone()[0]
2009-09-22 16:08:11 +02:00
# SQLite uses float for SUM
if not isinstance(amount, Decimal):
2011-10-28 10:28:26 +02:00
amount = Decimal(str(amount))
2012-09-24 22:17:29 +02:00
if not self.party.account_receivable.currency.is_zero(amount):
if amount > Decimal('0.0'):
2014-10-29 20:39:03 +01:00
self.credit = \
2012-09-24 22:17:29 +02:00
self.party.account_receivable.currency.round(amount)
2014-10-29 20:39:03 +01:00
self.debit = Decimal('0.0')
2008-03-19 22:13:51 +01:00
else:
2014-10-29 20:39:03 +01:00
self.credit = Decimal('0.0')
self.debit = \
2012-09-24 22:17:29 +02:00
- self.party.account_receivable.currency.round(amount)
2014-10-29 20:39:03 +01:00
self.account = self.party.account_receivable
2008-03-19 22:13:51 +01:00
else:
2013-09-06 22:33:47 +02:00
cursor.execute(*table.select(column,
where=where
& (table.account == self.party.account_payable.id)))
2008-03-19 22:13:51 +01:00
amount = cursor.fetchone()[0]
2012-09-24 22:17:29 +02:00
if not self.party.account_payable.currency.is_zero(amount):
if amount > Decimal('0.0'):
2014-10-29 20:39:03 +01:00
self.credit = \
2012-09-24 22:17:29 +02:00
self.party.account_payable.currency.round(amount)
2014-10-29 20:39:03 +01:00
self.debit = Decimal('0.0')
2008-03-19 22:13:51 +01:00
else:
2014-10-29 20:39:03 +01:00
self.credit = Decimal('0.0')
self.debit = \
2012-09-24 22:17:29 +02:00
- self.party.account_payable.currency.round(amount)
2014-10-29 20:39:03 +01:00
self.account = self.party.account_payable
2008-03-19 22:13:51 +01:00
2012-09-24 22:17:29 +02:00
if self.party and self.debit:
if self.debit > Decimal('0.0'):
2014-10-29 20:39:03 +01:00
if not self.account:
self.account = self.party.account_receivable
2008-03-19 22:13:51 +01:00
else:
2014-10-29 20:39:03 +01:00
if not self.account:
self.account = self.party.account_payable
2008-03-19 22:13:51 +01:00
2012-09-24 22:17:29 +02:00
if self.party and self.credit:
if self.credit > Decimal('0.0'):
2014-10-29 20:39:03 +01:00
if not self.account:
self.account = self.party.account_payable
2008-03-19 22:13:51 +01:00
else:
2014-10-29 20:39:03 +01:00
if not self.account:
self.account = self.party.account_receivable
2008-03-19 22:13:51 +01:00
2012-11-22 23:24:24 +01:00
journal = None
if self.journal:
journal = self.journal
elif Transaction().context.get('journal'):
journal = Journal(Transaction().context.get('journal'))
2012-09-24 22:17:29 +02:00
if journal and self.party:
if journal.type == 'revenue':
2014-10-29 20:39:03 +01:00
if not self.account:
self.account = self.party.account_receivable
elif journal.type == 'expense':
2014-10-29 20:39:03 +01:00
if not self.account:
self.account = self.party.account_payable
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
def get_move_field(self, name):
2014-09-13 15:26:13 +02:00
field = getattr(self.__class__, name)
2014-02-03 18:12:16 +01:00
if name.startswith('move_'):
name = name[5:]
2014-09-13 15:26:13 +02:00
value = getattr(self.move, name)
if isinstance(value, ModelSQL):
if field._type == 'reference':
return str(value)
return value.id
return value
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def set_move_field(cls, lines, name, value):
2014-02-03 18:12:16 +01:00
if name.startswith('move_'):
name = name[5:]
if not value:
return
2012-09-24 22:17:29 +02:00
Move = Pool().get('account.move')
Move.write([line.move for line in lines], {
name: value,
})
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def search_move_field(cls, name, clause):
2014-02-03 18:12:16 +01:00
if name.startswith('move_'):
name = name[5:]
return [('move.' + name,) + tuple(clause[1:])]
2013-09-17 10:49:25 +02:00
def _order_move_field(name):
def order_field(tables):
pool = Pool()
Move = pool.get('account.move')
field = Move._fields[name]
table, _ = tables[None]
move_tables = tables.get('move')
if move_tables is None:
move = Move.__table__()
move_tables = {
None: (move, move.id == table.move),
}
tables['move'] = move_tables
return field.convert_order(name, move_tables, Move)
return staticmethod(order_field)
order_journal = _order_move_field('journal')
order_period = _order_move_field('period')
order_date = _order_move_field('date')
order_origin = _order_move_field('origin')
order_move_state = _order_move_field('state')
2014-09-19 14:24:46 +02:00
def get_rec_name(self, name):
if self.debit > self.credit:
return self.account.rec_name
else:
return '(%s)' % self.account.rec_name
@classmethod
def search_rec_name(cls, name, clause):
return [('account.rec_name',) + tuple(clause[1:])]
2012-09-24 22:17:29 +02:00
@classmethod
2013-09-06 22:33:47 +02:00
def query_get(cls, table):
2008-03-17 18:32:06 +01:00
'''
2008-09-19 15:00:26 +02:00
Return SQL clause and fiscal years for account move line
depending of the context.
2013-09-06 22:33:47 +02:00
table is the SQL instance of account.move.line table
2008-03-17 18:32:06 +01:00
'''
2013-09-06 22:33:47 +02:00
pool = Pool()
FiscalYear = pool.get('account.fiscalyear')
Move = pool.get('account.move')
Period = pool.get('account.period')
move = Move.__table__()
period = Period.__table__()
2010-08-19 22:02:00 +02:00
if Transaction().context.get('date'):
2012-09-24 22:17:29 +02:00
fiscalyears = FiscalYear.search([
('start_date', '<=', Transaction().context['date']),
('end_date', '>=', Transaction().context['date']),
2014-12-03 16:13:08 +01:00
('company', '=', Transaction().context.get('company')),
2012-09-24 22:17:29 +02:00
], limit=1)
2012-09-24 22:17:29 +02:00
fiscalyear_id = fiscalyears and fiscalyears[0].id or 0
2010-08-19 22:02:00 +02:00
if Transaction().context.get('posted'):
2013-09-06 22:33:47 +02:00
return ((table.state != 'draft')
& table.move.in_(move.join(period,
condition=move.period == period.id
).select(move.id,
where=(period.fiscalyear == fiscalyear_id)
2013-09-06 22:33:47 +02:00
& (move.date <= Transaction().context['date'])
& (move.state == 'posted'))),
[f.id for f in fiscalyears])
else:
2013-09-06 22:33:47 +02:00
return ((table.state != 'draft')
& table.move.in_(move.join(period,
condition=move.period == period.id
).select(move.id,
where=(period.fiscalyear == fiscalyear_id)
& (move.date <= Transaction().context['date']))),
[f.id for f in fiscalyears])
2010-08-19 22:02:00 +02:00
if Transaction().context.get('periods'):
if Transaction().context.get('fiscalyear'):
2013-09-06 22:33:47 +02:00
fiscalyear_ids = [Transaction().context['fiscalyear']]
else:
fiscalyear_ids = []
2010-08-19 22:02:00 +02:00
if Transaction().context.get('posted'):
2013-09-06 22:33:47 +02:00
return ((table.state != 'draft')
& table.move.in_(
move.select(move.id,
2013-11-14 13:34:36 +01:00
where=move.period.in_(
2013-09-06 22:33:47 +02:00
Transaction().context['periods'])
2013-11-14 13:34:36 +01:00
& (move.state == 'posted'))),
2013-09-06 22:33:47 +02:00
fiscalyear_ids)
2008-03-28 17:25:14 +01:00
else:
2013-09-06 22:33:47 +02:00
return ((table.state != 'draft')
& table.move.in_(
move.select(move.id,
where=move.period.in_(
Transaction().context['periods']))),
fiscalyear_ids)
2008-03-17 18:32:06 +01:00
else:
if not Transaction().context.get('fiscalyear'):
2012-09-24 22:17:29 +02:00
fiscalyears = FiscalYear.search([
('state', '=', 'open'),
2014-12-03 16:13:08 +01:00
('company', '=', Transaction().context.get('company')),
])
2013-09-06 22:33:47 +02:00
fiscalyear_ids = [f.id for f in fiscalyears] or [0]
else:
2013-09-06 22:33:47 +02:00
fiscalyear_ids = [Transaction().context.get('fiscalyear')]
2010-08-19 22:02:00 +02:00
if Transaction().context.get('posted'):
2013-09-06 22:33:47 +02:00
return ((table.state != 'draft')
& table.move.in_(
move.select(move.id,
where=move.period.in_(
period.select(period.id,
where=period.fiscalyear.in_(
fiscalyear_ids)))
2013-11-14 13:34:36 +01:00
& (move.state == 'posted'))),
2013-09-06 22:33:47 +02:00
fiscalyear_ids)
2008-03-28 17:25:14 +01:00
else:
2013-09-06 22:33:47 +02:00
return ((table.state != 'draft')
& table.move.in_(
move.select(move.id,
where=move.period.in_(
period.select(period.id,
where=period.fiscalyear.in_(
fiscalyear_ids))))),
fiscalyear_ids)
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def on_write(cls, lines):
return list(set(l.id for line in lines for l in line.move.lines))
2008-03-17 18:32:06 +01:00
@classmethod
def validate(cls, lines):
super(Line, cls).validate(lines)
for line in lines:
line.check_account()
2012-09-24 22:17:29 +02:00
def check_account(self):
if self.account.kind in ('view',):
self.raise_user_error('move_view_account', (
self.account.rec_name,))
2012-09-24 22:17:29 +02:00
if not self.account.active:
self.raise_user_error('move_inactive_account', (
self.account.rec_name,))
if bool(self.party) != bool(self.account.party_required):
error = 'party_set' if self.party else 'party_required'
self.raise_user_error(error, self.rec_name)
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def check_journal_period_modify(cls, period, journal):
2008-03-17 18:32:06 +01:00
'''
Check if the lines can be modified or created for the journal - period
and if there is no journal - period, create it
'''
2012-09-24 22:17:29 +02:00
JournalPeriod = Pool().get('account.journal.period')
journal_periods = JournalPeriod.search([
('journal', '=', journal.id),
('period', '=', period.id),
], limit=1)
if journal_periods:
journal_period, = journal_periods
2008-03-17 18:32:06 +01:00
if journal_period.state == 'close':
cls.raise_user_error('add_modify_closed_journal_period', (
journal_period.rec_name,))
2008-03-17 18:32:06 +01:00
else:
2013-01-06 22:18:13 +01:00
JournalPeriod.create([{
'name': journal.name + ' - ' + period.name,
'journal': journal.id,
'period': period.id,
}])
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
2014-09-21 12:12:26 +02:00
def check_modify(cls, lines, modified_fields=None):
2008-03-17 18:32:06 +01:00
'''
Check if the lines can be modified
'''
2014-09-21 12:12:26 +02:00
if (modified_fields is not None
and modified_fields <= cls._check_modify_exclude):
return
2008-03-17 18:32:06 +01:00
journal_period_done = []
2012-09-24 22:17:29 +02:00
for line in lines:
2008-03-17 18:32:06 +01:00
if line.move.state == 'posted':
cls.raise_user_error('modify_posted_move', (
line.move.rec_name,))
2008-03-17 18:32:06 +01:00
journal_period = (line.journal.id, line.period.id)
if journal_period not in journal_period_done:
2012-09-24 22:17:29 +02:00
cls.check_journal_period_modify(line.period,
line.journal)
2008-03-17 18:32:06 +01:00
journal_period_done.append(journal_period)
@classmethod
def check_reconciliation(cls, lines, modified_fields=None):
if (modified_fields is not None
and not modified_fields & cls._reconciliation_modify_disallow):
return
for line in lines:
if line.reconciliation:
cls.raise_user_error('modify_reconciled', line.rec_name)
2012-09-24 22:17:29 +02:00
@classmethod
def delete(cls, lines):
Move = Pool().get('account.move')
cls.check_modify(lines)
cls.check_reconciliation(lines)
2012-09-24 22:17:29 +02:00
moves = [x.move for x in lines]
super(Line, cls).delete(lines)
Move.validate_move(moves)
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
2013-12-19 14:44:33 +01:00
def write(cls, *args):
2012-09-24 22:17:29 +02:00
Move = Pool().get('account.move')
2013-12-19 14:44:33 +01:00
actions = iter(args)
args = []
moves = []
all_lines = []
for lines, values in zip(actions, actions):
2014-09-21 12:12:26 +02:00
cls.check_modify(lines, set(values.keys()))
cls.check_reconciliation(lines, set(values.keys()))
2013-12-19 14:44:33 +01:00
moves.extend((x.move for x in lines))
all_lines.extend(lines)
args.extend((lines, values))
super(Line, cls).write(*args)
2010-08-19 22:02:00 +02:00
Transaction().timestamp = {}
2013-12-19 14:44:33 +01:00
Move.validate_move(list(set(l.move for l in all_lines) | set(moves)))
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
pool = Pool()
Journal = pool.get('account.journal')
Move = pool.get('account.move')
2013-01-06 22:18:13 +01:00
vlist = [x.copy() for x in vlist]
for vals in vlist:
if not vals.get('move'):
2013-01-06 22:18:13 +01:00
journal_id = (vals.get('journal')
or Transaction().context.get('journal'))
if not journal_id:
cls.raise_user_error('no_journal')
journal = Journal(journal_id)
if not vals.get('move'):
vals['move'] = Move.create([{
'period': (vals.get('period')
or Transaction().context.get('period')),
'journal': journal_id,
'date': vals.get('date'),
}])[0].id
else:
# prevent computation of default date
vals.setdefault('date', None)
lines = super(Line, cls).create(vlist)
2013-02-16 18:54:35 +01:00
period_and_journals = set((line.period, line.journal)
2013-01-06 22:18:13 +01:00
for line in lines)
for period, journal in period_and_journals:
cls.check_journal_period_modify(period, journal)
Move.validate_move(list(set(line.move for line in lines)))
2013-01-06 22:18:13 +01:00
return lines
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def copy(cls, lines, default=None):
if default is None:
default = {}
if 'move' not in default:
2012-03-27 10:43:03 +02:00
default['move'] = None
if 'reconciliation' not in default:
2012-03-27 10:43:03 +02:00
default['reconciliation'] = None
2012-09-24 22:17:29 +02:00
return super(Line, cls).copy(lines, default=default)
2012-09-24 22:17:29 +02:00
@classmethod
def view_header_get(cls, value, view_type='form'):
JournalPeriod = Pool().get('account.journal.period')
2010-08-19 22:02:00 +02:00
if (not Transaction().context.get('journal')
or not Transaction().context.get('period')):
2009-02-02 13:09:08 +01:00
return value
2012-09-24 22:17:29 +02:00
journal_periods = JournalPeriod.search([
('journal', '=', Transaction().context['journal']),
('period', '=', Transaction().context['period']),
], limit=1)
if not journal_periods:
return value
2012-09-24 22:17:29 +02:00
journal_period, = journal_periods
return value + ': ' + journal_period.rec_name
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def fields_view_get(cls, view_id=None, view_type='form'):
Journal = Pool().get('account.journal')
result = super(Line, cls).fields_view_get(view_id=view_id,
view_type=view_type)
2010-08-19 22:02:00 +02:00
if view_type == 'tree' and 'journal' in Transaction().context:
2012-09-24 22:17:29 +02:00
title = cls.view_header_get('', view_type=view_type)
journal = Journal(Transaction().context['journal'])
2008-03-17 18:32:06 +01:00
if not journal.view:
return result
xml = '<?xml version="1.0"?>\n' \
2013-02-16 18:54:35 +01:00
'<tree string="%s" editable="top" on_write="on_write" ' \
'colors="red:state==\'draft\'">\n' % title
fields = set()
2008-03-17 18:32:06 +01:00
for column in journal.view.columns:
fields.add(column.field.name)
2008-03-17 18:32:06 +01:00
attrs = []
if column.field.name == 'debit':
2008-03-18 17:30:54 +01:00
attrs.append('sum="Debit"')
2008-03-17 18:32:06 +01:00
elif column.field.name == 'credit':
2008-03-18 17:30:54 +01:00
attrs.append('sum="Credit"')
2008-03-17 18:32:06 +01:00
if column.readonly:
attrs.append('readonly="1"')
if column.required:
attrs.append('required="1"')
else:
attrs.append('required="0"')
2012-04-03 20:06:15 +02:00
xml += ('<field name="%s" %s/>\n'
% (column.field.name, ' '.join(attrs)))
2012-09-24 22:17:29 +02:00
for depend in getattr(cls, column.field.name).depends:
fields.add(depend)
fields.add('state')
2008-03-17 18:32:06 +01:00
xml += '</tree>'
result['arch'] = xml
2012-09-24 22:17:29 +02:00
result['fields'] = cls.fields_get(fields_names=list(fields))
2008-03-17 18:32:06 +01:00
return result
2012-09-24 22:17:29 +02:00
@classmethod
2013-11-28 23:26:15 +01:00
def reconcile(cls, lines, journal=None, date=None, account=None,
description=None):
2011-07-03 13:18:40 +02:00
pool = Pool()
2012-09-24 22:17:29 +02:00
Move = pool.get('account.move')
Reconciliation = pool.get('account.move.reconciliation')
Period = pool.get('account.period')
Date = pool.get('ir.date')
2012-09-24 22:17:29 +02:00
for line in lines:
2008-11-13 10:48:40 +01:00
if line.reconciliation:
2012-09-24 22:17:29 +02:00
cls.raise_user_error('already_reconciled',
2013-02-03 15:06:55 -03:00
error_args=(line.move.number, line.id,))
2008-11-13 10:48:40 +01:00
2012-09-24 22:17:29 +02:00
lines = lines[:]
2013-12-20 13:59:46 +01:00
reconcile_account = None
reconcile_party = None
2013-12-20 13:59:46 +01:00
amount = Decimal('0.0')
for line in lines:
amount += line.debit - line.credit
if not reconcile_account:
reconcile_account = line.account
if not reconcile_party:
reconcile_party = line.party
2013-12-20 13:59:46 +01:00
amount = reconcile_account.currency.round(amount)
if not account and journal:
if amount >= 0:
account = journal.debit_account
else:
account = journal.credit_account
2012-09-24 22:17:29 +02:00
if journal and account:
if not date:
2012-09-24 22:17:29 +02:00
date = Date.today()
2013-01-26 15:11:34 +01:00
period_id = Period.find(reconcile_account.company.id, date=date)
2013-01-06 22:18:13 +01:00
move, = Move.create([{
'journal': journal.id,
'period': period_id,
'date': date,
2013-11-28 23:26:15 +01:00
'description': description,
2013-01-06 22:18:13 +01:00
'lines': [
('create', [{
2013-01-26 15:11:34 +01:00
'account': reconcile_account.id,
'party': (reconcile_party.id
if reconcile_party else None),
2013-02-16 18:54:35 +01:00
'debit': (amount < Decimal('0.0')
2013-01-06 22:18:13 +01:00
and - amount or Decimal('0.0')),
2013-02-16 18:54:35 +01:00
'credit': (amount > Decimal('0.0')
2013-01-06 22:18:13 +01:00
and amount or Decimal('0.0')),
}, {
'account': account.id,
'party': (reconcile_party.id
if (account.party_required and
reconcile_party)
else None),
2013-02-16 18:54:35 +01:00
'debit': (amount > Decimal('0.0')
2013-01-06 22:18:13 +01:00
and amount or Decimal('0.0')),
2013-02-16 18:54:35 +01:00
'credit': (amount < Decimal('0.0')
2013-01-06 22:18:13 +01:00
and - amount or Decimal('0.0')),
}]),
],
}])
2012-09-24 22:17:29 +02:00
lines += cls.search([
2013-02-16 18:54:35 +01:00
('move', '=', move.id),
('account', '=', reconcile_account.id),
('debit', '=', amount < Decimal('0.0') and - amount
or Decimal('0.0')),
2013-02-16 18:54:35 +01:00
('credit', '=', amount > Decimal('0.0') and amount
or Decimal('0.0')),
2013-02-16 18:54:35 +01:00
], limit=1)
2013-01-06 22:18:13 +01:00
return Reconciliation.create([{
'lines': [('add', [x.id for x in lines])],
}])[0]
2008-03-17 18:32:06 +01:00
2009-02-28 16:57:10 +01:00
class OpenJournalAsk(ModelView):
'Open Journal Ask'
2012-09-24 22:17:29 +02:00
__name__ = 'account.move.open_journal.ask'
2008-03-17 18:32:06 +01:00
journal = fields.Many2One('account.journal', 'Journal', required=True)
period = fields.Many2One('account.period', 'Period', required=True,
2011-08-21 11:20:29 +02:00
domain=[
('state', '!=', 'close'),
('fiscalyear.company.id', '=',
Eval('context', {}).get('company', 0)),
2010-01-20 10:56:33 +01:00
])
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@staticmethod
def default_period():
Period = Pool().get('account.period')
return Period.find(Transaction().context.get('company'),
2010-08-19 22:02:00 +02:00
exception=False)
2008-03-17 18:32:06 +01:00
class OpenJournal(Wizard):
'Open Journal'
2012-09-24 22:17:29 +02:00
__name__ = 'account.move.open_journal'
2012-01-02 17:54:33 +01:00
start = StateTransition()
ask = StateView('account.move.open_journal.ask',
'account.open_journal_ask_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Open', 'open_', 'tryton-ok', default=True),
])
open_ = StateAction('account.act_move_line_form')
2012-09-24 22:17:29 +02:00
def transition_start(self):
2012-04-03 20:06:15 +02:00
if (Transaction().context.get('active_model', '')
== 'account.journal.period'
2012-01-02 17:54:33 +01:00
and Transaction().context.get('active_id')):
return 'open_'
return 'ask'
2012-09-24 22:17:29 +02:00
def default_ask(self, fields):
JournalPeriod = Pool().get('account.journal.period')
2012-01-02 17:54:33 +01:00
if (Transaction().context.get('active_model', '') ==
'account.journal.period'
and Transaction().context.get('active_id')):
2012-09-24 22:17:29 +02:00
journal_period = JournalPeriod(Transaction().context['active_id'])
return {
'journal': journal_period.journal.id,
'period': journal_period.period.id,
2012-09-24 22:17:29 +02:00
}
return {}
2012-09-24 22:17:29 +02:00
def do_open_(self, action):
JournalPeriod = Pool().get('account.journal.period')
2012-01-02 17:54:33 +01:00
if (Transaction().context.get('active_model', '') ==
'account.journal.period'
and Transaction().context.get('active_id')):
2012-09-24 22:17:29 +02:00
journal_period = JournalPeriod(Transaction().context['active_id'])
journal = journal_period.journal
period = journal_period.period
else:
2012-09-24 22:17:29 +02:00
journal = self.ask.journal
period = self.ask.period
journal_periods = JournalPeriod.search([
('journal', '=', journal.id),
('period', '=', period.id),
], limit=1)
if not journal_periods:
2013-01-06 22:18:13 +01:00
journal_period, = JournalPeriod.create([{
'name': journal.name + ' - ' + period.name,
'journal': journal.id,
'period': period.id,
}])
2012-01-02 17:54:33 +01:00
else:
2012-09-24 22:17:29 +02:00
journal_period, = journal_periods
2012-01-02 17:54:33 +01:00
action['name'] += ' - %s' % journal_period.rec_name
action['pyson_domain'] = PYSONEncoder().encode([
2012-09-24 22:17:29 +02:00
('journal', '=', journal.id),
('period', '=', period.id),
2012-01-02 17:54:33 +01:00
])
action['pyson_context'] = PYSONEncoder().encode({
2012-09-24 22:17:29 +02:00
'journal': journal.id,
'period': period.id,
2008-07-29 12:14:16 +02:00
})
2012-01-02 17:54:33 +01:00
return action, {}
2012-09-24 22:17:29 +02:00
def transition_open_(self):
2012-01-02 17:54:33 +01:00
return 'end'
2008-03-17 18:32:06 +01:00
2008-03-18 13:20:12 +01:00
class OpenAccount(Wizard):
'Open Account'
2012-09-24 22:17:29 +02:00
__name__ = 'account.move.open_account'
2012-01-02 17:54:33 +01:00
start_state = 'open_'
open_ = StateAction('account.act_move_line_form')
2008-03-18 13:20:12 +01:00
2012-09-24 22:17:29 +02:00
def do_open_(self, action):
FiscalYear = Pool().get('account.fiscalyear')
2008-03-18 13:20:12 +01:00
2010-08-19 22:02:00 +02:00
if not Transaction().context.get('fiscalyear'):
2012-09-24 22:17:29 +02:00
fiscalyears = FiscalYear.search([
('state', '=', 'open'),
])
2008-03-18 13:20:12 +01:00
else:
2012-09-24 22:17:29 +02:00
fiscalyears = [FiscalYear(Transaction().context['fiscalyear'])]
2008-03-18 13:20:12 +01:00
2012-09-24 22:17:29 +02:00
periods = [p for f in fiscalyears for p in f.periods]
2008-03-18 13:20:12 +01:00
2012-01-02 17:54:33 +01:00
action['pyson_domain'] = [
2012-09-24 22:17:29 +02:00
('period', 'in', [p.id for p in periods]),
2012-01-02 17:54:33 +01:00
('account', '=', Transaction().context['active_id']),
2008-03-28 17:39:54 +01:00
]
2010-08-19 22:02:00 +02:00
if Transaction().context.get('posted'):
2012-01-02 17:54:33 +01:00
action['pyson_domain'].append(('move.state', '=', 'posted'))
2012-08-16 15:55:45 +02:00
if Transaction().context.get('date'):
action['pyson_domain'].append(('move.date', '<=',
Transaction().context['date']))
2012-01-02 17:54:33 +01:00
action['pyson_domain'] = PYSONEncoder().encode(action['pyson_domain'])
action['pyson_context'] = PYSONEncoder().encode({
2010-08-19 22:02:00 +02:00
'fiscalyear': Transaction().context.get('fiscalyear'),
2010-01-20 10:56:33 +01:00
})
2012-01-02 17:54:33 +01:00
return action, {}
2008-03-18 13:20:12 +01:00
2008-03-18 23:40:57 +01:00
2009-02-28 16:57:10 +01:00
class ReconcileLinesWriteOff(ModelView):
2008-03-19 22:13:51 +01:00
'Reconcile Lines Write-Off'
2012-09-24 22:17:29 +02:00
__name__ = 'account.move.reconcile_lines.writeoff'
2013-12-20 13:59:46 +01:00
journal = fields.Many2One('account.journal', 'Journal', required=True,
domain=[
('type', '=', 'write-off'),
])
date = fields.Date('Date', required=True)
2013-08-21 18:19:26 +02:00
amount = fields.Numeric('Amount', digits=(16, Eval('currency_digits', 2)),
readonly=True, depends=['currency_digits'])
currency_digits = fields.Integer('Currency Digits', readonly=True)
2013-11-28 23:26:15 +01:00
description = fields.Char('Description')
2008-03-19 22:13:51 +01:00
2012-09-24 22:17:29 +02:00
@staticmethod
def default_date():
Date = Pool().get('ir.date')
return Date.today()
2008-03-19 22:13:51 +01:00
class ReconcileLines(Wizard):
'Reconcile Lines'
2012-09-24 22:17:29 +02:00
__name__ = 'account.move.reconcile_lines'
2012-01-02 17:54:33 +01:00
start = StateTransition()
writeoff = StateView('account.move.reconcile_lines.writeoff',
'account.reconcile_lines_writeoff_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Reconcile', 'reconcile', 'tryton-ok', default=True),
])
reconcile = StateTransition()
2013-08-21 18:19:26 +02:00
def get_writeoff(self):
"Return writeoff amount and company"
2012-09-24 22:17:29 +02:00
Line = Pool().get('account.move.line')
2008-03-19 22:13:51 +01:00
company = None
amount = Decimal('0.0')
2012-09-24 22:17:29 +02:00
for line in Line.browse(Transaction().context['active_ids']):
2008-03-19 22:13:51 +01:00
amount += line.debit - line.credit
if not company:
company = line.account.company
2013-08-21 18:19:26 +02:00
return amount, company
def transition_start(self):
amount, company = self.get_writeoff()
if not company:
return 'end'
2012-09-24 22:17:29 +02:00
if company.currency.is_zero(amount):
2008-03-19 22:13:51 +01:00
return 'reconcile'
return 'writeoff'
2013-08-21 18:19:26 +02:00
def default_writeoff(self, fields):
amount, company = self.get_writeoff()
return {
'amount': amount,
'currency_digits': company.currency.digits,
}
2012-09-24 22:17:29 +02:00
def transition_reconcile(self):
Line = Pool().get('account.move.line')
2008-03-19 22:13:51 +01:00
2013-01-26 15:11:34 +01:00
journal = getattr(self.writeoff, 'journal', None)
date = getattr(self.writeoff, 'date', None)
2013-11-28 23:26:15 +01:00
description = getattr(self.writeoff, 'description', None)
2012-09-24 22:17:29 +02:00
Line.reconcile(Line.browse(Transaction().context['active_ids']),
2013-12-20 13:59:46 +01:00
journal=journal, date=date, description=description)
2012-01-02 17:54:33 +01:00
return 'end'
2008-03-19 22:13:51 +01:00
class UnreconcileLines(Wizard):
'Unreconcile Lines'
2012-09-24 22:17:29 +02:00
__name__ = 'account.move.unreconcile_lines'
2012-03-25 00:17:23 +01:00
start_state = 'unreconcile'
2012-01-02 17:54:33 +01:00
unreconcile = StateTransition()
2012-09-24 22:17:29 +02:00
def transition_unreconcile(self):
pool = Pool()
Line = pool.get('account.move.line')
Reconciliation = pool.get('account.move.reconciliation')
2012-09-24 22:17:29 +02:00
lines = Line.browse(Transaction().context['active_ids'])
reconciliations = [x.reconciliation for x in lines if x.reconciliation]
if reconciliations:
Reconciliation.delete(reconciliations)
2012-01-02 17:54:33 +01:00
return 'end'
2014-09-19 16:24:48 +02:00
class Reconcile(Wizard):
'Reconcile'
__name__ = 'account.reconcile'
start_state = 'next_'
next_ = StateTransition()
show = StateView('account.reconcile.show',
'account.reconcile_show_view_form', [
2012-01-02 17:54:33 +01:00
Button('Cancel', 'end', 'tryton-cancel'),
2014-09-19 16:24:48 +02:00
Button('Skip', 'next_', 'tryton-go-next'),
Button('Reconcile', 'reconcile', 'tryton-ok', default=True),
2012-01-02 17:54:33 +01:00
])
2014-09-19 16:24:48 +02:00
reconcile = StateTransition()
2012-01-02 17:54:33 +01:00
2014-09-19 16:24:48 +02:00
def get_accounts(self):
'Return a list of account id to reconcile'
pool = Pool()
Line = pool.get('account.move.line')
line = Line.__table__()
Account = pool.get('account.account')
account = Account.__table__()
cursor = Transaction().cursor
balance = line.debit - line.credit
cursor.execute(*line.join(account,
condition=line.account == account.id).select(
account.id,
where=(line.reconciliation == Null) & account.reconcile,
group_by=account.id,
having=(
Sum(Case((balance > 0, 1), else_=0)) > 0)
& (Sum(Case((balance < 0, 1), else_=0)) > 0)
))
return [a for a, in cursor.fetchall()]
def get_parties(self, account):
'Return a list party to reconcile for the account'
pool = Pool()
Line = pool.get('account.move.line')
line = Line.__table__()
cursor = Transaction().cursor
balance = line.debit - line.credit
cursor.execute(*line.select(line.party,
where=(line.reconciliation == Null)
& (line.account == account.id),
group_by=line.party,
having=(
Sum(Case((balance > 0, 1), else_=0)) > 0)
& (Sum(Case((balance < 0, 1), else_=0)) > 0)
))
return [p for p, in cursor.fetchall()]
def transition_next_(self):
def next_account():
accounts = list(self.show.accounts)
if not accounts:
return
account = accounts.pop()
self.show.account = account
self.show.parties = self.get_parties(account)
self.show.accounts = accounts
return account
def next_party():
parties = list(self.show.parties)
if not parties:
return
party = parties.pop()
self.show.party = party
self.show.parties = parties
return party
if getattr(self.show, 'accounts', None) is None:
self.show.accounts = self.get_accounts()
if not next_account():
return 'end'
if getattr(self.show, 'parties', None) is None:
self.show.parties = self.get_parties(self.show.account)
while not next_party():
if not next_account():
return 'end'
return 'show'
def default_show(self, fields):
pool = Pool()
Date = pool.get('ir.date')
defaults = {}
defaults['accounts'] = [a.id for a in self.show.accounts]
defaults['account'] = self.show.account.id
defaults['parties'] = [p.id for p in self.show.parties]
defaults['party'] = self.show.party.id if self.show.party else None
defaults['currency_digits'] = self.show.account.company.currency.digits
defaults['lines'] = self._default_lines()
defaults['write_off'] = Decimal(0)
defaults['date'] = Date.today()
return defaults
def _all_lines(self):
'Return all lines to reconcile for the current state'
pool = Pool()
Line = pool.get('account.move.line')
return Line.search([
('account', '=', self.show.account.id),
('party', '=',
self.show.party.id if self.show.party else None),
('reconciliation', '=', None),
])
def _default_lines(self):
'Return the larger list of lines which can be reconciled'
currency = self.show.account.company.currency
chunk = config.getint('account', 'reconciliation_chunk', 10)
# Combination is exponential so it must be limited to small number
default = []
for lines in grouped_slice(self._all_lines(), chunk):
lines = list(lines)
best = None
for n in xrange(len(lines), 1, -1):
for comb_lines in combinations(lines, n):
amount = sum((l.debit - l.credit) for l in comb_lines)
if currency.is_zero(amount):
best = [l.id for l in comb_lines]
break
if best:
break
if best:
default.extend(best)
return default
def transition_reconcile(self):
pool = Pool()
Line = pool.get('account.move.line')
if self.show.lines:
Line.reconcile(self.show.lines,
journal=self.show.journal,
date=self.show.date,
description=self.show.description)
return 'next_'
class ReconcileShow(ModelView):
'Reconcile'
__name__ = 'account.reconcile.show'
accounts = fields.One2Many('account.account', None, 'Account',
readonly=True)
account = fields.Many2One('account.account', 'Account', readonly=True)
parties = fields.One2Many('party.party', None, 'Parties', readonly=True)
party = fields.Many2One('party.party', 'Party', readonly=True)
lines = fields.Many2Many('account.move.line', None, None, 'Lines',
domain=[
('account', '=', Eval('account')),
('party', '=', Eval('party')),
2012-03-27 10:43:03 +02:00
('reconciliation', '=', None),
2014-09-19 16:24:48 +02:00
],
2014-09-19 18:04:30 +02:00
depends=['account', 'party'])
2014-09-19 16:24:48 +02:00
_write_off_states = {
'required': Bool(Eval('write_off', 0)),
'invisible': ~Eval('write_off', 0),
}
_write_off_depends = ['write_off']
write_off = fields.Function(fields.Numeric('Write-Off',
digits=(16, Eval('currency_digits', 2)),
states=_write_off_states,
depends=_write_off_depends + ['currency_digits']),
'on_change_with_write_off')
currency_digits = fields.Function(fields.Integer('Currency Digits'),
'on_change_with_currency_digits')
journal = fields.Many2One('account.journal', 'Journal',
states=_write_off_states, depends=_write_off_depends,
domain=[
('type', '=', 'write-off'),
2008-03-19 22:13:51 +01:00
])
2014-09-19 16:24:48 +02:00
date = fields.Date('Date',
states=_write_off_states, depends=_write_off_depends)
description = fields.Char('Description',
states=_write_off_states, depends=_write_off_depends)
@fields.depends('lines')
def on_change_with_write_off(self, name=None):
return sum((l.debit - l.credit) for l in self.lines)
@fields.depends('account')
def on_change_with_currency_digits(self, name=None):
return self.account.company.currency.digits
2008-03-19 22:13:51 +01:00
2014-05-09 23:32:17 +02:00
class CancelMoves(Wizard):
'Cancel Moves'
__name__ = 'account.move.cancel'
2014-11-10 23:35:54 +01:00
start_state = 'default'
default = StateView('account.move.cancel.default',
'account.move_cancel_default_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Ok', 'cancel', 'tryton-ok', default=True),
])
2014-05-09 23:32:17 +02:00
cancel = StateTransition()
2014-11-10 23:35:54 +01:00
def default_cancel(self, move):
default = {}
if self.default.description:
default['description'] = self.default.description
return default
2014-05-09 23:32:17 +02:00
def transition_cancel(self):
pool = Pool()
Move = pool.get('account.move')
Line = pool.get('account.move.line')
moves = Move.browse(Transaction().context['active_ids'])
for move in moves:
2014-11-10 23:35:54 +01:00
default = self.default_cancel(move)
cancel_move = move.cancel(default=default)
2014-05-09 23:32:17 +02:00
to_reconcile = defaultdict(list)
for line in move.lines + cancel_move.lines:
if line.account.reconcile:
to_reconcile[line.account].append(line)
for lines in to_reconcile.itervalues():
Line.reconcile(lines)
return 'end'
2014-11-10 23:35:54 +01:00
class CancelMovesDefault(ModelView):
'Cancel Moves'
__name__ = 'account.move.cancel.default'
description = fields.Char('Description')
2009-03-05 14:51:19 +01:00
class FiscalYearLine(ModelSQL):
2009-03-17 13:09:06 +01:00
'Fiscal Year - Move Line'
2012-09-24 22:17:29 +02:00
__name__ = 'account.fiscalyear-account.move.line'
2009-03-05 14:51:19 +01:00
_table = 'account_fiscalyear_line_rel'
2009-03-17 13:09:06 +01:00
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
2012-03-05 10:52:56 +01:00
ondelete='CASCADE', select=True)
2009-03-05 14:51:19 +01:00
line = fields.Many2One('account.move.line', 'Line', ondelete='RESTRICT',
2012-03-05 10:52:56 +01:00
select=True, required=True)
2009-03-05 14:51:19 +01:00
2012-09-24 22:17:29 +02:00
class FiscalYear2:
__name__ = 'account.fiscalyear'
2009-03-05 14:51:19 +01:00
close_lines = fields.Many2Many('account.fiscalyear-account.move.line',
'fiscalyear', 'line', 'Close Lines')
2012-01-02 17:54:33 +01:00
class PrintGeneralJournalStart(ModelView):
'Print General Journal'
2012-09-24 22:17:29 +02:00
__name__ = 'account.move.print_general_journal.start'
2008-03-30 17:03:45 +02:00
from_date = fields.Date('From Date', required=True)
to_date = fields.Date('To Date', required=True)
company = fields.Many2One('company.company', 'Company', required=True)
2009-03-20 13:49:25 +01:00
posted = fields.Boolean('Posted Move', help='Show only posted move')
2008-03-30 17:03:45 +02:00
2012-09-24 22:17:29 +02:00
@staticmethod
def default_from_date():
Date = Pool().get('ir.date')
return datetime.date(Date.today().year, 1, 1)
2008-03-30 17:03:45 +02:00
2012-09-24 22:17:29 +02:00
@staticmethod
def default_to_date():
Date = Pool().get('ir.date')
return Date.today()
2008-03-30 17:03:45 +02:00
2012-09-24 22:17:29 +02:00
@staticmethod
def default_company():
2012-03-27 10:43:03 +02:00
return Transaction().context.get('company')
2008-03-30 17:03:45 +02:00
2012-09-24 22:17:29 +02:00
@staticmethod
def default_posted():
2008-03-30 17:03:45 +02:00
return False
class PrintGeneralJournal(Wizard):
'Print General Journal'
2012-09-24 22:17:29 +02:00
__name__ = 'account.move.print_general_journal'
2012-01-02 17:54:33 +01:00
start = StateView('account.move.print_general_journal.start',
'account.print_general_journal_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-print', default=True),
])
print_ = StateAction('account.report_general_journal')
2012-09-24 22:17:29 +02:00
def do_print_(self, action):
2012-01-02 17:54:33 +01:00
data = {
2012-09-24 22:17:29 +02:00
'company': self.start.company.id,
'from_date': self.start.from_date,
'to_date': self.start.to_date,
'posted': self.start.posted,
2012-01-02 17:54:33 +01:00
}
return action, data
2008-03-30 17:03:45 +02:00
class GeneralJournal(Report):
2012-09-24 22:17:29 +02:00
__name__ = 'account.move.general_journal'
2008-03-30 17:03:45 +02:00
2012-09-24 22:17:29 +02:00
@classmethod
def _get_records(cls, ids, model, data):
Move = Pool().get('account.move')
2008-03-30 17:03:45 +02:00
clause = [
2012-01-02 17:54:33 +01:00
('date', '>=', data['from_date']),
('date', '<=', data['to_date']),
('period.fiscalyear.company', '=', data['company']),
]
2012-01-02 17:54:33 +01:00
if data['posted']:
clause.append(('state', '=', 'posted'))
2012-09-24 22:17:29 +02:00
return Move.search(clause,
2012-09-29 15:33:21 +02:00
order=[('date', 'ASC'), ('id', 'ASC')])
2008-03-30 17:03:45 +02:00
2012-09-24 22:17:29 +02:00
@classmethod
def parse(cls, report, moves, data, localcontext):
Company = Pool().get('company.company')
2008-03-30 17:03:45 +02:00
2012-09-24 22:17:29 +02:00
company = Company(data['company'])
2008-03-30 17:03:45 +02:00
2010-08-19 22:02:00 +02:00
localcontext['company'] = company
localcontext['digits'] = company.currency.digits
2012-01-02 17:54:33 +01:00
localcontext['from_date'] = data['from_date']
localcontext['to_date'] = data['to_date']
2008-03-30 17:03:45 +02:00
2012-09-24 22:17:29 +02:00
return super(GeneralJournal, cls).parse(report, moves, data,
2012-01-02 17:54:33 +01:00
localcontext)