[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):
|
||||
_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."""
|
||||
|
@ -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()
|
||||
|
@ -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',
|
||||
)
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
~~~~~~~~~~
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
|
@ -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
|
||||
|
@ -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"/>
|
||||
</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>
|
||||
|
Loading…
Reference in New Issue
Block a user