2
0

[12.0][IMP] account_spread_cost_revenue, auto spread on validate invoice

This commit is contained in:
Kitti U 2020-05-17 21:37:40 +07:00 committed by Andrea Stirpe
parent f5e892d89f
commit 03745d5c4a
11 changed files with 338 additions and 1 deletions

View File

@ -7,6 +7,11 @@ from odoo import api, models
class AccountInvoice(models.Model):
_inherit = 'account.invoice'
def action_invoice_open(self):
for invoice in self:
invoice.invoice_line_ids.create_auto_spread()
return super().action_invoice_open()
@api.multi
def action_move_create(self):
"""Invoked when validating the invoices."""

View File

@ -2,6 +2,7 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class AccountInvoiceLine(models.Model):
@ -65,3 +66,43 @@ class AccountInvoiceLine(models.Model):
'target': 'new',
'context': ctx,
}
def create_auto_spread(self):
""" Create auto spread table for each invoice line, when needed """
def _filter_line(aline, iline):
""" Find matching template auto line with invoice line """
if aline.product_id and iline.product_id != aline.product_id:
return False
if aline.account_id and iline.account_id != aline.account_id:
return False
if aline.analytic_account_id and \
iline.account_analytic_id != aline.analytic_account_id:
return False
return True
for line in self:
if line.spread_check == 'linked':
continue
spread_type = (
'sale' if line.invoice_type in ['out_invoice', 'out_refund']
else 'purchase')
spread_auto = self.env['account.spread.template.auto'].search(
[('template_id.auto_spread', '=', True),
('template_id.spread_type', '=', spread_type)])
matched = spread_auto.filtered(lambda a, i=line: _filter_line(a, i))
template = matched.mapped('template_id')
if not template:
continue
elif len(template) > 1:
raise UserError(
_('Too many auto spread templates (%s) matched with the '
'invoice line, %s') % (len(template), line.display_name))
# Found auto spread template for this invoice line, create it
wizard = self.env['account.spread.invoice.line.link.wizard'].new({
'invoice_line_id': line.id,
'company_id': line.company_id.id,
'spread_action_type': 'template',
'template_id': template.id,
})
wizard.confirm()

View File

@ -1,7 +1,8 @@
# Copyright 2018-2019 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class AccountSpreadTemplate(models.Model):
@ -45,6 +46,16 @@ class AccountSpreadTemplate(models.Model):
('year', 'Year')],
help="Period length for the entries")
start_date = fields.Date()
auto_spread = fields.Boolean(
string='Auto assign template on invoice validate',
help="If checked, provide option to auto create spread during "
"invoice validation, based on product/account/analytic in invoice line."
)
auto_spread_ids = fields.One2many(
comodel_name='account.spread.template.auto',
string='Auto Spread On',
inverse_name='template_id',
)
@api.model
def default_get(self, fields):
@ -61,6 +72,14 @@ class AccountSpreadTemplate(models.Model):
res['spread_journal_id'] = default_journal.id
return res
@api.constrains('auto_spread', 'auto_spread_ids')
def _check_product_account(self):
for rec in self.filtered('auto_spread'):
for line in rec.auto_spread_ids:
if not line.product_id and not line.account_id:
raise UserError(_('Please select product and/or account '
'on auto spread options'))
@api.onchange('spread_type', 'company_id')
def onchange_spread_type(self):
company = self.company_id
@ -107,3 +126,58 @@ class AccountSpreadTemplate(models.Model):
spread_vals['invoice_type'] = invoice_type
return spread_vals
@api.constrains('auto_spread_ids', 'auto_spread')
def _check_auto_spread_ids_unique(self):
query = """
select product_id, account_id, analytic_account_id
from (
select product_id, account_id, analytic_account_id, count(*)
from account_spread_template_auto a
join account_spread_template b on a.template_id = b.id
where b.auto_spread = true and b.id in %s
group by product_id, account_id, analytic_account_id
) x where x.count > 1 """
self._cr.execute(query, [self._ids])
results = []
for res in self._cr.fetchall():
product = self.env['product.product'].browse(res[0])
account = self.env['account.account'].browse(res[1])
analytic = self.env['account.analytic.account'].browse(res[2])
results.append('%s / %s / %s' % (product.name, account.name, analytic.name))
if results:
raise UserError(
_('Followings are duplicated combinations,\n\n%s' % '\n'.join(results)))
class AccountSpreadTemplateAuto(models.Model):
_name = 'account.spread.template.auto'
_description = 'Auto create spread, based on product/account/analytic'
template_id = fields.Many2one(
comodel_name='account.spread.template',
string='Spread Template',
required=True,
ondelete='cascade',
index=True,
)
company_id = fields.Many2one(
related='template_id.company_id',
store=True,
)
name = fields.Char(
required=True,
default='/',
)
product_id = fields.Many2one(
comodel_name='product.product',
string='Product',
)
account_id = fields.Many2one(
comodel_name='account.account',
string='Account',
)
analytic_account_id = fields.Many2one(
comodel_name='account.analytic.account',
string='Analytic',
)

View File

@ -15,6 +15,8 @@ This module by default allows the spreading even before the receipt of the invoi
so that it is possible to work on the plan of the cost/revenue spreading. To disable this feature, on the form view of
the company disable the *Allow Spread Planning* option.
In Spread Template, there is also option to *Auto assign template on invoice validate*, based on the preset invoice line criteria.
On the form view of the company, the *Auto-post spread lines* option forces the account moves created
during the cost/revenue spreading to be automatically posted. When this option is false, the user can
enable/disable the automatic posting by the flag *Auto-post lines* present in the spread board.

View File

@ -1,3 +1,8 @@
12.0.2.0.0
~~~~~~~~~~
* [ENH] In spread template, add option to auto create spread on invoice validation
12.0.1.1.0
~~~~~~~~~~

View File

@ -71,6 +71,9 @@ Under Invoicing -> Configuration -> Accounting -> Spread Templates, create a new
* *Spread Balance Sheet Account*
* *Expense/Revenue Account* This option visible if invoice line account is balance sheet account, user need to specify this too.
* *Journal*
* *Auto assign template on invoice validate*
When creating a new Spread Costs/Revenues Board, select the right template.
This way the above fields will be copied to the Spread Board.
If *Auto assign template on invoice validate* is checked, this template will be used to auto create spread, if the underlining invoice match the preset product/account/analytic criteria.

View File

@ -9,5 +9,19 @@
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
<record id="account_spread_template_multi_company_rule" model="ir.rule">
<field name="name">Account Spread Template multi-company</field>
<field ref="model_account_spread_template" name="model_id"/>
<field eval="True" name="global"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
<record id="account_spread_template_auto_multi_company_rule" model="ir.rule">
<field name="name">Account Spread Tempalte Auto multi-company</field>
<field ref="model_account_spread_template_auto" name="model_id"/>
<field eval="True" name="global"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
</data>
</odoo>

View File

@ -5,3 +5,5 @@ access_account_spread_cost_revenue_line_full,Full access on account.spread.line,
access_account_spread_cost_revenue_line_read,Read access on account.spread.line,model_account_spread_line,account.group_account_user,1,0,0,0
access_account_spread_cost_revenue_template_full,Full access on account.spread.template,model_account_spread_template,account.group_account_manager,1,1,1,1
access_account_spread_cost_revenue_template_read,Read access on account.spread.template,model_account_spread_template,account.group_account_user,1,0,0,0
access_account_spread_cost_revenue_template_auto_full,Full access on account.spread.template.auto,model_account_spread_template_auto,account.group_account_manager,1,1,1,1
access_account_spread_cost_revenue_template_auto_read,Read access on account.spread.template.auto,model_account_spread_template_auto,account.group_account_user,1,0,0,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
5 access_account_spread_cost_revenue_line_read Read access on account.spread.line model_account_spread_line account.group_account_user 1 0 0 0
6 access_account_spread_cost_revenue_template_full Full access on account.spread.template model_account_spread_template account.group_account_manager 1 1 1 1
7 access_account_spread_cost_revenue_template_read Read access on account.spread.template model_account_spread_template account.group_account_user 1 0 0 0
8 access_account_spread_cost_revenue_template_auto_full Full access on account.spread.template.auto model_account_spread_template_auto account.group_account_manager 1 1 1 1
9 access_account_spread_cost_revenue_template_auto_read Read access on account.spread.template.auto model_account_spread_template_auto account.group_account_user 1 0 0 0

View File

@ -3,3 +3,4 @@
from . import test_account_spread_cost_revenue
from . import test_compute_spread_board
from . import test_account_invoice_spread
from . import test_account_invoice_auto_spread

View File

@ -0,0 +1,175 @@
# Copyright 2018-2019 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo.tools import convert_file
from odoo.modules.module import get_resource_path
from odoo.exceptions import UserError
from odoo.tests import common
class TestAccountInvoiceSpread(common.TransactionCase):
def _load(self, module, *args):
convert_file(
self.cr,
'account_spread_cost_revenue',
get_resource_path(module, *args),
{}, 'init', False, 'test', self.registry._assertion_report)
def setUp(self):
super().setUp()
self._load('account', 'test', 'account_minimal_test.xml')
type_receivable = self.env.ref('account.data_account_type_receivable')
type_payable = self.env.ref('account.data_account_type_payable')
type_revenue = self.env.ref('account.data_account_type_revenue')
self.invoice_account = self.env['account.account'].create({
'name': 'test_account_receivable',
'code': '123',
'user_type_id': type_receivable.id,
'reconcile': True
})
self.account_payable = self.env['account.account'].create({
'name': 'test_account_payable',
'code': '321',
'user_type_id': type_payable.id,
'reconcile': True
})
self.account_revenue = self.env['account.account'].create({
'name': 'test_account_revenue',
'code': '864',
'user_type_id': type_revenue.id,
'reconcile': True
})
self.invoice_line_account = self.account_payable
self.spread_account = self.env['account.account'].create({
'name': 'test spread account_payable',
'code': '765',
'user_type_id': type_payable.id,
'reconcile': True
})
partner = self.env['res.partner'].create({
'name': 'Partner Name',
'supplier': True,
})
# Purchase Invoice
self.invoice = self.env['account.invoice'].create({
'partner_id': partner.id,
'account_id': self.invoice_account.id,
'type': 'in_invoice',
})
self.invoice_line = self.env['account.invoice.line'].create({
'quantity': 1.0,
'price_unit': 1000.0,
'invoice_id': self.invoice.id,
'name': 'product that cost 1000',
'account_id': self.invoice_account.id,
})
# Sales Invoice
self.invoice_2 = self.env['account.invoice'].create({
'partner_id': partner.id,
'account_id': self.invoice_account.id,
'type': 'out_invoice',
})
self.invoice_line_2 = self.env['account.invoice.line'].create({
'quantity': 1.0,
'price_unit': 1000.0,
'invoice_id': self.invoice_2.id,
'name': 'product that cost 1000',
'account_id': self.invoice_line_account.id,
})
def test_01_no_auto_spread_sheet(self):
self.env['account.spread.template'].create({
'name': 'test',
'spread_type': 'purchase',
'period_number': 5,
'period_type': 'month',
'spread_account_id': self.account_payable.id,
'spread_journal_id': self.ref(
'account_spread_cost_revenue.expenses_journal'),
'auto_spread': False, # Auto Spread = False
'auto_spread_ids': [
(0, 0, {'account_id': self.invoice_account.id})]
})
self.assertFalse(self.invoice_line.spread_id)
self.invoice.action_invoice_open()
self.assertFalse(self.invoice_line.spread_id)
def test_02_new_auto_spread_sheet_purchase(self):
self.env['account.spread.template'].create({
'name': 'test 1',
'spread_type': 'purchase',
'period_number': 5,
'period_type': 'month',
'spread_account_id': self.account_payable.id,
'spread_journal_id': self.ref(
'account_spread_cost_revenue.expenses_journal'),
'auto_spread': True, # Auto Spread
'auto_spread_ids': [
(0, 0, {'account_id': self.invoice_account.id})]
})
template2 = self.env['account.spread.template'].create({
'name': 'test 2',
'spread_type': 'purchase',
'period_number': 5,
'period_type': 'month',
'spread_account_id': self.account_payable.id,
'spread_journal_id': self.ref(
'account_spread_cost_revenue.expenses_journal'),
'auto_spread': True, # Auto Spread
'auto_spread_ids': [
(0, 0, {'account_id': self.invoice_account.id})]
})
template2._check_auto_spread_ids_unique()
self.assertFalse(self.invoice_line.spread_id)
with self.assertRaises(UserError): # too many auto_spread_ids matched
self.invoice.action_invoice_open()
template2.auto_spread = False # Do not use this template
self.invoice.action_invoice_open()
self.assertTrue(self.invoice_line.spread_id)
spread_lines = self.invoice_line.spread_id.line_ids
self.assertTrue(spread_lines)
for line in spread_lines:
line.create_move()
self.assertTrue(line.move_id)
def test_03_new_auto_spread_sheet_sale(self):
self.env['account.spread.template'].create({
'name': 'test',
'spread_type': 'sale',
'period_number': 5,
'period_type': 'month',
'spread_account_id': self.account_revenue.id,
'spread_journal_id': self.ref(
'account_spread_cost_revenue.sales_journal'),
'auto_spread': True, # Auto Spread
'auto_spread_ids': [(0, 0, {'account_id': self.invoice_line_account.id})]
})
self.assertFalse(self.invoice_line_2.spread_id)
self.invoice_2.action_invoice_open()
self.assertTrue(self.invoice_line_2.spread_id)
spread_lines = self.invoice_line_2.spread_id.line_ids
self.assertTrue(spread_lines)
for line in spread_lines:
line.create_move()
self.assertTrue(line.move_id)

View File

@ -28,6 +28,21 @@
<field name="spread_journal_id" domain="[('company_id', '=', company_id)]" widget="selection"/>
</group>
</group>
<div>
<field name="auto_spread"/>
<label for="auto_spread"/>
</div>
<p attrs="{'invisible': [('auto_spread', '!=', True)]}">
Automatically use this spread template on invoice validation for invoice lines using below product and/or account and/or analytic,
</p>
<field name="auto_spread_ids" attrs="{'invisible': [('auto_spread', '!=', True)]}" nolabel="1">
<tree editable="bottom">
<field name="name"/>
<field name="product_id"/>
<field name="account_id"/>
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
</tree>
</field>
</sheet>
</form>
</field>