[12.0][IMP] account_spread_cost_revenue, auto spread on validate invoice
This commit is contained in:
parent
f5e892d89f
commit
03745d5c4a
@ -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."""
|
||||||
|
@ -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()
|
||||||
|
@ -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',
|
||||||
|
)
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
@ -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)
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user