[MIG] base_exception: Migration to 11.0
parent
401b114771
commit
877487608e
|
@ -1,19 +1,24 @@
|
|||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
||||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||
:target: https://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||
:alt: License: AGPL-3
|
||||
|
||||
==============
|
||||
Base Exception
|
||||
==============
|
||||
|
||||
This module provide an abstract model to manage customizable exceptions to be applied on different models (sale order, invoice, ...). It is not usefull for itself. You can see an example of implementation in the 'sale_exception' module. (sale-workflow repository).
|
||||
This module provide an abstract model to manage customizable
|
||||
exceptions to be applied on different models (sale order, invoice, ...).
|
||||
|
||||
It is not useful for itself. You can see an example of implementation
|
||||
in the 'sale_exception' module. (sale-workflow repository) or
|
||||
'purchase_exception' module (purchase-workflow repository).
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
|
||||
:alt: Try me on Runbot
|
||||
:target: https://runbot.odoo-community.org/runbot/149/10.0
|
||||
:target: https://runbot.odoo-community.org/runbot/149/11.0
|
||||
|
||||
|
||||
Bug Tracker
|
||||
|
@ -22,11 +27,7 @@ Bug Tracker
|
|||
Bugs are tracked on `GitHub Issues
|
||||
<https://github.com/OCA/server-tools/issues>`_. In case of trouble, please
|
||||
check there if your issue has already been reported. If you spotted it first,
|
||||
help us smashing it by providing a detailed and welcomed `feedback
|
||||
<https://github.com/OCA/
|
||||
server-tools/issues/new?body=module:%20
|
||||
base_exception%0Aversion:%20
|
||||
10.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
||||
help us smash it by providing detailed and welcomed feedback.
|
||||
|
||||
Images
|
||||
------
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# © 2011 Raphaël Valyi, Renato Lima, Guewen Baconnier, Sodexis
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
# Copyright 2011 Raphaël Valyi, Renato Lima, Guewen Baconnier, Sodexis
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from . import wizard, models
|
||||
from .tests import test_tmp_model
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# © 2011 Raphaël Valyi, Renato Lima, Guewen Baconnier, Sodexis
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
# Copyright 2011 Raphaël Valyi, Renato Lima, Guewen Baconnier, Sodexis
|
||||
# Copyright 2017 Akretion (http://www.akretion.com)
|
||||
# Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com>
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
{'name': 'Exception Rule',
|
||||
'version': '10.0.1.0.0',
|
||||
{
|
||||
'name': 'Exception Rule',
|
||||
'version': '11.0.1.0.0',
|
||||
'category': 'Generic Modules',
|
||||
'summary': """This module provide an abstract model to manage customizable
|
||||
'summary': """
|
||||
This module provide an abstract model to manage customizable
|
||||
exceptions to be applied on different models (sale order, invoice, ...)""",
|
||||
'author': "Akretion, Sodexis, Camptocamp, Odoo Community Association (OCA)",
|
||||
'author':
|
||||
"Akretion, Sodexis, Camptocamp, Odoo Community Association (OCA)",
|
||||
'website': 'http://www.akretion.com',
|
||||
'depends': ['base_setup'],
|
||||
'license': 'AGPL-3',
|
||||
|
@ -18,4 +22,4 @@
|
|||
'views/base_exception_view.xml',
|
||||
],
|
||||
'installable': True,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# © 2011 Raphaël Valyi, Renato Lima, Guewen Baconnier, Sodexis
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from . import base_exception
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# © 2011 Raphaël Valyi, Renato Lima, Guewen Baconnier, Sodexis
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
# Copyright 2011 Raphaël Valyi, Renato Lima, Guewen Baconnier, Sodexis
|
||||
# Copyright 2017 Akretion (http://www.akretion.com)
|
||||
# Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com>
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
import time
|
||||
from functools import wraps
|
||||
|
@ -42,6 +43,12 @@ class ExceptionRule(models.Model):
|
|||
selection=[],
|
||||
string='Apply on', required=True)
|
||||
active = fields.Boolean('Active')
|
||||
next_state = fields.Char(
|
||||
'Next state',
|
||||
help="If we detect exception we set de state of object (ex purchase) "
|
||||
"to the next_state (ex 'to approve'). If there are more than one "
|
||||
"exception detected and all have a value for next_state, we use"
|
||||
"the exception having the smallest sequence value")
|
||||
code = fields.Text(
|
||||
'Python Code',
|
||||
help="Python code executed to check if the exception apply or "
|
||||
|
@ -63,6 +70,26 @@ class ExceptionRule(models.Model):
|
|||
# - context: current context
|
||||
""")
|
||||
|
||||
@api.constrains('next_state')
|
||||
def _check_next_state_value(self):
|
||||
""" Ensure that the next_state value is in the state values of
|
||||
destination model """
|
||||
for rule in self:
|
||||
if rule.next_state:
|
||||
select_vals = self.env[
|
||||
rule.model].fields_get()[
|
||||
'state']['selection']
|
||||
if rule.next_state\
|
||||
not in [s[0] for s in select_vals]:
|
||||
raise ValidationError(
|
||||
_('The value "%s" you chose for the "next state" '
|
||||
'field state of "%s" is wrong.'
|
||||
' Value must be in this list %s')
|
||||
% (rule.next_state,
|
||||
rule.model,
|
||||
select_vals)
|
||||
)
|
||||
|
||||
|
||||
class BaseException(models.AbstractModel):
|
||||
_name = 'base.exception'
|
||||
|
@ -182,7 +209,7 @@ class BaseException(models.AbstractModel):
|
|||
space,
|
||||
mode='exec',
|
||||
nocopy=True) # nocopy allows to return 'result'
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
raise UserError(
|
||||
_('Error when evaluating the exception.rule '
|
||||
'rule:\n %s \n(%s)') % (rule.name, e))
|
||||
|
@ -193,9 +220,16 @@ class BaseException(models.AbstractModel):
|
|||
sub_exceptions):
|
||||
self.ensure_one()
|
||||
exception_ids = []
|
||||
next_state_rule = False
|
||||
for rule in model_exceptions:
|
||||
if self._rule_eval(rule, self.rule_group, self):
|
||||
exception_ids.append(rule.id)
|
||||
if rule.next_state:
|
||||
if not next_state_rule:
|
||||
next_state_rule = rule
|
||||
elif next_state_rule and\
|
||||
rule.sequence < next_state_rule.sequence:
|
||||
next_state_rule = rule
|
||||
if sub_exceptions:
|
||||
for obj_line in self._get_lines():
|
||||
for rule in sub_exceptions:
|
||||
|
@ -207,6 +241,9 @@ class BaseException(models.AbstractModel):
|
|||
group_line = self.rule_group + '_line'
|
||||
if self._rule_eval(rule, group_line, obj_line):
|
||||
exception_ids.append(rule.id)
|
||||
# set object to next state
|
||||
if next_state_rule:
|
||||
self.state = next_state_rule.next_state
|
||||
return exception_ids
|
||||
|
||||
@implemented_by_base_exception
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
|
||||
"access_exception_rule","base.exception","model_exception_rule","base.group_user",1,0,0,0
|
||||
"access_exception_rule_manager","base.exception","model_exception_rule","base_exception.group_exception_rule_manager",1,1,1,1
|
||||
"access_base_exception","base.exception","model_base_exception","base.group_user",1,0,0,0
|
||||
"access_base_exception_manager","base.exception","model_base_exception","base_exception.group_exception_rule_manager",1,1,1,1
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_exception_rule,base.exception,model_exception_rule,base.group_user,1,0,0,0
|
||||
access_exception_rule_manager,base.exception,model_exception_rule,base_exception.group_exception_rule_manager,1,1,1,1
|
||||
access_base_exception,base.exception,model_base_exception,base.group_user,1,0,0,0
|
||||
access_base_exception_manager,base.exception,model_base_exception,base_exception.group_exception_rule_manager,1,1,1,1
|
||||
access_base_exception_test_purchase,access_base_exception_test_purchase,model_base_exception_test_purchase,base.group_system,1,1,1,1
|
||||
access_base_exception_test_model_line,access_base_exception_test_model_line,model_base_exception_test_model_line,base.group_system,1,1,1,1
|
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
from . import test_tmp_model
|
||||
from . import test_base_exception
|
|
@ -0,0 +1,83 @@
|
|||
# Copyright 2016 Akretion Mourad EL HADJ MIMOUNE
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo.tests import common
|
||||
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# @common.at_install(False)
|
||||
# @common.post_install(True)
|
||||
class TestBaseException(common.TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestBaseException, self).setUp()
|
||||
|
||||
self.base_exception = self.env['base.exception']
|
||||
self.exception_rule = self.env['exception.rule']
|
||||
self.exception_confirm = self.env['exception.rule.confirm']
|
||||
|
||||
self.exception_rule._fields['rule_group'].selection.append(
|
||||
('test_base', 'test base exception')
|
||||
)
|
||||
self.exception_rule._fields['model'].selection.append(
|
||||
('base.exception.test.purchase',
|
||||
'base.exception.test.purchase')
|
||||
)
|
||||
self.exception_rule._fields['model'].selection.append(
|
||||
('base.exception.test.purchase.line',
|
||||
'base.exception.test.purchase.line')
|
||||
)
|
||||
self.exceptionnozip = self.env['exception.rule'].create({
|
||||
'name': "No ZIP code on destination",
|
||||
'sequence': 10,
|
||||
'rule_group': "test_base",
|
||||
'model': "base.exception.test.purchase",
|
||||
'code': """if not test_base.partner_id.zip:
|
||||
failed=True""",
|
||||
})
|
||||
self.exceptionno_minorder = self.env['exception.rule'].create({
|
||||
'name': "Min order except",
|
||||
'sequence': 10,
|
||||
'rule_group': "test_base",
|
||||
'model': "base.exception.test.purchase",
|
||||
'code': """if test_base.amount_total <= 200.0:
|
||||
failed=True""",
|
||||
})
|
||||
|
||||
self.exceptionno_lineqty = self.env['exception.rule'].create({
|
||||
'name': "Qty > 0",
|
||||
'sequence': 10,
|
||||
'rule_group': "test_base",
|
||||
'model': "base.exception.test.purchase.line",
|
||||
'code': """if test_base_line.qty <= 0:
|
||||
failed=True"""})
|
||||
|
||||
def test_sale_order_exception(self):
|
||||
partner = self.env.ref('base.res_partner_1')
|
||||
partner.zip = False
|
||||
potest1 = self.env['base.exception.test.purchase'].create({
|
||||
'name': 'Test base exception to basic purchase',
|
||||
'partner_id': partner.id,
|
||||
'line_ids': [(0, 0, {'name': "line test",
|
||||
'amount': 120.0,
|
||||
'qty': 1.5})],
|
||||
})
|
||||
|
||||
potest1.button_confirm()
|
||||
# Set ignore_exception flag (Done after ignore is selected at wizard)
|
||||
potest1.ignore_exception = True
|
||||
potest1.button_confirm()
|
||||
self.assertTrue(potest1.state == 'purchase')
|
||||
# Simulation the opening of the wizard exception_confirm and
|
||||
# set ignore_exception to True
|
||||
except_confirm = self.exception_confirm.with_context(
|
||||
{
|
||||
'active_id': potest1.id,
|
||||
'active_ids': [potest1.id],
|
||||
'active_model': potest1._name
|
||||
}).new({'ignore': True})
|
||||
except_confirm.action_confirm()
|
||||
self.assertTrue(potest1.ignore_exception)
|
|
@ -0,0 +1,71 @@
|
|||
# Copyright 2017 Akretion (http://www.akretion.com)
|
||||
# Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com>
|
||||
|
||||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class PurchaseTest(models.Model):
|
||||
_inherit = 'base.exception'
|
||||
_name = "base.exception.test.purchase"
|
||||
_description = "Base Ecxeption Test Model"
|
||||
|
||||
rule_group = fields.Selection(
|
||||
selection_add=[('test_base', 'test')],
|
||||
default='test_base',
|
||||
)
|
||||
name = fields.Char(required=True)
|
||||
user_id = fields.Many2one('res.users', string='Responsible')
|
||||
state = fields.Selection(
|
||||
[('draft', 'New'), ('cancel', 'Cancelled'),
|
||||
('purchase', 'Purchase'),
|
||||
('to approve', 'To approve'), ('done', 'Done')],
|
||||
string="Status", readonly=True, default='draft')
|
||||
active = fields.Boolean(default=True)
|
||||
partner_id = fields.Many2one('res.partner', string='Partner')
|
||||
line_ids = fields.One2many('base.exception.test.model.line', 'lead_id')
|
||||
amount_total = fields.Float(compute='_compute_amount_total', store=True)
|
||||
|
||||
@api.depends('line_ids')
|
||||
def _compute_amount_total(self):
|
||||
for record in self:
|
||||
for line in record.line_ids:
|
||||
record.amount_total += line.amount * line.qty
|
||||
|
||||
@api.constrains('ignore_exception', 'line_ids', 'state')
|
||||
def test_purchase_check_exception(self):
|
||||
orders = self.filtered(lambda s: s.state == 'purchase')
|
||||
if orders:
|
||||
orders._check_exception()
|
||||
|
||||
@api.multi
|
||||
def button_approve(self, force=False):
|
||||
self.write({'state': 'to approve'})
|
||||
return {}
|
||||
|
||||
@api.multi
|
||||
def button_draft(self):
|
||||
self.write({'state': 'draft'})
|
||||
return {}
|
||||
|
||||
@api.multi
|
||||
def button_confirm(self):
|
||||
self.write({'state': 'purchase'})
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def button_cancel(self):
|
||||
self.write({'state': 'cancel'})
|
||||
|
||||
def test_base_get_lines(self):
|
||||
self.ensure_one()
|
||||
return self.line_ids
|
||||
|
||||
|
||||
class LineTest(models.Model):
|
||||
_name = "base.exception.test.model.line"
|
||||
_description = "Base Ecxeption Test Model Line"
|
||||
|
||||
name = fields.Char()
|
||||
lead_id = fields.Many2one('base.exception.test.model', ondelete='cascade')
|
||||
qty = fields.Float()
|
||||
amount = fields.Float()
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version="1.0" ?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_exception_rule_tree" model="ir.ui.view">
|
||||
<field name="name">exception.rule.tree</field>
|
||||
<field name="model">exception.rule</field>
|
||||
|
@ -31,6 +30,7 @@
|
|||
<group colspan="4" col="2" groups="base.group_system">
|
||||
<field name="rule_group"/>
|
||||
<field name="model"/>
|
||||
<field name="next_state"/>
|
||||
<field name="code"/>
|
||||
</group>
|
||||
</form>
|
||||
|
@ -47,5 +47,4 @@
|
|||
</record>
|
||||
|
||||
<menuitem action="action_exception_rule_tree" id="menu_action_exception" parent="base.menu_custom" sequence="90" />
|
||||
|
||||
</odoo>
|
||||
|
|
|
@ -1,5 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# © 2011 Raphaël Valyi, Renato Lima, Guewen Baconnier, Sodexis
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from . import base_exception_confirm
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# © 2011 Raphaël Valyi, Renato Lima, Guewen Baconnier, Sodexis
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
# Copyright 2011 Raphaël Valyi, Renato Lima, Guewen Baconnier, Sodexis
|
||||
# Copyright 2017 Akretion (http://www.akretion.com)
|
||||
# Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com>
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="view_exception_rule_confirm" model="ir.ui.view">
|
||||
<field name="name">Exceptions Rules</field>
|
||||
<field name="model">exception.rule.confirm</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Blocked in draft due to exceptions" version="7.0">
|
||||
<form string="Outstanding exceptions to manager" version="7.0">
|
||||
<group>
|
||||
<field name="exception_ids" nolabel="1" colspan="4">
|
||||
<tree string="Exceptions Rules">
|
||||
|
@ -15,18 +13,20 @@
|
|||
</tree>
|
||||
</field>
|
||||
<newline/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="ignore" groups='base_exception.group_exception_rule_manager'/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="action_confirm" string="_Close"
|
||||
colspan="1" type="object" icon="gtk-ok" />
|
||||
colspan="1" type="object" class="btn-primary"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_exception_rule_confirm" model="ir.actions.act_window">
|
||||
<field name="name">Blocked in draft due to exceptions</field>
|
||||
<field name="name">Outstanding exceptions to manage</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">exception.rule.confirm</field>
|
||||
<field name="view_type">form</field>
|
||||
|
@ -34,6 +34,4 @@
|
|||
<field name="view_id" ref="view_exception_rule_confirm"/>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
Loading…
Reference in New Issue