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:
@@ -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
|
||||
|
||||
@@ -9,6 +9,7 @@ from .line import *
|
||||
def register():
|
||||
Pool.register(
|
||||
Account,
|
||||
AccountDistribution,
|
||||
OpenChartAccountStart,
|
||||
AnalyticAccountEntry,
|
||||
Line,
|
||||
|
||||
+102
-2
@@ -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
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user