diff --git a/CHANGELOG b/CHANGELOG index 8cfa080caf..cd4592579e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Add analytic state on move line * Improve account tree creation * Remove name, journal, reference, party and active from line * Remove currency from account diff --git a/account.py b/account.py index 56923008e7..7869d5a6a7 100644 --- a/account.py +++ b/account.py @@ -86,7 +86,8 @@ class Account(ModelSQL, ModelView): mandatory = fields.Boolean('Mandatory', states={ 'invisible': Eval('type') != 'root', }, - depends=['type']) + depends=['type'], + help="Make this account mandatory when filling documents") @classmethod def __setup__(cls): diff --git a/line.py b/line.py index f6b58de373..01496acf62 100644 --- a/line.py +++ b/line.py @@ -1,6 +1,7 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from decimal import Decimal +from collections import defaultdict from sql import Literal @@ -119,6 +120,35 @@ class Line(ModelSQL, ModelView): clause &= table.date <= Transaction().context['end_date'] return clause + @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) + @classmethod def validate(cls, lines): super(Line, cls).validate(lines) @@ -138,6 +168,16 @@ class Move: __metaclass__ = PoolMeta __name__ = 'account.move' + @classmethod + @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] + MoveLine.set_analytic_state(lines) + MoveLine.save(lines) + def cancel(self, default=None): 'Reverse credit/debit of analytic lines' pool = Pool() @@ -157,6 +197,76 @@ class MoveLine(ModelSQL, ModelView): __name__ = 'account.move.line' analytic_lines = fields.One2Many('analytic_account.line', 'move_line', 'Analytic Lines') + analytic_state = fields.Selection([ + ('draft', 'Draft'), + ('valid', 'Valid'), + ], 'Analytic State', readonly=True, select=True) + + @classmethod + def __setup__(cls): + super(MoveLine, cls).__setup__() + cls._check_modify_exclude |= {'analytic_lines', 'analytic_state'} + + @classmethod + def __register__(cls, module_name): + TableHandler = backend.get('TableHandler') + table = TableHandler(cls, module_name) + cursor = Transaction().connection.cursor() + sql_table = cls.__table__() + + state_exist = table.column_exist('analytic_state') + + super(MoveLine, cls).__register__(module_name) + + # Migration from 4.0: add analytic_state + if not state_exist: + cursor.execute( + *sql_table.update([sql_table.analytic_state], ['valid'])) + + @classmethod + def default_analytic_state(cls): + return 'draft' + + @classmethod + def set_analytic_state(cls, lines): + pool = Pool() + AccountType = pool.get('account.account.type') + AnalyticAccount = pool.get('analytic_account.account') + + income_types = AccountType.search([ + ('income_statement', '=', True), + ]) + income_types = AccountType.search([ + ('parent', 'child_of', [t.id for t in income_types]), + ]) + income_types = set(income_types) + + roots = AnalyticAccount.search([ + ('parent', '=', None), + ]) + roots = set(roots) + + for line in lines: + if line.account.type not in income_types: + 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 + if not roots <= set(amounts.iterkeys()): + line.analytic_state = 'draft' + continue + amount = line.debit - line.credit + for analytic_amount in amounts.itervalues(): + if analytic_amount != amount: + line.analytic_state = 'draft' + break + else: + line.analytic_state = 'valid' class OpenAccount(Wizard): diff --git a/line.xml b/line.xml index 0cdffbb982..965e1c5ad6 100644 --- a/line.xml +++ b/line.xml @@ -67,5 +67,33 @@ this repository contains the full copyright notices and license terms. --> move_line_form + + account.move.line + tree + move_line_list + + + + Analytic Lines to Complete + account.move.line + + + + + + + + + + + + + + diff --git a/tests/test_analytic_account.py b/tests/test_analytic_account.py index 0047af27de..1bd1441934 100644 --- a/tests/test_analytic_account.py +++ b/tests/test_analytic_account.py @@ -134,6 +134,83 @@ class AnalyticAccountTestCase(ModuleTestCase): (Decimal(30), Decimal(100))) self.assertEqual(analytic_account.balance, Decimal(70)) + @with_transaction() + def test_move_line_state(self): + "Test move line state" + pool = Pool() + Party = pool.get('party.party') + AnalyticAccount = pool.get('analytic_account.account') + AnalyticLine = pool.get('analytic_account.line') + Journal = pool.get('account.journal') + Account = pool.get('account.account') + Move = pool.get('account.move') + MoveLine = pool.get('account.move.line') + + party = Party(name='Party') + party.save() + company = create_company() + with set_company(company): + root, = AnalyticAccount.create([{ + 'type': 'root', + 'name': 'Root', + }]) + analytic_account1, analytic_account2 = AnalyticAccount.create([{ + 'type': 'normal', + 'name': 'Analytic Account 1', + 'parent': root.id, + 'root': root.id, + }, { + 'type': 'normal', + 'name': 'Analytic Account 2', + 'parent': root.id, + 'root': root.id, + }]) + create_chart(company) + fiscalyear = get_fiscalyear(company) + fiscalyear.save() + fiscalyear.create_period([fiscalyear]) + period = fiscalyear.periods[0] + journal_expense, = Journal.search([ + ('code', '=', 'EXP'), + ]) + expense, = Account.search([ + ('kind', '=', 'expense'), + ]) + payable, = Account.search([ + ('kind', '=', 'payable'), + ]) + + move = Move() + move.period = period + move.journal = journal_expense + move.date = period.start_date + move.lines = [ + MoveLine(account=expense, debit=Decimal(100)), + MoveLine(account=payable, credit=Decimal(100), party=party), + ] + move.save() + Move.post([move]) + + expense_line, = [l for l in move.lines if l.account == expense] + payable_line, = [l for l in move.lines if l.account == payable] + + self.assertEqual(expense_line.analytic_state, 'draft') + self.assertEqual(payable_line.analytic_state, 'valid') + + expense_line.analytic_lines = [ + AnalyticLine( + account=analytic_account1, name='Test', + debit=Decimal(50), journal=journal_expense, + date=period.start_date), + AnalyticLine( + account=analytic_account2, name='Test', + debit=Decimal(50), journal=journal_expense, + date=period.start_date), + ] + expense_line.save() + + self.assertEqual(expense_line.analytic_state, 'valid') + def suite(): suite = trytond.tests.test_tryton.suite() diff --git a/view/move_line_list.xml b/view/move_line_list.xml new file mode 100644 index 0000000000..6c5e549056 --- /dev/null +++ b/view/move_line_list.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + +