Add analytic state on move line
issue5900 review25661002
This commit is contained in:
@@ -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
|
||||
|
||||
+2
-1
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -67,5 +67,33 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="name">move_line_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="move_line_view_list">
|
||||
<field name="model">account.move.line</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">move_line_list</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_move_line_form_completion">
|
||||
<field name="name">Analytic Lines to Complete</field>
|
||||
<field name="res_model">account.move.line</field>
|
||||
<field name="domain"
|
||||
eval="[('analytic_state', '=', 'draft'), ('move_state', '=', 'posted')]"
|
||||
pyson="1"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view"
|
||||
id="act_move_line_form_completion_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="move_line_view_list"/>
|
||||
<field name="act_window" ref="act_move_line_form_completion"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view"
|
||||
id="act_move_line_form_completion_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="account.move_line_view_form"/>
|
||||
<field name="act_window" ref="act_move_line_form_completion"/>
|
||||
</record>
|
||||
<menuitem parent="account.menu_processing"
|
||||
action="act_move_line_form_completion" id="menu_completion"/>
|
||||
|
||||
</data>
|
||||
</tryton>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tree>
|
||||
<field name="move"/>
|
||||
<field name="origin"/>
|
||||
<field name="description"/>
|
||||
<field name="move_description"/>
|
||||
<field name="date"/>
|
||||
<field name="account"/>
|
||||
<field name="party"/>
|
||||
<field name="debit" sum="Debit"/>
|
||||
<field name="credit" sum="Credit"/>
|
||||
<field name="analytic_state"/>
|
||||
</tree>
|
||||
Reference in New Issue
Block a user