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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+