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): class AccountInvoice(models.Model):
_inherit = 'account.invoice' _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 @api.multi
def action_move_create(self): def action_move_create(self):
"""Invoked when validating the invoices.""" """Invoked when validating the invoices."""

View File

@ -2,6 +2,7 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # 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 AccountInvoiceLine(models.Model): class AccountInvoiceLine(models.Model):
@ -65,3 +66,43 @@ class AccountInvoiceLine(models.Model):
'target': 'new', 'target': 'new',
'context': ctx, '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>) # Copyright 2018-2019 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # 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): class AccountSpreadTemplate(models.Model):
@ -45,6 +46,16 @@ class AccountSpreadTemplate(models.Model):
('year', 'Year')], ('year', 'Year')],
help="Period length for the entries") help="Period length for the entries")
start_date = fields.Date() 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 @api.model
def default_get(self, fields): def default_get(self, fields):
@ -61,6 +72,14 @@ class AccountSpreadTemplate(models.Model):
res['spread_journal_id'] = default_journal.id res['spread_journal_id'] = default_journal.id
return res 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') @api.onchange('spread_type', 'company_id')
def onchange_spread_type(self): def onchange_spread_type(self):
company = self.company_id company = self.company_id
@ -107,3 +126,58 @@ class AccountSpreadTemplate(models.Model):
spread_vals['invoice_type'] = invoice_type spread_vals['invoice_type'] = invoice_type
return spread_vals 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 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. 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 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 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. 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 12.0.1.1.0
~~~~~~~~~~ ~~~~~~~~~~

View File

@ -71,6 +71,9 @@ Under Invoicing -> Configuration -> Accounting -> Spread Templates, create a new
* *Spread Balance Sheet Account* * *Spread Balance Sheet Account*
* *Expense/Revenue Account* This option visible if invoice line account is balance sheet account, user need to specify this too. * *Expense/Revenue Account* This option visible if invoice line account is balance sheet account, user need to specify this too.
* *Journal* * *Journal*
* *Auto assign template on invoice validate*
When creating a new Spread Costs/Revenues Board, select the right template. When creating a new Spread Costs/Revenues Board, select the right template.
This way the above fields will be copied to the Spread Board. 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> <field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record> </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> </data>
</odoo> </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_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_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_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_account_spread_cost_revenue
from . import test_compute_spread_board from . import test_compute_spread_board
from . import test_account_invoice_spread 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"/> <field name="spread_journal_id" domain="[('company_id', '=', company_id)]" widget="selection"/>
</group> </group>
</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> </sheet>
</form> </form>
</field> </field>