Files
tryton/modules/analytic_account/line.py
T

307 lines
10 KiB
Python
Raw Normal View History

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
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')
company = fields.Function(fields.Many2One('company.company', 'Company'),
2016-09-27 19:36:00 +02:00
'on_change_with_company', searcher='search_company')
account = fields.Many2One(
'analytic_account.account', "Account", required=True,
domain=[
2023-05-17 14:46:52 +02:00
('type', '=', 'normal'),
('company', '=', Eval('company', -1)),
])
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__()
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
]
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)
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():
return Decimal(0)
2012-09-24 22:11:39 +02:00
@staticmethod
def default_credit():
return Decimal(0)
@fields.depends('move_line', '_parent_move_line.account')
2021-08-21 09:09:55 +02:00
def on_change_with_currency(self, name=None):
if self.move_line and self.move_line.account:
2023-03-26 09:49:00 +02:00
return self.move_line.account.company.currency
@fields.depends('move_line', '_parent_move_line.account')
def on_change_with_company(self, name=None):
if self.move_line and self.move_line.account:
2023-03-26 09:49:00 +02:00
return self.move_line.account.company
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):
__name__ = 'account.move'
@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
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'),
], "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(
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'] = [
('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'])
)
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'