Add distribution account type

A new type of account is added for distribution.
It is used on Account Entry to yield the lines to create for a accounting line.

issue5908
review31551002
This commit is contained in:
Cédric Krier
2016-10-05 19:20:00 +02:00
parent fa7412390b
commit 57ce272f7c
8 changed files with 183 additions and 2 deletions
+2
View File
@@ -1,3 +1,5 @@
* Add get_analytic_lines on AnalyticAccountEntry
* Add distribution account type
* Add analytic state on move line
* Improve account tree creation
* Remove name, journal, reference, party and active from line
+1
View File
@@ -9,6 +9,7 @@ from .line import *
def register():
Pool.register(
Account,
AccountDistribution,
OpenChartAccountStart,
AnalyticAccountEntry,
Line,
+102 -2
View File
@@ -14,7 +14,8 @@ from trytond.pyson import Eval, If, PYSONEncoder, PYSONDecoder
from trytond.transaction import Transaction
from trytond.pool import Pool
__all__ = ['Account', 'OpenChartAccountStart', 'OpenChartAccount',
__all__ = ['Account', 'AccountDistribution',
'OpenChartAccountStart', 'OpenChartAccount',
'AnalyticAccountEntry', 'AnalyticMixin']
@@ -34,6 +35,7 @@ class Account(ModelSQL, ModelView):
('root', 'Root'),
('view', 'View'),
('normal', 'Normal'),
('distribution', 'Distribution'),
], 'Type', required=True)
root = fields.Many2One('analytic_account.account', 'Root', select=True,
domain=[
@@ -88,12 +90,28 @@ class Account(ModelSQL, ModelView):
},
depends=['type'],
help="Make this account mandatory when filling documents")
distributions = fields.One2Many(
'analytic_account.account.distribution', 'parent',
"Distributions",
states={
'invisible': Eval('type') != 'distribution',
'required': Eval('type') == 'distribution',
},
depends=['type'])
distribution_parents = fields.Many2Many(
'analytic_account.account.distribution', 'account', 'parent',
"Distribution Parents", readonly=True)
@classmethod
def __setup__(cls):
super(Account, cls).__setup__()
cls._order.insert(0, ('code', 'ASC'))
cls._order.insert(1, ('name', 'ASC'))
cls._error_messages.update({
'invalid_distribution': (
'The distribution sum of account "%(account)" '
'is not 100%.'),
})
@classmethod
def __register__(cls, module_name):
@@ -132,6 +150,17 @@ class Account(ModelSQL, ModelView):
def validate(cls, accounts):
super(Account, cls).validate(accounts)
cls.check_recursion(accounts)
cls.check_recursion(accounts, parent='distribution_parents')
for account in accounts:
account.check_distribution()
def check_distribution(self):
if self.type != 'distribution':
return
if sum((d.ratio for d in self.distributions)) != 1:
self.raise_user_error('invalid_distribution', {
'account': self.rec_name,
})
@fields.depends('company')
def on_change_with_currency(self, name=None):
@@ -269,6 +298,30 @@ class Account(ModelSQL, ModelView):
else:
return [(cls._rec_name,) + tuple(clause[1:])]
def distribute(self, amount):
"Return a list of (account, amount) distribution"
assert self.type in {'normal', 'distribution'}
if self.type == 'normal':
return [(self, amount)]
else:
result = []
remainder = amount
for distribution in self.distributions:
account = distribution.account
ratio = distribution.ratio
current_amount = self.currency.round(amount * ratio)
remainder -= current_amount
result.extend(account.distribute(current_amount))
if remainder:
i = 0
while remainder:
account, amount = result[i]
rounding = self.currency.rounding.copy_sign(remainder)
result[i] = (account, amount - rounding)
remainder -= rounding
i = (i + 1) % len(result)
return result
class OpenChartAccountStart(ModelView):
'Open Chart of Accounts'
@@ -298,6 +351,37 @@ class OpenChartAccount(Wizard):
return 'end'
class AccountDistribution(ModelView, ModelSQL):
"Analytic Account Distribution"
__name__ = 'analytic_account.account.distribution'
parent = fields.Many2One(
'analytic_account.account', "Parent", required=True, select=True)
root = fields.Function(
fields.Many2One('analytic_account.account', "Root"),
'on_change_with_root')
account = fields.Many2One(
'analytic_account.account', "Account", required=True,
domain=[
('root', '=', Eval('root', -1)),
],
depends=['root'])
ratio = fields.Numeric("Ratio", required=True,
domain=[
('ratio', '>=', 0),
('ratio', '<=', 1),
])
@classmethod
def __setup__(cls):
super(AccountDistribution, cls).__setup__()
cls._order.insert(0, ('ratio', 'DESC'))
@fields.depends('parent', '_parent_parent.root')
def on_change_with_root(self, name=None):
if self.parent:
return self.parent.root.id
class AnalyticAccountEntry(ModelView, ModelSQL):
'Analytic Account Entry'
__name__ = 'analytic.account.entry'
@@ -318,7 +402,7 @@ class AnalyticAccountEntry(ModelView, ModelSQL):
},
domain=[
('root', '=', Eval('root')),
('type', '=', 'normal'),
('type', 'in', ['normal', 'distribution']),
],
depends=['root', 'required', 'company'])
required = fields.Function(fields.Boolean('Required'),
@@ -394,6 +478,22 @@ class AnalyticAccountEntry(ModelView, ModelSQL):
def search_company(cls, name, clause):
return []
def get_analytic_lines(self, line, date):
"Yield analytic lines for the accounting line and the date"
pool = Pool()
AnalyticLine = pool.get('analytic_account.line')
if not self.account:
return
amount = line.debit or line.credit
for account, amount in self.account.distribute(amount):
analytic_line = AnalyticLine()
analytic_line.debit = amount if line.debit else Decimal(0)
analytic_line.credit = amount if line.credit else Decimal(0)
analytic_line.account = account
analytic_line.date = date
yield analytic_line
class AnalyticMixin(ModelSQL):
+30
View File
@@ -114,6 +114,36 @@ this repository contains the full copyright notices and license terms. -->
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.ui.view" id="account_distribution_view_form">
<field name="model">analytic_account.account.distribution</field>
<field name="type">form</field>
<field name="name">account_distribution_form</field>
</record>
<record model="ir.ui.view" id="account_distribution_view_list">
<field name="model">analytic_account.account.distribution</field>
<field name="type">tree</field>
<field name="name">account_distribution_list</field>
</record>
<record model="ir.model.access" id="access_account_distribution">
<field name="model"
search="[('model', '=', 'analytic_account.account.distribution')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_account_distribution_account_admin">
<field name="model"
search="[('model', '=', 'analytic_account.account.distribution')]"/>
<field name="group" ref="group_analytic_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.ui.view" id="open_chart_start_view_form">
<field name="model">analytic_account.open_chart.start</field>
<field name="type">form</field>
+21
View File
@@ -12,6 +12,7 @@ from trytond.pool import Pool
from trytond.modules.company.tests import create_company, set_company
from trytond.modules.account.tests import create_chart, get_fiscalyear
from trytond.modules.currency.tests import create_currency
class AnalyticAccountTestCase(ModuleTestCase):
@@ -211,6 +212,26 @@ class AnalyticAccountTestCase(ModuleTestCase):
self.assertEqual(expense_line.analytic_state, 'valid')
@with_transaction()
def test_account_distribute(self):
"Test account distribute"
pool = Pool()
Account = pool.get('analytic_account.account')
Distribution = pool.get('analytic_account.account.distribution')
currency = create_currency('usd')
account1 = Account(type='normal', currency=currency)
account2 = Account(type='normal', currency=currency)
account = Account(type='distribution', currency=currency)
account.distributions = [
Distribution(account=account1, ratio=Decimal('0.7')),
Distribution(account=account2, ratio=Decimal('0.3')),
]
self.assertListEqual(
account.distribute(Decimal('100.03')),
[(account1, Decimal('70.02')), (account2, Decimal('30.01'))])
def suite():
suite = trytond.tests.test_tryton.suite()
+14
View File
@@ -0,0 +1,14 @@
<?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. -->
<form>
<label name="parent"/>
<field name="parent" colspan="3"/>
<label name="account"/>
<field name="account"/>
<label name="ratio"/>
<group col="2" id="ratio">
<field name="ratio" factor="100" xexpand="0"/>
<label name="ratio" string="%" xalign="0.0" xexpand="1"/>
</group>
</form>
+10
View File
@@ -0,0 +1,10 @@
<?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 editable="bottom">
<field name="parent"/>
<field name="account"/>
<field name="ratio" factor="100">
<suffix string="%" name="ratio"/>
</field>
</tree>
+3
View File
@@ -27,6 +27,9 @@ this repository contains the full copyright notices and license terms. -->
<field name="childs" colspan="4"
view_ids="analytic_account.account_view_list2"/>
</page>
<page name="distributions">
<field name="distributions"/>
</page>
<page string="Notes" id="notes">
<field name="note"/>
</page>