Files
tryton/modules/account/move.py
T

2176 lines
79 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
2019-11-18 12:24:37 +01:00
import hashlib
from itertools import groupby, combinations, chain, islice
2013-02-23 14:09:33 +01:00
from operator import itemgetter
2014-05-09 23:32:17 +02:00
from collections import defaultdict
2017-09-07 18:40:32 +02:00
from sql import Null, Literal
2016-02-03 21:23:34 +01:00
from sql.aggregate import Sum, Max
from sql.functions import CharLength
2014-09-19 16:24:48 +02:00
from sql.conditionals import Coalesce, Case
2018-12-29 14:20:28 +01:00
from trytond.i18n import gettext
2018-09-01 11:20:13 +02:00
from trytond.model import ModelView, ModelSQL, fields, Check, DeactivableMixin
2018-12-29 14:20:28 +01:00
from trytond.model.exceptions import AccessError
2021-03-20 12:50:45 +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, If, PYSONEncoder
2010-08-19 22:02:00 +02:00
from trytond.transaction import Transaction
from trytond.pool import Pool
2012-09-24 22:17:29 +02:00
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
2018-12-29 14:20:28 +01:00
from .exceptions import (PostError, MoveDatesError, CancelWarning,
2019-11-18 12:24:37 +01:00
ReconciliationError, DeleteDelegatedWarning, GroupLineError,
CancelDelegatedWarning)
2018-12-29 14:20:28 +01:00
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,
2019-12-18 09:16:34 +01:00
help='Also known as Folio Number.')
company = fields.Many2One('company.company', 'Company', required=True,
states=_MOVE_STATES, depends=_MOVE_DEPENDS)
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)),
If(Eval('state') == 'draft',
('state', '=', 'open'),
()),
2014-12-03 16:13:08 +01:00
],
states=_MOVE_STATES, depends=_MOVE_DEPENDS + ['company', 'state'],
select=True)
2008-03-17 18:32:06 +01:00
journal = fields.Many2One('account.journal', 'Journal', required=True,
2020-12-17 22:21:43 +01:00
states={
'readonly': Eval('number') & Eval('journal'),
},
context={
'company': Eval('company', -1),
},
depends=['number', 'company'])
2017-09-07 18:40:32 +02:00
date = fields.Date('Effective Date', required=True, select=True,
states=_MOVE_STATES, 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
states=_MOVE_STATES, depends=_MOVE_DEPENDS + ['company'],
2020-03-01 12:33:50 +01:00
context={
'journal': Eval('journal'),
'period': Eval('period'),
'date': Eval('date'),
2010-01-20 10:56:33 +01:00
})
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def __setup__(cls):
super(Move, cls).__setup__()
2022-02-03 22:29:08 +01:00
cls.create_date.select = True
2020-06-17 13:58:44 +02:00
cls._check_modify_exclude = ['lines']
2012-09-24 22:17:29 +02:00
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._buttons.update({
2012-03-25 00:17:23 +01:00
'post': {
'invisible': Eval('state') == 'posted',
2018-02-19 11:36:13 +01:00
'depends': ['state'],
2012-03-25 00:17:23 +01:00
},
})
2018-08-26 18:06:32 +02:00
cls.__rpc__.update({
'post': RPC(
readonly=False, instantiate=0, fresh_session=True),
})
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def __register__(cls, module_name):
cursor = Transaction().connection.cursor()
2018-07-24 22:01:07 +02:00
table = cls.__table_handler__(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
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]))
2018-07-24 22:01:07 +02:00
table = cls.__table_handler__(module_name)
2009-03-06 00:45:18 +01:00
table.index_action(['journal', 'period'], 'add')
2008-03-17 18:32:06 +01:00
@classmethod
def order_post_number(cls, tables):
table, _ = tables[None]
return [CharLength(table.post_number), table.post_number]
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')
today = Date.today()
2012-09-24 22:17:29 +02:00
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)
if today < period.start_date or today > period.end_date:
return period.start_date
return 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):
pool = Pool()
Line = pool.get('account.move.line')
Date = pool.get('ir.date')
today = Date.today()
2012-09-24 22:17:29 +02:00
date = self.date
if date:
if self.period and not (
self.period.start_date <= date <= self.period.end_date):
if (today >= self.period.start_date
and today <= self.period.end_date):
date = today
else:
date = self.period.start_date
self.on_change_date()
return date
2012-09-24 22:17:29 +02:00
lines = Line.search([
('journal', '=', self.journal),
('period', '=', self.period),
], order=[('id', 'DESC')], limit=1)
if lines:
date = lines[0].date
elif self.period:
if (today >= self.period.start_date
and today <= self.period.end_date):
date = today
else:
date = self.period.start_date
2012-09-24 22:17:29 +02:00
return date
2009-10-10 13:31:39 +02:00
@fields.depends('date', 'lines')
def on_change_date(self):
for line in (self.lines or []):
line.date = self.date
@fields.depends(methods=['on_change_with_date', 'on_change_date'])
def on_change_period(self):
self.date = self.on_change_with_date()
self.on_change_date()
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')
2020-12-27 00:19:30 +01:00
get_name = Model.get_name
2012-09-29 15:33:21 +02:00
models = cls._get_origin()
2020-12-27 00:19:30 +01:00
return [(None, '')] + [(m, get_name(m)) for m in models]
2012-09-29 15:33:21 +02:00
@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):
2018-12-29 14:20:28 +01:00
raise MoveDatesError(
gettext('account.msg_move_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':
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account.msg_modify_posted_moved',
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):
2015-04-05 23:39:57 +02:00
if clause[1].startswith('!') or clause[1].startswith('not '):
bool_op = 'AND'
else:
bool_op = 'OR'
return [bool_op,
2014-01-31 14:06:56 +01:00
('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):
2018-06-26 19:11:40 +02:00
keys = list(values.keys())
2013-12-19 14:44:33 +01:00
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
Journal = pool.get('account.journal')
context = Transaction().context
journals = {}
default_company = cls.default_company()
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', context.get('journal'))
company_id = vals.get('company', default_company)
2013-01-06 22:18:13 +01:00
if journal_id:
if journal_id not in journals:
journal = journals[journal_id] = Journal(journal_id)
else:
journal = journals[journal_id]
sequence = journal.get_multivalue(
'sequence', company=company_id)
if sequence:
vals['number'] = sequence.get()
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):
if default is None:
default = {}
2018-09-01 01:02:03 +02:00
else:
default = default.copy()
default.setdefault('number', None)
default.setdefault('post_number', None)
default.setdefault('state', cls.default_state())
default.setdefault('post_date', None)
return super().copy(moves, default=default)
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().connection.cursor()
2013-02-23 14:09:33 +01:00
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))
amounts.update(dict(cursor))
2013-02-23 14:09:33 +01:00
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])
for k, g in groupby(cursor, itemgetter(0))))
2013-02-23 14:09:33 +01:00
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')
2018-12-29 14:20:28 +01:00
Warning = pool.get('res.user.warning')
2014-05-09 23:32:17 +02:00
default = {
'origin': str(self),
}
if self.period.state == 'close':
2018-12-29 14:20:28 +01:00
key = '%s.cancel' % self
if Warning.check(key):
raise CancelWarning(key,
gettext('account.msg_move_cancel_closed_period',
2018-12-29 14:20:28 +01:00
move=self.rec_name))
2014-05-09 23:32:17 +02:00
date = Date.today()
period_id = Period.find(self.company.id, date=date)
default.update({
'date': date,
'period': period_id,
})
2018-09-01 01:02:03 +02:00
default['lines.debit'] = lambda data: data['debit'] * -1
default['lines.credit'] = lambda data: data['credit'] * -1
default['lines.amount_second_currency'] = (
lambda data: data['amount_second_currency'] * -1
if data['amount_second_currency']
else data['amount_second_currency'])
default['lines.tax_lines.amount'] = lambda data: data['amount'] * -1
default['lines.origin'] = (
lambda data: 'account.move.line,%s' % data['id'])
2014-05-09 23:32:17 +02:00
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 = {}
2018-09-01 01:02:03 +02:00
else:
default = default.copy()
2014-11-10 23:35:54 +01:00
default.update(self._cancel_default())
cancel_move, = self.copy([self], default=default)
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
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:
2018-12-29 14:20:28 +01:00
raise PostError(
gettext('account.msg_post_empty_move', 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):
2018-12-29 14:20:28 +01:00
raise PostError(
gettext('account.msg_post_unbalanced_move',
move=move.rec_name))
2008-03-17 18:32:06 +01:00
for move in moves:
2016-06-17 13:40:45 +02:00
move.state = 'posted'
if not move.post_number:
2016-06-17 13:40:45 +02:00
move.post_date = Date.today()
2021-03-11 23:00:09 +01:00
move.post_number = move.period.post_move_sequence_used.get()
2008-03-17 18:32:06 +01:00
2020-03-01 12:33:50 +01:00
def keyfunc(l):
return 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))
2016-06-17 13:40:45 +02:00
cls.save(moves)
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)
2021-03-21 22:38:05 +01:00
company = fields.Many2One('company.company', "Company", required=True)
lines = fields.One2Many(
'account.move.line', 'reconciliation', 'Lines',
domain=[
('move.company', '=', Eval('company', -1)),
],
depends=['company'])
2016-02-03 21:23:34 +01:00
date = fields.Date('Date', required=True, select=True,
2019-12-18 09:16:34 +01:00
help='Highest date of the reconciled lines.')
2019-04-12 19:43:53 +02:00
delegate_to = fields.Many2One(
'account.move.line', "Delegate To", ondelete="RESTRICT", select=True,
2021-03-21 22:38:05 +01:00
domain=[
('move.company', '=', Eval('company', -1)),
],
depends=['company'],
2019-04-12 19:43:53 +02:00
help="The line to which the reconciliation status is delegated.")
2008-03-19 22:13:51 +01:00
2016-02-03 21:23:34 +01:00
@classmethod
def __register__(cls, module_name):
cursor = Transaction().connection.cursor()
2018-07-24 22:01:07 +02:00
table = cls.__table_handler__(module_name)
2016-02-03 21:23:34 +01:00
sql_table = cls.__table__()
pool = Pool()
Move = pool.get('account.move')
Line = pool.get('account.move.line')
move = Move.__table__()
line = Line.__table__()
2021-03-21 22:38:05 +01:00
line_h = Line.__table_handler__(module_name)
2016-02-03 21:23:34 +01:00
date_exist = table.column_exist('date')
2021-03-21 22:38:05 +01:00
company_exist = table.column_exist('company')
2016-02-03 21:23:34 +01:00
super(Reconciliation, cls).__register__(module_name)
# Migration from 3.8: new date field
if (not date_exist
2019-12-06 17:33:46 +01:00
and backend.TableHandler.table_exist(Line._table)
2018-07-24 22:01:07 +02:00
and Line.__table_handler__().column_exist('move')):
2016-02-03 21:23:34 +01:00
cursor.execute(*sql_table.update(
[sql_table.date],
line.join(move,
condition=move.id == line.move
).select(Max(move.date),
where=line.reconciliation == sql_table.id,
group_by=line.reconciliation)))
2021-03-21 22:38:05 +01:00
# Migration from 5.8: add company field
if not company_exist and line_h.column_exist('reconciliation'):
value = (line
.join(move, condition=line.move == move.id)
.select(
move.company,
where=line.reconciliation == sql_table.id,
group_by=move.company))
cursor.execute(*sql_table.update([sql_table.company], [value]))
@classmethod
def default_company(cls):
return Transaction().context.get('company')
2012-09-24 22:17:29 +02:00
@classmethod
2013-01-06 22:18:13 +01:00
def create(cls, vlist):
2021-03-11 23:00:09 +01:00
pool = Pool()
Configuration = pool.get('account.configuration')
configuration = Configuration(1)
2013-01-06 22:18:13 +01:00
vlist = [x.copy() for x in vlist]
2021-04-03 14:27:09 +02:00
default_company = cls.default_company()
2013-01-06 22:18:13 +01:00
for vals in vlist:
if 'name' not in vals:
2021-04-03 14:27:09 +02:00
vals['name'] = configuration.get_multivalue(
'reconciliation_sequence',
company=vals.get('company', default_company)).get()
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):
2018-12-29 14:20:28 +01:00
raise AccessError(gettext('account.msg_write_reconciliation'))
@classmethod
def validate(cls, reconciliations):
super(Reconciliation, cls).validate(reconciliations)
cls.check_lines(reconciliations)
2019-04-12 19:43:53 +02:00
@classmethod
def delete(cls, reconciliations):
pool = Pool()
Warning = pool.get('res.user.warning')
for reconciliation in reconciliations:
if reconciliation.delegate_to:
key = '%s.delete.delegated' % reconciliation
if Warning.check(key):
raise DeleteDelegatedWarning(key,
gettext('account.msg_reconciliation_delete_delegated',
reconciliation=reconciliation.rec_name,
line=reconciliation.delegate_to.rec_name,
move=reconciliation.delegate_to.move.rec_name))
super().delete(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':
2018-12-29 14:20:28 +01:00
raise ReconciliationError(
gettext('account.msg_reconciliation_line_not_valid',
line=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:
2018-12-29 14:20:28 +01:00
raise ReconciliationError(
gettext('account'
'.msg_reconciliation_different_accounts',
line=line.rec_name,
account1=line.account.rec_name,
account2=account.rec_name))
if not account.reconcile:
2018-12-29 14:20:28 +01:00
raise ReconciliationError(
gettext('account'
'.msg_reconciliation_account_not_reconcile',
line=line.rec_name,
account=line.account.rec_name))
if line.party != party:
2018-12-29 14:20:28 +01:00
raise ReconciliationError(
gettext('account'
'.msg_reconciliation_different_parties',
line=line.rec_name,
party1=line.party.rec_name,
party2=party.rec_name))
2021-03-21 22:38:05 +01:00
if not reconciliation.company.currency.is_zero(debit - credit):
2017-11-15 00:16:39 +01:00
lang = Lang.get()
2021-03-21 22:38:05 +01:00
debit = lang.currency(debit, reconciliation.company.currency)
credit = lang.currency(credit, reconciliation.company.currency)
2018-12-29 14:20:28 +01:00
raise ReconciliationError(
gettext('account.msg_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'
2016-06-30 18:50:40 +02:00
_states = {
'readonly': Eval('move_state') == 'posted',
}
_depends = ['move_state']
2010-01-20 10:56:33 +01:00
debit = fields.Numeric('Debit', digits=(16, Eval('currency_digits', 2)),
2016-06-30 18:50:40 +02:00
required=True, states=_states,
2020-03-01 12:33:50 +01:00
depends=['currency_digits', 'credit', 'tax_lines', 'journal']
+ _depends)
2010-01-20 10:56:33 +01:00
credit = fields.Numeric('Credit', digits=(16, Eval('currency_digits', 2)),
2016-06-30 18:50:40 +02:00
required=True, states=_states,
2020-03-01 12:33:50 +01:00
depends=['currency_digits', 'debit', 'tax_lines', 'journal']
+ _depends)
2008-03-17 18:32:06 +01:00
account = fields.Many2One('account.account', 'Account', required=True,
2018-08-31 14:58:05 +02:00
domain=[
2021-04-10 13:55:35 +02:00
('company', '=', Eval('company', -1)),
2019-03-24 18:00:01 +01:00
('type', '!=', None),
('closed', '!=', True),
2018-08-31 14:58:05 +02:00
['OR',
('start_date', '=', None),
('start_date', '<=', Eval('date', None)),
],
['OR',
('end_date', '=', None),
('end_date', '>=', Eval('date', None)),
],
],
2021-04-10 13:55:35 +02:00
context={
'company': Eval('company', -1),
},
select=True, states=_states, depends=_depends + ['date', 'company'])
2012-03-05 10:52:56 +01:00
move = fields.Many2One('account.move', 'Move', select=True, required=True,
2016-03-04 00:28:51 +01:00
ondelete='CASCADE',
2011-08-04 16:57:14 +02:00
states={
'required': False,
2016-06-30 18:50:40 +02:00
'readonly': (((Eval('state') == 'valid') | _states['readonly'])
& Bool(Eval('move'))),
2011-08-04 16:57:14 +02:00
},
2016-06-30 18:50:40 +02:00
depends=['state'] + _depends)
2021-04-10 13:55:35 +02:00
journal = fields.Function(fields.Many2One(
'account.journal', 'Journal',
states=_states,
context={
'company': Eval('company', -1),
},
depends=_depends + ['company']),
'get_move_field', setter='set_move_field',
searcher='search_move_field')
2016-06-30 18:50:40 +02:00
period = fields.Function(fields.Many2One('account.period', 'Period',
states=_states, depends=_depends),
2010-03-11 16:45:27 +01:00
'get_move_field', setter='set_move_field',
searcher='search_move_field')
2021-04-10 13:55:35 +02:00
company = fields.Function(fields.Many2One(
'company.company', "Company", states=_states, depends=_depends),
'get_move_field', setter='set_move_field',
searcher='search_move_field')
2016-06-30 18:50:40 +02:00
date = fields.Function(fields.Date('Effective Date', required=True,
states=_states, depends=_depends),
2018-08-31 14:58:05 +02:00
'on_change_with_date', setter='set_move_field',
2010-03-11 16:45:27 +01:00
searcher='search_move_field')
origin = fields.Reference(
"Origin", selection='get_origin', states=_states, depends=_depends)
move_origin = fields.Function(
fields.Reference("Move Origin", selection='get_move_origin'),
2012-09-29 15:33:21 +02:00
'get_move_field', searcher='search_move_field')
2016-06-30 18:50:40 +02:00
description = fields.Char('Description', states=_states, depends=_depends)
move_description = fields.Function(fields.Char('Move Description',
states=_states, depends=_depends),
2014-02-03 18:12:16 +01:00
'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)),
2019-12-18 09:16:34 +01:00
help='The amount expressed in a second currency.',
states={
'required': Bool(Eval('second_currency')),
2016-06-30 18:50:40 +02:00
'readonly': _states['readonly'],
},
2016-06-30 18:50:40 +02:00
depends=['second_currency_digits', 'second_currency'] + _depends)
2008-05-09 17:24:55 +02:00
second_currency = fields.Many2One('currency.currency', 'Second Currency',
2019-12-18 09:16:34 +01:00
help='The second currency.',
domain=[
If(~Eval('second_currency_required'),
(),
('id', '=', Eval('second_currency_required', -1))),
],
states={
'required': (Bool(Eval('amount_second_currency'))
| Bool(Eval('second_currency_required'))),
2016-06-30 18:50:40 +02:00
'readonly': _states['readonly']
},
depends=['amount_second_currency', 'second_currency_required']
+ _depends)
second_currency_required = fields.Function(
fields.Many2One('currency.currency', "Second Currency Required"),
'on_change_with_second_currency_required')
party = fields.Many2One('party.party', 'Party', select=True,
states={
'required': Eval('party_required', False),
'invisible': ~Eval('party_required', False),
2016-06-30 18:50:40 +02:00
'readonly': _states['readonly'],
},
2021-04-10 13:55:35 +02:00
context={
'company': Eval('company', -1),
},
depends=['party_required', 'company'] + _depends, 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)
2019-04-12 19:43:53 +02:00
reconciliations_delegated = fields.One2Many(
'account.move.reconciliation', 'delegate_to',
"Reconciliations Delegated", readonly=True)
2008-03-23 23:05:12 +01:00
tax_lines = fields.One2Many('account.tax.line', 'move_line', 'Tax Lines')
move_state = fields.Function(
fields.Selection('get_move_states', "Move State"),
'on_change_with_move_state', searcher='search_move_field')
2020-10-09 19:09:05 +02:00
currency = fields.Function(fields.Many2One(
'currency.currency', "Currency"), 'on_change_with_currency')
2010-03-11 16:45:27 +01:00
currency_digits = fields.Function(fields.Integer('Currency Digits'),
'on_change_with_currency_digits')
2010-03-11 16:45:27 +01:00
second_currency_digits = fields.Function(fields.Integer(
'Second Currency Digits'), 'on_change_with_second_currency_digits')
2015-02-10 00:13:10 +01:00
amount = fields.Function(fields.Numeric('Amount',
digits=(16, Eval('amount_currency_digits', 2)),
depends=['amount_currency_digits']),
'get_amount')
amount_currency = fields.Function(fields.Many2One('currency.currency',
'Amount Currency'), 'get_amount_currency')
amount_currency_digits = fields.Function(fields.Integer(
'Amount Currency Digits'), 'get_amount_currency')
2008-03-17 18:32:06 +01:00
2016-06-30 18:50:40 +02:00
del _states, _depends
2012-09-24 22:17:29 +02:00
@classmethod
def __setup__(cls):
super(Line, cls).__setup__()
2021-03-07 11:13:53 +01:00
cls.__access__.add('move')
2018-04-30 15:48:54 +02:00
cls._check_modify_exclude = {
'maturity_date', 'reconciliation', 'tax_lines'}
cls._reconciliation_modify_disallow = {
'account', 'debit', 'credit', 'party',
}
table = cls.__table__()
2012-09-24 22:17:29 +02:00
cls._sql_constraints += [
2008-03-17 18:32:06 +01:00
('credit_debit',
Check(table, table.credit * table.debit == 0),
2018-12-29 14:20:28 +01:00
'account.msg_line_debit_credit'),
('second_currency_sign',
Check(table, Coalesce(table.amount_second_currency, 0)
* (table.debit - table.credit) >= 0),
2018-12-29 14:20:28 +01:00
'account.msg_line_second_currency_sign'),
2012-09-24 22:17:29 +02:00
]
cls.__rpc__.update({
'on_write': RPC(instantiate=0),
})
2020-12-16 21:31:25 +01:00
# Do not cache default_date nor default_move
cls.__rpc__['default_get'].cache = None
2012-09-24 22:17:29 +02:00
cls._order[0] = ('id', 'DESC')
2008-03-17 18:32:06 +01:00
2012-09-24 22:17:29 +02:00
@classmethod
def __register__(cls, module_name):
super(Line, cls).__register__(module_name)
table = cls.__table__()
table_h = cls.__table_handler__(module_name)
2010-03-12 14:14:53 +01:00
# Index for General Ledger
table_h.index_action(['move', 'account'], 'add')
# Index for account.account.party
table_h.index_action(
['account', 'party', 'id'], 'add', where=table.party != Null)
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
2017-07-27 00:03:37 +02:00
@classmethod
def default_move(cls):
transaction = Transaction()
context = transaction.context
if context.get('journal') and context.get('period'):
lines = cls.search([
2021-04-10 13:55:35 +02:00
('company', '=', context.get('company')),
2017-07-27 00:03:37 +02:00
('move.journal', '=', context['journal']),
('move.period', '=', context['period']),
('create_uid', '=', transaction.user),
('state', '=', 'draft'),
], order=[('id', 'DESC')], limit=1)
if lines:
line, = lines
return line.move.id
2021-04-10 13:55:35 +02:00
@fields.depends(
'move', 'debit', 'credit',
'_parent_move.lines', '_parent_move.company')
2017-07-27 00:03:37 +02:00
def on_change_move(self):
2021-04-10 13:55:35 +02:00
if self.move:
if not self.debit and not self.credit:
total = sum(l.debit - l.credit
for l in getattr(self.move, 'lines', []))
self.debit = -total if total < 0 else Decimal(0)
self.credit = total if total > 0 else Decimal(0)
self.company = self.move.company
@classmethod
def default_company(cls):
return Transaction().context.get('company')
2017-07-27 00:03:37 +02: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)
2020-10-09 19:09:05 +02:00
@fields.depends('account')
def on_change_with_currency(self, name=None):
if self.account:
return self.account.currency.id
@fields.depends('account')
def on_change_with_currency_digits(self, name=None):
if self.account:
return self.account.currency_digits
else:
return 2
@fields.depends('second_currency')
def on_change_with_second_currency_digits(self, name=None):
if self.second_currency:
return self.second_currency.digits
else:
return 2
2008-09-22 18:23:05 +02:00
@classmethod
def _get_origin(cls):
'Return list of Model names for origin Reference'
return ['account.move.line']
2012-09-29 15:33:21 +02:00
@classmethod
def get_origin(cls):
Model = Pool().get('ir.model')
2020-12-27 00:19:30 +01:00
get_name = Model.get_name
models = cls._get_origin()
2020-12-27 00:19:30 +01:00
return [(None, '')] + [(m, get_name(m)) for m in models]
@classmethod
def get_move_origin(cls):
2012-09-29 15:33:21 +02:00
Move = Pool().get('account.move')
return Move.get_origin()
2017-07-27 00:03:37 +02:00
@fields.depends('debit', 'credit', 'amount_second_currency')
2012-09-24 22:17:29 +02:00
def on_change_debit(self):
if self.debit:
2014-10-29 20:39:03 +01:00
self.credit = Decimal('0.0')
self._amount_second_currency_sign()
2017-07-27 00:03:37 +02:00
@fields.depends('debit', 'credit', 'amount_second_currency')
2012-09-24 22:17:29 +02:00
def on_change_credit(self):
if self.credit:
2014-10-29 20:39:03 +01:00
self.debit = Decimal('0.0')
self._amount_second_currency_sign()
@fields.depends('amount_second_currency', 'debit', 'credit')
def on_change_amount_second_currency(self):
self._amount_second_currency_sign()
def _amount_second_currency_sign(self):
'Set correct sign to amount_second_currency'
if self.amount_second_currency:
self.amount_second_currency = \
self.amount_second_currency.copy_sign(self.debit - self.credit)
2017-07-27 00:03:37 +02:00
@fields.depends('account')
2012-09-24 22:17:29 +02:00
def on_change_account(self):
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:
self.second_currency = self.account.second_currency
self.second_currency_digits = (
self.on_change_with_second_currency_digits())
if not self.account.party_required:
self.party = None
@fields.depends('account')
def on_change_with_second_currency_required(self, name=None):
if self.account and self.account.second_currency:
return self.account.second_currency.id
@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 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
2018-08-31 14:58:05 +02:00
@fields.depends('move', '_parent_move.date')
def on_change_with_date(self, name=None):
if self.move:
return self.move.date
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):
2017-09-27 18:37:24 +02:00
nested = clause[0].lstrip(name)
2014-02-03 18:12:16 +01:00
if name.startswith('move_'):
name = name[5:]
2017-09-27 18:37:24 +02:00
return [('move.' + name + nested,) + tuple(clause[1:])]
@classmethod
def get_move_states(cls):
pool = Pool()
Move = pool.get('account.move')
return Move.fields_get(['state'])['state']['selection']
2017-07-17 09:59:50 +02:00
@fields.depends('move', '_parent_move.state')
2016-06-30 18:50:40 +02:00
def on_change_with_move_state(self, name=None):
if self.move:
return self.move.state
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')
2021-04-10 13:55:35 +02:00
order_company = _order_move_field('company')
2013-09-17 10:49:25 +02:00
order_date = _order_move_field('date')
order_move_origin = _order_move_field('origin')
2013-09-17 10:49:25 +02:00
order_move_state = _order_move_field('state')
2015-02-10 00:13:10 +01:00
def get_amount(self, name):
2019-03-24 18:00:01 +01:00
sign = -1 if self.account.type.statement == 'income' else 1
2015-02-10 00:13:10 +01:00
if self.amount_second_currency is not None:
return self.amount_second_currency * sign
else:
return (self.debit - self.credit) * sign
2015-02-10 00:13:10 +01:00
def get_amount_currency(self, name):
if self.second_currency:
currency = self.second_currency
else:
currency = self.account.currency
if name == 'amount_currency':
return currency.id
elif name == 'amount_currency_digits':
return currency.digits
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__()
2017-09-07 18:40:32 +02:00
fiscalyear = FiscalYear.__table__()
context = Transaction().context
company = context.get('company')
2017-09-07 18:40:32 +02:00
fiscalyear_ids = []
where = Literal(True)
if context.get('posted'):
where &= move.state == 'posted'
2021-01-04 21:33:28 +01:00
if context.get('journal'):
where &= move.journal == context['journal']
2017-09-07 18:40:32 +02:00
date = context.get('date')
from_date, to_date = context.get('from_date'), context.get('to_date')
fiscalyear_id = context.get('fiscalyear')
period_ids = context.get('periods')
if date:
2012-09-24 22:17:29 +02:00
fiscalyears = FiscalYear.search([
2017-09-07 18:40:32 +02:00
('start_date', '<=', date),
('end_date', '>=', date),
('company', '=', company),
],
order=[('start_date', 'DESC')],
limit=1)
2017-09-07 18:40:32 +02:00
if fiscalyears:
fiscalyear_id = fiscalyears[0].id
else:
2017-09-07 18:40:32 +02:00
fiscalyear_id = -1
2018-06-26 19:11:40 +02:00
fiscalyear_ids = list(map(int, fiscalyears))
2017-09-07 18:40:32 +02:00
where &= period.fiscalyear == fiscalyear_id
where &= move.date <= date
elif fiscalyear_id or period_ids is not None or from_date or to_date:
2017-09-07 18:40:32 +02:00
if fiscalyear_id:
fiscalyear_ids = [fiscalyear_id]
where &= fiscalyear.id == fiscalyear_id
if period_ids is not None:
where &= move.period.in_(period_ids or [None])
2017-09-07 18:40:32 +02:00
if from_date:
where &= move.date >= from_date
if to_date:
where &= move.date <= to_date
2008-03-17 18:32:06 +01:00
else:
2017-09-07 18:40:32 +02:00
where &= fiscalyear.state == 'open'
where &= fiscalyear.company == company
fiscalyears = FiscalYear.search([
('state', '=', 'open'),
2017-09-07 18:40:32 +02:00
('company', '=', company),
])
2018-06-26 19:11:40 +02:00
fiscalyear_ids = list(map(int, fiscalyears))
2017-09-07 18:40:32 +02:00
# Use LEFT JOIN to allow database optimization
# if no joined table is used in the where clause.
return (table.move.in_(move
2017-09-07 18:40:32 +02:00
.join(period, 'LEFT', condition=move.period == period.id)
.join(fiscalyear, 'LEFT',
condition=period.fiscalyear == fiscalyear.id)
.select(move.id, where=where)),
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):
2019-03-24 18:00:01 +01:00
if not self.account.type or self.account.closed:
2018-12-29 14:20:28 +01:00
raise AccessError(
2019-03-24 18:00:01 +01:00
gettext('account.msg_line_closed_account',
2018-12-29 14:20:28 +01:00
account=self.account.rec_name))
if bool(self.party) != bool(self.account.party_required):
error = 'party_set' if self.party else 'party_required'
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account.msg_line_%s' % error,
account=self.account.rec_name,
line=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')
transaction = Transaction()
database = transaction.database
connection = transaction.connection
2012-09-24 22:17:29 +02:00
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':
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account.msg_modify_line_closed_journal_period',
2018-12-29 14:20:28 +01:00
journal_period=journal_period.rec_name))
2008-03-17 18:32:06 +01:00
else:
database.lock(connection, JournalPeriod._table)
2013-01-06 22:18:13 +01:00
JournalPeriod.create([{
'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':
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account.msg_modify_line_posted_move',
line=line.rec_name,
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:
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account.msg_modify_line_reconciled',
line=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()
Move = pool.get('account.move')
def move_fields():
for fname, field in cls._fields.items():
if (isinstance(field, fields.Function)
and field.setter == 'set_move_field'):
if fname.startswith('move_'):
fname = fname[5:]
yield fname
moves = {}
context = Transaction().context
2013-01-06 22:18:13 +01:00
vlist = [x.copy() for x in vlist]
for vals in vlist:
if not vals.get('move'):
move_values = {}
for fname in move_fields():
move_values[fname] = vals.get(fname) or context.get(fname)
key = tuple(sorted(move_values.items()))
move = moves.get(key)
2016-06-17 13:40:45 +02:00
if move is None:
move = Move(**move_values)
2016-06-17 13:40:45 +02:00
move.save()
moves[key] = move
2016-06-17 13:40:45 +02:00
vals['move'] = move.id
2013-01-06 22:18:13 +01:00
else:
# prevent default value for field with set_move_field
for fname in move_fields():
vals.setdefault(fname, None)
2013-01-06 22:18:13 +01:00
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)
# Re-browse for cache alignment
moves = Move.browse(list(set(line.move for line in lines)))
Move.check_modify(moves)
Move.validate_move(moves)
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 = {}
2018-09-01 01:02:03 +02:00
else:
default = default.copy()
default.setdefault('move', None)
default.setdefault('reconciliation', None)
default.setdefault('reconciliations_delegated', [])
2012-09-24 22:17:29 +02:00
return super(Line, cls).copy(lines, default=default)
2015-03-02 23:34:41 +01:00
@classmethod
def view_toolbar_get(cls):
pool = Pool()
Template = pool.get('account.move.template')
toolbar = super(Line, cls).view_toolbar_get()
# Add a wizard entry for each templates
context = Transaction().context
company = context.get('company')
journal = context.get('journal')
period = context.get('period')
if company and journal and period:
templates = Template.search([
('company', '=', company),
('journal', '=', journal),
])
for template in templates:
action = toolbar['action']
# Use template id for action id to auto-select the template
action.append({
'name': template.name,
'type': 'ir.action.wizard',
'wiz_name': 'account.move.template.create',
'id': template.id,
})
return toolbar
2012-09-24 22:17:29 +02:00
@classmethod
2018-09-01 11:20:13 +02:00
def reconcile(
2019-04-12 19:43:53 +02:00
cls, *lines_list, date=None, writeoff=None, description=None,
delegate_to=None):
"""
Reconcile each list of lines together.
2018-09-01 11:20:13 +02:00
The writeoff keys are: date, method and description.
"""
2011-07-03 13:18:40 +02:00
pool = Pool()
2012-09-24 22:17:29 +02:00
Reconciliation = pool.get('account.move.reconciliation')
2019-04-12 19:43:53 +02:00
delegate_to = delegate_to.id if delegate_to else None
reconciliations = []
for lines in lines_list:
for line in lines:
if line.reconciliation:
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account.msg_line_already_reconciled',
line=line.rec_name))
lines = list(lines)
reconcile_account = None
reconcile_party = None
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
if amount:
move = cls._get_writeoff_move(
2018-09-01 11:20:13 +02:00
reconcile_account, reconcile_party, amount,
date, writeoff, description)
move.save()
lines += cls.search([
('move', '=', move.id),
('account', '=', reconcile_account.id),
('debit', '=', amount < Decimal('0.0') and - amount
or Decimal('0.0')),
('credit', '=', amount > Decimal('0.0') and amount
or Decimal('0.0')),
], limit=1)
reconciliations.append({
2021-03-21 22:38:05 +01:00
'company': reconcile_account.company,
'lines': [('add', [x.id for x in lines])],
'date': max(l.date for l in lines),
2019-04-12 19:43:53 +02:00
'delegate_to': delegate_to,
})
return Reconciliation.create(reconciliations)
@classmethod
def _get_writeoff_move(cls, reconcile_account, reconcile_party, amount,
2018-09-01 11:20:13 +02:00
date=None, writeoff=None, description=None):
pool = Pool()
2012-09-24 22:17:29 +02:00
Date = pool.get('ir.date')
Period = pool.get('account.period')
Move = pool.get('account.move')
if not date:
date = Date.today()
period_id = Period.find(reconcile_account.company.id, date=date)
2018-09-01 11:20:13 +02:00
account = None
journal = None
if writeoff:
2013-12-20 13:59:46 +01:00
if amount >= 0:
2018-09-01 11:20:13 +02:00
account = writeoff.debit_account
2013-12-20 13:59:46 +01:00
else:
2018-09-01 11:20:13 +02:00
account = writeoff.credit_account
journal = writeoff.journal
move = Move()
2021-03-21 22:38:05 +01:00
move.company = reconcile_account.company
move.journal = journal
move.period = period_id
move.date = date
move.description = description
lines = []
line = cls()
lines.append(line)
line.account = reconcile_account
line.party = reconcile_party
line.debit = -amount if amount < 0 else 0
line.credit = amount if amount > 0 else 0
line = cls()
lines.append(line)
line.account = account
line.party = (
reconcile_party if account and account.party_required else None)
line.debit = amount if amount > 0 else 0
line.credit = -amount if amount < 0 else 0
move.lines = lines
return move
2008-03-17 18:32:06 +01:00
2018-09-01 11:20:13 +02:00
class WriteOff(DeactivableMixin, ModelSQL, ModelView):
'Reconcile Write Off'
__name__ = 'account.move.reconcile.write_off'
company = fields.Many2One('company.company', "Company", required=True)
name = fields.Char("Name", required=True, translate=True)
journal = fields.Many2One('account.journal', "Journal", required=True,
domain=[('type', '=', 'write-off')],
context={
'company': Eval('company', -1),
},
depends=['company'])
2018-09-01 11:20:13 +02:00
credit_account = fields.Many2One('account.account', "Credit Account",
required=True,
domain=[
2019-03-24 18:00:01 +01:00
('type', '!=', None),
('closed', '!=', True),
2018-09-01 11:20:13 +02:00
('company', '=', Eval('company')),
],
depends=['company'])
debit_account = fields.Many2One('account.account', "Debit Account",
required=True,
domain=[
2019-03-24 18:00:01 +01:00
('type', '!=', None),
('closed', '!=', True),
2018-09-01 11:20:13 +02:00
('company', '=', Eval('company')),
],
depends=['company'])
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('name', 'ASC'))
2018-09-01 11:20:13 +02:00
@classmethod
def default_company(cls):
return Transaction().context.get('company')
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'),
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):
if (self.model
and self.model.__name__ == 'account.journal.period'
and self.record):
2012-01-02 17:54:33 +01:00
return 'open_'
return 'ask'
2012-09-24 22:17:29 +02:00
def default_ask(self, fields):
if (self.model
and self.model.__name__ == 'account.journal.period'
and self.record):
return {
'journal': self.record.journal.id,
'period': self.record.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 (self.model
and self.model.__name__ == 'account.journal.period'
and self.record):
journal = self.record.journal
period = self.record.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([{
'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
2012-01-02 17:54:33 +01:00
action['pyson_domain'] = PYSONEncoder().encode([
2012-09-24 22:17:29 +02:00
('journal', '=', journal.id),
('period', '=', period.id),
2021-04-10 13:55:35 +02:00
('company', '=', period.company.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,
2021-04-10 13:55:35 +02:00
'company': period.company.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):
pool = Pool()
FiscalYear = pool.get('account.fiscalyear')
context = Transaction().context
2008-03-18 13:20:12 +01:00
company_id = self.record.company.id if self.record else -1
date = context.get('date')
fiscalyear = context.get('fiscalyear')
if date:
fiscalyears = FiscalYear.search([
('start_date', '<=', date),
('end_date', '>=', date),
('company', '=', company_id),
],
order=[('start_date', 'DESC')],
limit=1)
elif fiscalyear:
fiscalyears = [FiscalYear(fiscalyear)]
else:
2012-09-24 22:17:29 +02:00
fiscalyears = FiscalYear.search([
('state', '=', 'open'),
('company', '=', company_id),
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]),
('account', '=', self.record.id if self.record else None),
('state', '=', 'valid'),
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']))
if self.record:
action['name'] += ' (%s)' % self.record.rec_name
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'
2018-09-01 11:20:13 +02:00
company = fields.Many2One('company.company', "Company", readonly=True)
writeoff = fields.Many2One('account.move.reconcile.write_off', "Write Off",
required=True,
2013-12-20 13:59:46 +01:00
domain=[
2018-09-01 11:20:13 +02:00
('company', '=', Eval('company')),
],
depends=['company'])
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'])
2018-09-01 11:20:13 +02:00
currency_digits = fields.Function(fields.Integer('Currency Digits'),
'on_change_with_currency_digits')
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
2018-09-01 11:20:13 +02:00
@fields.depends('company')
def on_change_with_currency_digits(self, name=None):
if self.company:
return self.company.currency.digits
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"
2008-03-19 22:13:51 +01:00
company = None
amount = Decimal('0.0')
for line in self.records:
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,
2018-09-01 11:20:13 +02:00
'company': company.id,
2013-08-21 18:19:26 +02:00
}
2012-09-24 22:17:29 +02:00
def transition_reconcile(self):
self.model.reconcile(
self.records,
writeoff=getattr(self.writeoff, 'writeoff', None),
date=getattr(self.writeoff, 'date', None),
description=getattr(self.writeoff, 'description', None))
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):
self.make_unreconciliation(self.records)
2019-11-18 12:24:37 +01:00
return 'end'
@classmethod
def make_unreconciliation(cls, lines):
pool = Pool()
Reconciliation = pool.get('account.move.reconciliation')
2012-09-24 22:17:29 +02:00
reconciliations = [x.reconciliation for x in lines if x.reconciliation]
if reconciliations:
Reconciliation.delete(reconciliations)
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'),
Button('Skip', 'next_', 'tryton-forward', validate=False),
2014-09-19 16:24:48 +02:00
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()
Rule = pool.get('ir.rule')
2014-09-19 16:24:48 +02:00
Line = pool.get('account.move.line')
line = Line.__table__()
Account = pool.get('account.account')
2019-03-24 18:00:01 +01:00
AccountType = pool.get('account.account.type')
2014-09-19 16:24:48 +02:00
account = Account.__table__()
2019-03-24 18:00:01 +01:00
account_type = AccountType.__table__()
cursor = Transaction().connection.cursor()
account_rule = Rule.query_get(Account.__name__)
2014-09-19 16:24:48 +02:00
if self.model and self.model.__name__ == 'account.move.line':
lines = [l for l in self.records if not l.reconciliation]
return list({l.account for l in lines if l.account.reconcile})
2014-09-19 16:24:48 +02:00
balance = line.debit - line.credit
cursor.execute(*line.join(account,
2019-03-24 18:00:01 +01:00
condition=line.account == account.id)
.join(account_type, condition=account.type == account_type.id)
.select(
2014-09-19 16:24:48 +02:00
account.id,
where=((line.reconciliation == Null) & account.reconcile
& account.id.in_(account_rule)),
2019-03-24 18:00:01 +01:00
group_by=[account.id,
account_type.receivable, account_type.payable],
having=((
Sum(Case((balance > 0, 1), else_=0)) > 0)
& (Sum(Case((balance < 0, 1), else_=0)) > 0)
2019-03-24 18:00:01 +01:00
| Case((account_type.receivable & ~account_type.payable,
Sum(balance) < 0),
else_=False)
| Case((account_type.payable & ~account_type.receivable,
Sum(balance) > 0),
else_=False)
)))
return [a for a, in cursor]
2014-09-19 16:24:48 +02:00
def get_parties(self, account, _balanced=False, party=None):
2014-09-19 16:24:48 +02:00
'Return a list party to reconcile for the account'
pool = Pool()
Line = pool.get('account.move.line')
line = Line.__table__()
cursor = Transaction().connection.cursor()
2014-09-19 16:24:48 +02:00
if self.model and self.model.__name__ == 'account.move.line':
lines = [l for l in self.records if not l.reconciliation]
return list({l.party for l in lines if l.account == account})
2014-09-19 16:24:48 +02:00
balance = line.debit - line.credit
2020-03-28 16:54:08 +01:00
if _balanced:
having = Sum(balance) == 0
else:
having = ((
Sum(Case((balance > 0, 1), else_=0)) > 0)
& (Sum(Case((balance < 0, 1), else_=0)) > 0)
| Case((account.type.receivable, Sum(balance) < 0),
else_=False)
| Case((account.type.payable, Sum(balance) > 0),
else_=False)
)
where = ((line.reconciliation == Null)
& (line.account == account.id))
if party:
where &= (line.party == party.id)
2014-09-19 16:24:48 +02:00
cursor.execute(*line.select(line.party,
where=where,
2014-09-19 16:24:48 +02:00
group_by=line.party,
2020-03-28 16:54:08 +01:00
having=having))
return [p for p, in cursor]
2014-09-19 16:24:48 +02:00
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,
2014-09-19 16:24:48 +02:00
2022-03-06 13:45:10 +01:00
with Transaction().set_context(_check_access=True):
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)
2014-09-19 16:24:48 +02:00
2022-03-06 13:45:10 +01:00
while not next_party():
if not next_account():
return 'end'
return 'show'
2014-09-19 16:24:48 +02:00
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['company'] = self.show.account.company.id
2014-09-19 16:24:48 +02:00
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_amount'] = Decimal(0)
2014-09-19 16:24:48 +02:00
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'
if self.model and self.model.__name__ == 'account.move.line':
requested = {
l for l in self.records
if l.account == self.show.account
and l.party == self.show.party}
else:
requested = None
2014-09-19 16:24:48 +02:00
currency = self.show.account.company.currency
2015-03-22 03:19:42 +01:00
chunk = config.getint('account', 'reconciliation_chunk', default=10)
2014-09-19 16:24:48 +02:00
# 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
2018-06-26 19:11:40 +02:00
for n in range(len(lines), 1, -1):
2014-09-19 16:24:48 +02:00
for comb_lines in combinations(lines, n):
if requested and not requested.intersection(comb_lines):
continue
2014-09-19 16:24:48 +02:00
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)
if not default and requested:
2018-06-26 19:11:40 +02:00
default = list(map(int, requested))
2014-09-19 16:24:48 +02:00
return default
def transition_reconcile(self):
pool = Pool()
Line = pool.get('account.move.line')
if self.show.lines:
Line.reconcile(self.show.lines,
date=self.show.date,
writeoff=self.show.write_off,
2014-09-19 16:24:48 +02:00
description=self.show.description)
if self.get_parties(self.show.account, party=self.show.party):
return 'show'
2014-09-19 16:24:48 +02:00
return 'next_'
class ReconcileShow(ModelView):
'Reconcile'
__name__ = 'account.reconcile.show'
company = fields.Many2One('company.company', "Company", readonly=True)
accounts = fields.Many2Many('account.account', None, None, 'Account',
2014-09-19 16:24:48 +02:00
readonly=True)
account = fields.Many2One('account.account', 'Account', readonly=True)
parties = fields.Many2Many(
'party.party', None, None, 'Parties', readonly=True,
context={
'company': Eval('company', -1),
},
depends=['company'])
party = fields.Many2One(
'party.party', 'Party', readonly=True,
context={
'company': Eval('company', -1),
},
depends=['company'])
2014-09-19 16:24:48 +02:00
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_amount', 0)),
'invisible': ~Eval('write_off_amount', 0),
2014-09-19 16:24:48 +02:00
}
_write_off_depends = ['write_off_amount']
2014-09-19 16:24:48 +02:00
write_off_amount = fields.Function(fields.Numeric('Amount',
2014-09-19 16:24:48 +02:00
digits=(16, Eval('currency_digits', 2)),
states=_write_off_states,
depends=_write_off_depends + ['currency_digits']),
'on_change_with_write_off_amount')
2014-09-19 16:24:48 +02:00
currency_digits = fields.Function(fields.Integer('Currency Digits'),
'on_change_with_currency_digits')
write_off = fields.Many2One(
'account.move.reconcile.write_off', "Write Off",
domain=[
('company', '=', Eval('company', -1)),
],
states=_write_off_states, depends=_write_off_depends + ['company'])
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={
'invisible': _write_off_states['invisible'],
}, depends=_write_off_depends)
2014-09-19 16:24:48 +02:00
@fields.depends('lines', 'currency_digits')
def on_change_with_write_off_amount(self, name=None):
digits = self.currency_digits or 0
exp = Decimal(str(10.0 ** -digits))
amount = sum(((l.debit - l.credit) for l in self.lines), Decimal(0))
return amount.quantize(exp)
2014-09-19 16:24:48 +02:00
@fields.depends('company')
2014-09-19 16:24:48 +02:00
def on_change_with_currency_digits(self, name=None):
if self.company:
return self.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'),
2015-03-10 18:27:06 +01:00
Button('OK', 'cancel', 'tryton-ok', default=True),
2014-11-10 23:35:54 +01:00
])
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()
Line = pool.get('account.move.line')
2019-11-18 12:24:37 +01:00
Warning = pool.get('res.user.warning')
Unreconcile = pool.get('account.move.unreconcile_lines', type='wizard')
2014-05-09 23:32:17 +02:00
moves = self.records
2019-11-18 12:24:37 +01:00
moves_w_delegation = {
m: [ml for ml in m.lines
if ml.reconciliation and ml.reconciliation.delegate_to]
for m in moves}
if any(dml for dml in moves_w_delegation.values()):
names = ', '.join(m.rec_name for m in
islice(moves_w_delegation.keys(), None, 5))
if len(moves_w_delegation) > 5:
names += '...'
key = '%s.cancel_delegated' % hashlib.md5(
str(list(moves_w_delegation)).encode('utf8')).hexdigest()
if Warning.check(key):
raise CancelDelegatedWarning(
key, gettext(
'account.msg_cancel_line_delegated', moves=names))
2014-05-09 23:32:17 +02:00
for move in moves:
2019-11-18 12:24:37 +01:00
if moves_w_delegation.get(move):
# Skip further warnings
with Transaction().set_user(0):
Unreconcile.make_unreconciliation(moves_w_delegation[move])
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, line.party)].append(line)
2018-06-26 19:11:40 +02:00
for lines in to_reconcile.values():
2014-05-09 23:32:17 +02:00
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')
2019-04-12 19:43:53 +02:00
class GroupLines(Wizard):
"Group Lines"
__name__ = 'account.move.line.group'
start = StateView('account.move.line.group.start',
'account.move_line_group_start_view_form', [
Button("Cancel", 'end', 'tryton-cancel'),
Button("Group", 'group', 'tryton-ok', default=True),
])
group = StateAction('account.act_move_form_grouping')
2019-04-12 19:43:53 +02:00
def do_group(self, action):
move, balance_line = self._group_lines(self.records)
action['res_id'] = [move.id]
return action, {}
2019-11-25 12:08:39 +01:00
def _group_lines(self, lines, date=None):
move, balance_line = self.group_lines(lines, self.start.journal, date)
move.description = self.start.description
move.save()
2019-11-25 12:08:39 +01:00
return move, balance_line
@classmethod
def group_lines(cls, lines, journal, date=None):
pool = Pool()
Line = pool.get('account.move.line')
2019-11-25 12:08:39 +01:00
grouping = cls.grouping(lines)
move, balance_line = cls.get_move(lines, grouping, journal, date)
2019-04-12 19:43:53 +02:00
move.save()
to_reconcile = defaultdict(list)
for line in chain(lines, move.lines):
if line.account.reconcile:
to_reconcile[line.account].append(line)
if balance_line:
balance_line.move = move
balance_line.save()
2019-04-12 19:43:53 +02:00
for lines in to_reconcile.values():
Line.reconcile(lines, delegate_to=balance_line)
2019-11-25 12:08:39 +01:00
return move, balance_line
2019-04-12 19:43:53 +02:00
@classmethod
def grouping(cls, lines):
if len(lines) == 1:
raise GroupLineError(gettext('account.msg_group_line_single'))
companies = set()
parties = set()
accounts = set()
second_currencies = set()
for line in lines:
if not cls.allow_grouping(line.account):
raise GroupLineError(gettext('account.msg_group_line'))
companies.add(line.move.company)
parties.add(line.party)
accounts.add(line.account)
second_currencies.add(line.second_currency)
try:
company, = companies
except ValueError:
raise GroupLineError(
gettext('account.msg_group_line_same_company'))
try:
party, = parties
except ValueError:
raise GroupLineError(
gettext('account.msg_group_line_many_parties'))
try:
second_currency, = second_currencies
except ValueError:
raise GroupLineError(
gettext('account.msg_group_line_same_second_currency'))
if len(accounts) > 2:
raise GroupLineError(
gettext('account.msg_group_line_maximum_account'))
return {
'company': company,
'party': party,
'second_currency': second_currency,
'accounts': accounts,
}
@classmethod
def allow_grouping(cls, account):
return account.type.payable or account.type.receivable
2019-11-25 12:08:39 +01:00
@classmethod
def get_move(cls, lines, grouping, journal, date=None):
2019-04-12 19:43:53 +02:00
pool = Pool()
Date = pool.get('ir.date')
Move = pool.get('account.move')
Period = pool.get('account.period')
Line = pool.get('account.move.line')
if not date:
date = Date.today()
2021-05-16 17:50:48 +02:00
company = grouping['company']
period = Period.find(company.id, date=date)
2019-04-12 19:43:53 +02:00
move = Move()
2021-05-16 17:50:48 +02:00
move.company = company
2019-04-12 19:43:53 +02:00
move.date = date
move.period = period
2019-11-25 12:08:39 +01:00
move.journal = journal
2019-04-12 19:43:53 +02:00
accounts = {a: 0 for a in grouping['accounts']}
amount_second_currency = 0
maturity_dates = {a: None for a in grouping['accounts']}
counterpart_lines = []
for line in lines:
if maturity_dates[line.account] and line.maturity_date:
2019-04-12 19:43:53 +02:00
maturity_dates[line.account] = min(
maturity_dates[line.account], line.maturity_date)
elif line.maturity_date:
2019-04-12 19:43:53 +02:00
maturity_dates[line.account] = line.maturity_date
2020-03-06 11:27:10 +01:00
cline = cls._counterpart_line(line)
accounts[cline.account] += cline.debit - cline.credit
if cline.amount_second_currency:
amount_second_currency += cline.amount_second_currency
counterpart_lines.append(cline)
2019-04-12 19:43:53 +02:00
move.lines = counterpart_lines
balance_line = None
if len(accounts) == 1:
account, = grouping['accounts']
amount = -accounts[account]
if amount:
balance_line = Line()
balance_line.account = account
else:
first, second = grouping['accounts']
if accounts[first] != accounts[second]:
balance_line = Line()
amount = -(accounts[first] + accounts[second])
if abs(accounts[first]) > abs(accounts[second]):
balance_line.account = first
else:
balance_line.account = second
if balance_line:
if balance_line.account.party_required:
balance_line.party = grouping['party']
if amount > 0:
balance_line.debit, balance_line.credit = amount, 0
else:
balance_line.debit, balance_line.credit = 0, -amount
if grouping['second_currency']:
balance_line.second_currency = grouping['second_currency']
balance_line.amount_second_currency = (
-amount_second_currency)
balance_line.maturity_date = maturity_dates[balance_line.account]
2019-04-12 19:43:53 +02:00
return move, balance_line
2019-11-25 12:08:39 +01:00
@classmethod
def _counterpart_line(cls, line):
2019-04-12 19:43:53 +02:00
pool = Pool()
Line = pool.get('account.move.line')
counterpart = Line()
counterpart.account = line.account
counterpart.debit = line.credit
counterpart.credit = line.debit
counterpart.party = line.party
counterpart.second_currency = line.second_currency
if line.second_currency:
counterpart.amount_second_currency = -line.amount_second_currency
else:
counterpart.amount_second_currency = None
return counterpart
class GroupLinesStart(ModelView):
"Group Lines"
__name__ = 'account.move.line.group.start'
journal = fields.Many2One('account.journal', "Journal", required=True)
description = fields.Char("Description")
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
2021-02-21 19:19:26 +01:00
def get_context(cls, records, header, data):
2021-03-16 22:13:51 +01:00
pool = Pool()
Company = pool.get('company.company')
context = Transaction().context
2021-02-21 19:19:26 +01:00
report_context = super().get_context(records, header, data)
2021-03-16 22:13:51 +01:00
report_context['company'] = Company(context['company'])
2015-01-20 18:49:53 +01:00
return report_context