2014-12-02 12:54:55 +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.
|
2016-09-28 14:44:59 +02:00
|
|
|
from collections import defaultdict
|
2021-12-11 17:58:42 +01:00
|
|
|
from decimal import Decimal
|
2021-05-28 09:03:22 +02:00
|
|
|
from itertools import groupby
|
2016-09-27 20:55:23 +02:00
|
|
|
|
|
|
|
|
from sql import Literal
|
|
|
|
|
|
2023-10-28 10:56:33 +02:00
|
|
|
from trytond.model import Check, Index, ModelSQL, ModelView, dualmethod, fields
|
2021-12-11 17:58:42 +01:00
|
|
|
from trytond.modules.currency.fields import Monetary
|
|
|
|
|
from trytond.pool import Pool, PoolMeta
|
2010-01-20 10:58:31 +01:00
|
|
|
from trytond.pyson import Eval, PYSONEncoder
|
2010-08-16 19:24:05 +02:00
|
|
|
from trytond.transaction import Transaction
|
2021-12-11 17:58:42 +01:00
|
|
|
from trytond.wizard import StateAction, Wizard
|
2021-08-21 09:09:55 +02:00
|
|
|
|
2008-05-23 15:04:38 +02:00
|
|
|
|
2009-02-28 16:58:26 +01:00
|
|
|
class Line(ModelSQL, ModelView):
|
2008-05-23 15:04:38 +02:00
|
|
|
'Analytic Line'
|
2012-09-24 22:11:39 +02:00
|
|
|
__name__ = 'analytic_account.line'
|
2021-08-21 09:09:55 +02:00
|
|
|
debit = Monetary(
|
|
|
|
|
"Debit", currency='currency', digits='currency', required=True)
|
|
|
|
|
credit = Monetary(
|
|
|
|
|
"Credit", currency='currency', digits='currency', required=True)
|
|
|
|
|
currency = fields.Function(fields.Many2One(
|
|
|
|
|
'currency.currency', "Currency"),
|
|
|
|
|
'on_change_with_currency')
|
2014-06-04 16:45:11 +02:00
|
|
|
company = fields.Function(fields.Many2One('company.company', 'Company'),
|
2016-09-27 19:36:00 +02:00
|
|
|
'on_change_with_company', searcher='search_company')
|
2022-10-11 00:44:49 +02:00
|
|
|
account = fields.Many2One(
|
|
|
|
|
'analytic_account.account', "Account", required=True,
|
|
|
|
|
domain=[
|
2023-05-17 14:46:52 +02:00
|
|
|
('type', '=', 'normal'),
|
2023-05-17 15:03:35 +02:00
|
|
|
('company', '=', Eval('company', -1)),
|
2022-04-08 19:07:12 +02:00
|
|
|
])
|
2008-05-23 15:04:38 +02:00
|
|
|
move_line = fields.Many2One('account.move.line', 'Account Move Line',
|
|
|
|
|
ondelete='CASCADE', required=True)
|
|
|
|
|
date = fields.Date('Date', required=True)
|
|
|
|
|
|
2012-09-24 22:11:39 +02:00
|
|
|
@classmethod
|
|
|
|
|
def __setup__(cls):
|
|
|
|
|
super(Line, cls).__setup__()
|
2015-06-22 16:59:04 +02:00
|
|
|
t = cls.__table__()
|
2012-09-24 22:11:39 +02:00
|
|
|
cls._sql_constraints += [
|
2019-01-14 23:52:58 +01:00
|
|
|
('credit_debit_',
|
|
|
|
|
Check(t, t.credit * t.debit == 0),
|
2018-12-29 14:20:29 +01:00
|
|
|
'account.msg_line_debit_credit'),
|
2012-09-24 22:11:39 +02:00
|
|
|
]
|
2022-10-11 00:44:49 +02:00
|
|
|
cls._sql_indexes.update({
|
|
|
|
|
Index(t, (t.account, Index.Equality())),
|
|
|
|
|
Index(t, (t.date, Index.Range())),
|
|
|
|
|
})
|
2012-09-24 22:11:39 +02:00
|
|
|
cls._order.insert(0, ('date', 'ASC'))
|
2008-05-23 15:04:38 +02:00
|
|
|
|
2012-09-24 22:11:39 +02:00
|
|
|
@classmethod
|
|
|
|
|
def __register__(cls, module_name):
|
|
|
|
|
super(Line, cls).__register__(module_name)
|
2018-07-24 22:01:07 +02:00
|
|
|
table = cls.__table_handler__(module_name)
|
2009-06-06 16:13:29 +02:00
|
|
|
|
2019-01-14 23:52:58 +01:00
|
|
|
# Migration from 5.0: replace credit_debit constraint by credit_debit_
|
|
|
|
|
table.drop_constraint('credit_debit')
|
|
|
|
|
|
2012-09-24 22:11:39 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def default_date():
|
|
|
|
|
Date = Pool().get('ir.date')
|
|
|
|
|
return Date.today()
|
2008-05-23 15:04:38 +02:00
|
|
|
|
2012-09-24 22:11:39 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def default_debit():
|
2012-03-23 18:38:52 +01:00
|
|
|
return Decimal(0)
|
|
|
|
|
|
2012-09-24 22:11:39 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def default_credit():
|
2012-03-23 18:38:52 +01:00
|
|
|
return Decimal(0)
|
|
|
|
|
|
2017-04-26 13:03:51 +02:00
|
|
|
@fields.depends('move_line', '_parent_move_line.account')
|
2021-08-21 09:09:55 +02:00
|
|
|
def on_change_with_currency(self, name=None):
|
2017-04-26 13:03:51 +02:00
|
|
|
if self.move_line and self.move_line.account:
|
2023-03-26 09:49:00 +02:00
|
|
|
return self.move_line.account.company.currency
|
2009-06-06 16:13:29 +02:00
|
|
|
|
2017-04-26 13:03:51 +02:00
|
|
|
@fields.depends('move_line', '_parent_move_line.account')
|
2014-06-04 16:45:11 +02:00
|
|
|
def on_change_with_company(self, name=None):
|
2017-04-26 13:03:51 +02:00
|
|
|
if self.move_line and self.move_line.account:
|
2023-03-26 09:49:00 +02:00
|
|
|
return self.move_line.account.company
|
2014-06-04 16:45:11 +02:00
|
|
|
|
2016-09-27 19:36:00 +02:00
|
|
|
@classmethod
|
|
|
|
|
def search_company(cls, name, clause):
|
2017-09-27 18:37:27 +02:00
|
|
|
return [('move_line.account.' + clause[0],) + tuple(clause[1:])]
|
2016-09-27 19:36:00 +02:00
|
|
|
|
2016-09-27 20:55:23 +02:00
|
|
|
@fields.depends('move_line', '_parent_move_line.date',
|
|
|
|
|
'_parent_move_line.debit', '_parent_move_line.credit')
|
|
|
|
|
def on_change_move_line(self):
|
|
|
|
|
if self.move_line:
|
|
|
|
|
self.date = self.move_line.date
|
|
|
|
|
self.debit = self.move_line.debit
|
|
|
|
|
self.credit = self.move_line.credit
|
|
|
|
|
|
2012-09-24 22:11:39 +02:00
|
|
|
@staticmethod
|
2013-09-06 22:33:59 +02:00
|
|
|
def query_get(table):
|
2008-05-23 15:04:38 +02:00
|
|
|
'''
|
|
|
|
|
Return SQL clause for analytic line depending of the context.
|
2013-09-06 22:33:59 +02:00
|
|
|
table is the SQL instance of the analytic_account_line table.
|
2008-05-23 15:04:38 +02:00
|
|
|
'''
|
2016-09-27 20:55:23 +02:00
|
|
|
clause = Literal(True)
|
2010-08-16 19:24:05 +02:00
|
|
|
if Transaction().context.get('start_date'):
|
2013-09-06 22:33:59 +02:00
|
|
|
clause &= table.date >= Transaction().context['start_date']
|
2010-08-16 19:24:05 +02:00
|
|
|
if Transaction().context.get('end_date'):
|
2013-09-06 22:33:59 +02:00
|
|
|
clause &= table.date <= Transaction().context['end_date']
|
|
|
|
|
return clause
|
2008-05-23 15:04:38 +02:00
|
|
|
|
2016-09-28 14:44:59 +02:00
|
|
|
@classmethod
|
|
|
|
|
def create(cls, vlist):
|
|
|
|
|
pool = Pool()
|
|
|
|
|
MoveLine = pool.get('account.move.line')
|
|
|
|
|
lines = super(Line, cls).create(vlist)
|
|
|
|
|
move_lines = [l.move_line for l in lines]
|
|
|
|
|
MoveLine.set_analytic_state(move_lines)
|
|
|
|
|
MoveLine.save(move_lines)
|
|
|
|
|
return lines
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def write(cls, *args):
|
|
|
|
|
pool = Pool()
|
|
|
|
|
MoveLine = pool.get('account.move.line')
|
|
|
|
|
super(Line, cls).write(*args)
|
|
|
|
|
lines = sum(args[0:None:2], [])
|
|
|
|
|
move_lines = [l.move_line for l in lines]
|
|
|
|
|
MoveLine.set_analytic_state(move_lines)
|
|
|
|
|
MoveLine.save(move_lines)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def delete(cls, lines):
|
|
|
|
|
pool = Pool()
|
|
|
|
|
MoveLine = pool.get('account.move.line')
|
|
|
|
|
move_lines = [l.move_line for l in lines]
|
|
|
|
|
super(Line, cls).delete(lines)
|
|
|
|
|
MoveLine.set_analytic_state(move_lines)
|
|
|
|
|
MoveLine.save(move_lines)
|
|
|
|
|
|
2008-05-23 15:04:38 +02:00
|
|
|
|
2018-06-26 19:11:45 +02:00
|
|
|
class Move(metaclass=PoolMeta):
|
2015-02-13 18:16:42 +01:00
|
|
|
__name__ = 'account.move'
|
|
|
|
|
|
2023-10-28 10:56:33 +02:00
|
|
|
@dualmethod
|
2016-09-28 14:44:59 +02:00
|
|
|
@ModelView.button
|
|
|
|
|
def post(cls, moves):
|
|
|
|
|
pool = Pool()
|
|
|
|
|
MoveLine = pool.get('account.move.line')
|
|
|
|
|
super(Move, cls).post(moves)
|
|
|
|
|
lines = [l for m in moves for l in m.lines]
|
2017-01-07 16:46:24 +01:00
|
|
|
MoveLine.apply_rule(lines)
|
2016-09-28 14:44:59 +02:00
|
|
|
MoveLine.set_analytic_state(lines)
|
|
|
|
|
MoveLine.save(lines)
|
|
|
|
|
|
2023-01-27 01:06:15 +01:00
|
|
|
def _cancel_default(self, reversal=False):
|
|
|
|
|
default = super()._cancel_default(reversal=reversal)
|
|
|
|
|
if reversal:
|
|
|
|
|
default['lines.analytic_lines.debit'] = (
|
|
|
|
|
lambda data: data['credit'])
|
|
|
|
|
default['lines.analytic_lines.credit'] = (
|
|
|
|
|
lambda data: data['debit'])
|
|
|
|
|
else:
|
|
|
|
|
default['lines.analytic_lines.debit'] = (
|
|
|
|
|
lambda data: data['debit'] * -1)
|
|
|
|
|
default['lines.analytic_lines.credit'] = (
|
|
|
|
|
lambda data: data['credit'] * -1)
|
|
|
|
|
return default
|
2015-02-13 18:16:42 +01:00
|
|
|
|
|
|
|
|
|
2022-10-23 10:19:38 +02:00
|
|
|
class MoveLine(metaclass=PoolMeta):
|
2012-09-24 22:11:39 +02:00
|
|
|
__name__ = 'account.move.line'
|
2008-05-23 15:04:38 +02:00
|
|
|
analytic_lines = fields.One2Many('analytic_account.line', 'move_line',
|
|
|
|
|
'Analytic Lines')
|
2016-09-28 14:44:59 +02:00
|
|
|
analytic_state = fields.Selection([
|
|
|
|
|
('draft', 'Draft'),
|
|
|
|
|
('valid', 'Valid'),
|
2022-10-11 00:44:49 +02:00
|
|
|
], "Analytic State", readonly=True, sort=False)
|
2016-09-28 14:44:59 +02:00
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def __setup__(cls):
|
|
|
|
|
super(MoveLine, cls).__setup__()
|
2020-06-06 14:21:57 +02:00
|
|
|
cls._buttons.update({
|
|
|
|
|
'apply_analytic_rules': {
|
|
|
|
|
'invisible': Eval('analytic_state') != 'draft',
|
|
|
|
|
'depends': ['analytic_state'],
|
|
|
|
|
},
|
|
|
|
|
})
|
2016-09-28 14:44:59 +02:00
|
|
|
cls._check_modify_exclude |= {'analytic_lines', 'analytic_state'}
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def default_analytic_state(cls):
|
|
|
|
|
return 'draft'
|
|
|
|
|
|
2017-01-07 16:46:24 +01:00
|
|
|
@property
|
|
|
|
|
def rule_pattern(self):
|
|
|
|
|
return {
|
|
|
|
|
'company': self.move.company.id,
|
|
|
|
|
'account': self.account.id,
|
|
|
|
|
'journal': self.move.journal.id,
|
|
|
|
|
'party': self.party.id if self.party else None,
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-24 18:00:01 +01:00
|
|
|
@property
|
|
|
|
|
def must_have_analytic(self):
|
|
|
|
|
"If the line must have analytic lines set"
|
2020-07-25 12:31:10 +02:00
|
|
|
pool = Pool()
|
|
|
|
|
FiscalYear = pool.get('account.fiscalyear')
|
2019-03-24 18:00:01 +01:00
|
|
|
if self.account.type:
|
2020-07-25 12:31:10 +02:00
|
|
|
return self.account.type.statement == 'income' and not (
|
|
|
|
|
# ignore balance move of non-deferral account
|
|
|
|
|
self.journal.type == 'situation'
|
|
|
|
|
and self.period.type == 'adjustment'
|
|
|
|
|
and isinstance(self.move.origin, FiscalYear))
|
2019-03-03 12:19:17 +01:00
|
|
|
|
2017-01-07 16:46:24 +01:00
|
|
|
@classmethod
|
|
|
|
|
def apply_rule(cls, lines):
|
|
|
|
|
pool = Pool()
|
|
|
|
|
Rule = pool.get('analytic_account.rule')
|
|
|
|
|
|
|
|
|
|
rules = Rule.search([])
|
|
|
|
|
|
|
|
|
|
for line in lines:
|
2019-03-24 18:00:01 +01:00
|
|
|
if not line.must_have_analytic:
|
2019-03-03 12:19:17 +01:00
|
|
|
continue
|
2017-01-07 16:46:24 +01:00
|
|
|
if line.analytic_lines:
|
|
|
|
|
continue
|
|
|
|
|
pattern = line.rule_pattern
|
|
|
|
|
for rule in rules:
|
|
|
|
|
if rule.match(pattern):
|
|
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
continue
|
|
|
|
|
analytic_lines = []
|
|
|
|
|
for entry in rule.analytic_accounts:
|
|
|
|
|
analytic_lines.extend(
|
2020-04-19 11:32:37 +02:00
|
|
|
entry.get_analytic_lines(line, line.date))
|
2017-01-07 16:46:24 +01:00
|
|
|
line.analytic_lines = analytic_lines
|
|
|
|
|
|
2016-09-28 14:44:59 +02:00
|
|
|
@classmethod
|
|
|
|
|
def set_analytic_state(cls, lines):
|
|
|
|
|
pool = Pool()
|
|
|
|
|
AnalyticAccount = pool.get('analytic_account.account')
|
|
|
|
|
|
|
|
|
|
roots = AnalyticAccount.search([
|
|
|
|
|
('parent', '=', None),
|
2021-05-28 09:03:22 +02:00
|
|
|
],
|
|
|
|
|
order=[('company', 'ASC')])
|
|
|
|
|
company2roots = {
|
|
|
|
|
company: set(roots)
|
|
|
|
|
for company, roots in groupby(roots, key=lambda r: r.company)}
|
2016-09-28 14:44:59 +02:00
|
|
|
|
|
|
|
|
for line in lines:
|
2019-03-24 18:00:01 +01:00
|
|
|
if not line.must_have_analytic:
|
2016-09-28 14:44:59 +02:00
|
|
|
if not line.analytic_lines:
|
|
|
|
|
line.analytic_state = 'valid'
|
|
|
|
|
else:
|
|
|
|
|
line.analytic_state = 'draft'
|
|
|
|
|
continue
|
|
|
|
|
amounts = defaultdict(Decimal)
|
|
|
|
|
for analytic_line in line.analytic_lines:
|
|
|
|
|
amount = analytic_line.debit - analytic_line.credit
|
|
|
|
|
amounts[analytic_line.account.root] += amount
|
2021-07-05 11:39:39 +02:00
|
|
|
roots = company2roots.get(line.move.company, set())
|
2018-06-26 19:11:45 +02:00
|
|
|
if not roots <= set(amounts.keys()):
|
2016-09-28 14:44:59 +02:00
|
|
|
line.analytic_state = 'draft'
|
|
|
|
|
continue
|
|
|
|
|
amount = line.debit - line.credit
|
2018-06-26 19:11:45 +02:00
|
|
|
for analytic_amount in amounts.values():
|
2016-09-28 14:44:59 +02:00
|
|
|
if analytic_amount != amount:
|
|
|
|
|
line.analytic_state = 'draft'
|
|
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
line.analytic_state = 'valid'
|
2008-05-23 15:04:38 +02:00
|
|
|
|
2020-06-06 14:21:57 +02:00
|
|
|
@classmethod
|
|
|
|
|
@ModelView.button
|
|
|
|
|
def apply_analytic_rules(cls, lines):
|
|
|
|
|
cls.apply_rule(lines)
|
|
|
|
|
cls.set_analytic_state(lines)
|
|
|
|
|
cls.save(lines)
|
|
|
|
|
|
2008-05-23 15:04:38 +02:00
|
|
|
|
|
|
|
|
class OpenAccount(Wizard):
|
|
|
|
|
'Open Account'
|
2012-09-24 22:11:39 +02:00
|
|
|
__name__ = 'analytic_account.line.open_account'
|
2012-01-02 18:00:00 +01:00
|
|
|
start_state = 'open_'
|
|
|
|
|
open_ = StateAction('analytic_account.act_line_form')
|
2008-05-23 15:04:38 +02:00
|
|
|
|
2012-09-24 22:11:39 +02:00
|
|
|
def do_open_(self, action):
|
2012-01-02 18:00:00 +01:00
|
|
|
action['pyson_domain'] = [
|
2020-07-07 23:56:09 +02:00
|
|
|
('account', '=', self.record.id if self.record else None),
|
2012-01-02 18:00:00 +01:00
|
|
|
]
|
2010-08-16 19:24:05 +02:00
|
|
|
if Transaction().context.get('start_date'):
|
2012-01-02 18:00:00 +01:00
|
|
|
action['pyson_domain'].append(
|
|
|
|
|
('date', '>=', Transaction().context['start_date'])
|
|
|
|
|
)
|
2010-08-16 19:24:05 +02:00
|
|
|
if Transaction().context.get('end_date'):
|
2012-01-02 18:00:00 +01:00
|
|
|
action['pyson_domain'].append(
|
|
|
|
|
('date', '<=', Transaction().context['end_date'])
|
|
|
|
|
)
|
2020-10-08 12:22:06 +02:00
|
|
|
if self.record:
|
|
|
|
|
action['name'] += ' (%s)' % self.record.rec_name
|
2012-01-02 18:00:00 +01:00
|
|
|
action['pyson_domain'] = PYSONEncoder().encode(action['pyson_domain'])
|
|
|
|
|
return action, {}
|
|
|
|
|
|
2012-09-24 22:11:39 +02:00
|
|
|
def transition_open_(self):
|
2012-01-02 18:00:00 +01:00
|
|
|
return 'end'
|