From 959def731188a40a2c59231cf92386c3fa808c73 Mon Sep 17 00:00:00 2001 From: Mourad Elhadj Mimoune Date: Tue, 21 Feb 2017 11:00:55 +0100 Subject: [PATCH] [ADD] split module sale_exception into base_exception --- base_exception/README.rst | 60 +++++ base_exception/__init__.py | 5 + base_exception/__manifest__.py | 21 ++ base_exception/i18n/base_exception.pot | 211 +++++++++++++++++ base_exception/i18n/fr.po | 213 +++++++++++++++++ base_exception/models/__init__.py | 5 + base_exception/models/base_exception.py | 215 ++++++++++++++++++ .../security/base_exception_security.xml | 9 + base_exception/security/ir.model.access.csv | 5 + base_exception/static/description/icon.png | Bin 0 -> 9455 bytes base_exception/views/base_exception_view.xml | 51 +++++ base_exception/wizard/__init__.py | 5 + .../wizard/base_exception_confirm.py | 35 +++ .../wizard/base_exception_confirm_view.xml | 39 ++++ 14 files changed, 874 insertions(+) create mode 100644 base_exception/README.rst create mode 100644 base_exception/__init__.py create mode 100644 base_exception/__manifest__.py create mode 100644 base_exception/i18n/base_exception.pot create mode 100644 base_exception/i18n/fr.po create mode 100644 base_exception/models/__init__.py create mode 100644 base_exception/models/base_exception.py create mode 100644 base_exception/security/base_exception_security.xml create mode 100644 base_exception/security/ir.model.access.csv create mode 100644 base_exception/static/description/icon.png create mode 100644 base_exception/views/base_exception_view.xml create mode 100644 base_exception/wizard/__init__.py create mode 100644 base_exception/wizard/base_exception_confirm.py create mode 100644 base_exception/wizard/base_exception_confirm_view.xml diff --git a/base_exception/README.rst b/base_exception/README.rst new file mode 100644 index 000000000..9a34bdfed --- /dev/null +++ b/base_exception/README.rst @@ -0,0 +1,60 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://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). + +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 + + +Bug Tracker +=========== + +Bugs are tracked on `GitHub 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 +`_. + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Raphaël Valyi +* Renato Lima +* Sébastien BEAU +* Guewen Baconnier +* Yannick Vaucher +* SodexisTeam +* Mourad EL HADJ MIMOUNE + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/base_exception/__init__.py b/base_exception/__init__.py new file mode 100644 index 000000000..3c4dc4909 --- /dev/null +++ b/base_exception/__init__.py @@ -0,0 +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). + +from . import wizard, models diff --git a/base_exception/__manifest__.py b/base_exception/__manifest__.py new file mode 100644 index 000000000..6a42b0b1b --- /dev/null +++ b/base_exception/__manifest__.py @@ -0,0 +1,21 @@ +# -*- 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). + +{'name': 'Exception Rule', + 'version': '10.0.1.0.0', + 'category': 'Generic Modules', + '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)", + 'website': 'http://www.akretion.com', + 'depends': ['base_setup'], + 'license': 'AGPL-3', + 'data': [ + 'security/base_exception_security.xml', + 'security/ir.model.access.csv', + 'wizard/base_exception_confirm_view.xml', + 'views/base_exception_view.xml', + ], + 'installable': True, + } diff --git a/base_exception/i18n/base_exception.pot b/base_exception/i18n/base_exception.pot new file mode 100644 index 000000000..b99c2555c --- /dev/null +++ b/base_exception/i18n/base_exception.pot @@ -0,0 +1,211 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * base_exception +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-22 09:46+0000\n" +"PO-Revision-Date: 2017-03-22 09:46+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_active +msgid "Active" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_model +msgid "Apply on" +msgstr "" + +#. module: base_exception +#: model:ir.actions.act_window,name:base_exception.action_exception_rule_confirm +#: model:ir.ui.view,arch_db:base_exception.view_exception_rule_confirm +msgid "Blocked in draft due to exceptions" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_create_uid +msgid "Created by" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_create_date +msgid "Created on" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_description +msgid "Description" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_base_exception_display_name +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_confirm_display_name +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_display_name +msgid "Display Name" +msgstr "" + +#. module: base_exception +#: code:addons/base_exception/models/base_exception.py:185 +#, python-format +msgid "Error when evaluating the exception.rule rule:\n" +" %s \n" +"(%s)" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_name +msgid "Exception Name" +msgstr "" + +#. module: base_exception +#: model:ir.ui.view,arch_db:base_exception.view_exception_rule_tree +msgid "Exception Rule" +msgstr "" + +#. module: base_exception +#: model:ir.ui.view,arch_db:base_exception.view_exception_rule_form +msgid "Exception Rule Setup" +msgstr "" + +#. module: base_exception +#: model:ir.actions.act_window,name:base_exception.action_exception_rule_tree +#: model:ir.model,name:base_exception.model_exception_rule +#: model:ir.ui.menu,name:base_exception.menu_action_exception +msgid "Exception Rules" +msgstr "" + +#. module: base_exception +#: model:res.groups,name:base_exception.group_exception_rule_manager +msgid "Exception manager" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_base_exception_exception_ids +#: model:ir.model.fields,field_description:base_exception.field_sale_order_exception_ids +msgid "Exceptions" +msgstr "" + +#. module: base_exception +#: model:ir.ui.view,arch_db:base_exception.view_exception_rule_confirm +msgid "Exceptions Rules" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_confirm_exception_ids +#: model:ir.model.fields,field_description:base_exception.field_sale_exception_confirm_exception_ids +msgid "Exceptions to resolve" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,help:base_exception.field_exception_rule_sequence +msgid "Gives the sequence order when applying the test" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_base_exception_id +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_confirm_id +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_id +msgid "ID" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_base_exception_ignore_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_confirm_ignore +#: model:ir.model.fields,field_description:base_exception.field_sale_exception_confirm_ignore +#: model:ir.model.fields,field_description:base_exception.field_sale_order_ignore_exception +msgid "Ignore Exceptions" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_base_exception___last_update +#: model:ir.model.fields,field_description:base_exception.field_exception_rule___last_update +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_confirm___last_update +msgid "Last Modified on" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_write_uid +msgid "Last Updated by" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_write_date +msgid "Last Updated on" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_base_exception_main_exception_id +#: model:ir.model.fields,field_description:base_exception.field_sale_order_main_exception_id +msgid "Main Exception" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_code +msgid "Python Code" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,help:base_exception.field_exception_rule_code +msgid "Python code executed to check if the exception apply or not. The code must apply block = True to apply the exception." +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_confirm_related_model_id +msgid "Related model id" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_base_exception_rule_group +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_rule_group +msgid "Rule group" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,help:base_exception.field_exception_rule_rule_group +msgid "Rule group is used to group the rules that must validated at same time for a target object. Ex: validate sale.order.line rules with sale order rules." +msgstr "" + +#. module: base_exception +#: selection:exception.rule,rule_group:0 +msgid "Sale" +msgstr "" + +#. module: base_exception +#: selection:exception.rule,model:0 +msgid "Sale order" +msgstr "" + +#. module: base_exception +#: selection:exception.rule,model:0 +msgid "Sale order line" +msgstr "" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_sequence +msgid "Sequence" +msgstr "" + +#. module: base_exception +#: model:ir.ui.view,arch_db:base_exception.view_exception_rule_confirm +msgid "_Close" +msgstr "" + +#. module: base_exception +#: model:ir.model,name:base_exception.model_base_exception +msgid "base.exception" +msgstr "" + +#. module: base_exception +#: model:ir.model,name:base_exception.model_exception_rule_confirm +msgid "exception.rule.confirm" +msgstr "" + diff --git a/base_exception/i18n/fr.po b/base_exception/i18n/fr.po new file mode 100644 index 000000000..f072c4025 --- /dev/null +++ b/base_exception/i18n/fr.po @@ -0,0 +1,213 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * base_exception +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-22 09:46+0000\n" +"PO-Revision-Date: 2017-03-22 09:46+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_active +msgid "Active" +msgstr "Actif" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_model +msgid "Apply on" +msgstr "Appliquer sur" + +#. module: base_exception +#: model:ir.actions.act_window,name:base_exception.action_exception_rule_confirm +#: model:ir.ui.view,arch_db:base_exception.view_exception_rule_confirm +msgid "Blocked in draft due to exceptions" +msgstr "Bloqué à l'état brouillon à cause d'une restriction" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_create_uid +msgid "Created by" +msgstr "Créé par" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_create_date +msgid "Created on" +msgstr "Créé le" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_description +msgid "Description" +msgstr "Description" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_base_exception_display_name +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_confirm_display_name +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_display_name +msgid "Display Name" +msgstr "Nom à afficher" + +#. module: base_exception +#: code:addons/base_exception/models/base_exception.py:185 +#, python-format +msgid "Error when evaluating the exception.rule rule:\n" +" %s \n" +"(%s)" +msgstr "L'evaluatino de la règle d'exception a généré une erreur :\n" +" %s \n" +"(%s)" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_name +msgid "Exception Name" +msgstr "Nom de l'exception" + +#. module: base_exception +#: model:ir.ui.view,arch_db:base_exception.view_exception_rule_tree +msgid "Exception Rule" +msgstr "Règle de l'exception" + +#. module: base_exception +#: model:ir.ui.view,arch_db:base_exception.view_exception_rule_form +msgid "Exception Rule Setup" +msgstr "Paramètre de la règle de l'exception" + +#. modul: base_exception +#: model:ir.actions.act_window,name:base_exception.action_exception_rule_tree +#: model:ir.model,name:base_exception.model_exception_rule +#: model:ir.ui.menu,name:base_exception.menu_action_exception +msgid "Exception Rules" +msgstr "Règles de restriction" + +#. module: base_exception +#: model:res.groups,name:base_exception.group_exception_rule_manager +msgid "Exception manager" +msgstr "Gestionnaire d'exception" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_base_exception_exception_ids +#: model:ir.model.fields,field_description:base_exception.field_sale_order_exception_ids +msgid "Exceptions" +msgstr "Exceptions" + +#. module: base_exception +#: model:ir.ui.view,arch_db:base_exception.view_exception_rule_confirm +msgid "Exceptions Rules" +msgstr "Règles d'exceptions" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_confirm_exception_ids +#: model:ir.model.fields,field_description:base_exception.field_sale_exception_confirm_exception_ids +msgid "Exceptions to resolve" +msgstr "Exceptions à resoudre" + +#. module: base_exception +#: model:ir.model.fields,help:base_exception.field_exception_rule_sequence +msgid "Gives the sequence order when applying the test" +msgstr "Donner l'order d'application des règles" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_base_exception_id +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_confirm_id +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_id +msgid "ID" +msgstr "ID" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_base_exception_ignore_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_confirm_ignore +#: model:ir.model.fields,field_description:base_exception.field_sale_exception_confirm_ignore +#: model:ir.model.fields,field_description:base_exception.field_sale_order_ignore_exception +msgid "Ignore Exceptions" +msgstr "Ignorer les exceptions" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_base_exception___last_update +#: model:ir.model.fields,field_description:base_exception.field_exception_rule___last_update +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_confirm___last_update +msgid "Last Modified on" +msgstr "Dernière Modification le" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_write_uid +msgid "Last Updated by" +msgstr "Dernière mise à jour par" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_write_date +msgid "Last Updated on" +msgstr "Dernière mise à jour le" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_base_exception_main_exception_id +#: model:ir.model.fields,field_description:base_exception.field_sale_order_main_exception_id +msgid "Main Exception" +msgstr "Exception principale" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_code +msgid "Python Code" +msgstr "Code Python" + +#. module: base_exception +#: model:ir.model.fields,help:base_exception.field_exception_rule_code +msgid "Python code executed to check if the exception apply or not. The code must apply block = True to apply the exception." +msgstr "Code python à vérifier si l'exception est applicable ou pas. Le code doit renvoyer True pour appliquer l'exception." + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_confirm_related_model_id +msgid "Related model id" +msgstr "Model (obj) lié" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_base_exception_rule_group +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_rule_group +msgid "Rule group" +msgstr "Groupe de règles" + +#. module: base_exception +#: model:ir.model.fields,help:base_exception.field_exception_rule_rule_group +msgid "Rule group is used to group the rules that must validated at same time for a target object. Ex: validate sale.order.line rules with sale order rules." +msgstr "Le groupe de règles est utiliser pour grouper toutes les règles qui doivent être vérifiées pour un même objet. Ex: valider les règles de sale.order.line avec les règles de sale order." + +#. module: base_exception +#: selection:exception.rule,rule_group:0 +msgid "Sale" +msgstr "Vente" + +#. module: base_exception +#: selection:exception.rule,model:0 +msgid "Sale order" +msgstr "Commande de vente" + +#. module: base_exception +#: selection:exception.rule,model:0 +msgid "Sale order line" +msgstr "Ligne de vente" + +#. module: base_exception +#: model:ir.model.fields,field_description:base_exception.field_exception_rule_sequence +msgid "Sequence" +msgstr "Séquence" + +#. module: base_exception +#: model:ir.ui.view,arch_db:base_exception.view_exception_rule_confirm +msgid "_Close" +msgstr "_Close" + +#. module: base_exception +#: model:ir.model,name:base_exception.model_base_exception +msgid "base.exception" +msgstr "base.exception" + +#. module: base_exception +#: model:ir.model,name:base_exception.model_exception_rule_confirm +msgid "exception.rule.confirm" +msgstr "exception.rule.confirm" + diff --git a/base_exception/models/__init__.py b/base_exception/models/__init__.py new file mode 100644 index 000000000..5f94bb885 --- /dev/null +++ b/base_exception/models/__init__.py @@ -0,0 +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). + +from . import base_exception diff --git a/base_exception/models/base_exception.py b/base_exception/models/base_exception.py new file mode 100644 index 000000000..2c72f694c --- /dev/null +++ b/base_exception/models/base_exception.py @@ -0,0 +1,215 @@ +# -*- 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). + +import time +from functools import wraps + +from odoo import api, models, fields, _ +from odoo.exceptions import UserError, ValidationError +from odoo.tools.safe_eval import safe_eval + + +def implemented_by_base_exception(func): + """Call a prefixed function based on 'namespace'.""" + @wraps(func) + def wrapper(cls, *args, **kwargs): + fun_name = func.__name__ + fun = '_%s%s' % (cls.rule_group, fun_name) + if not hasattr(cls, fun): + fun = '_default%s' % (fun_name) + return getattr(cls, fun)(*args, **kwargs) + return wrapper + + +class ExceptionRule(models.Model): + _name = 'exception.rule' + _description = "Exception Rules" + _order = 'active desc, sequence asc' + + name = fields.Char('Exception Name', required=True, translate=True) + description = fields.Text('Description', translate=True) + sequence = fields.Integer( + string='Sequence', + help="Gives the sequence order when applying the test") + rule_group = fields.Selection( + selection=[], + help="Rule group is used to group the rules that must validated " + "at same time for a target object. Ex: " + "validate sale.order.line rules with sale order rules.", + required=True) + model = fields.Selection( + selection=[], + string='Apply on', required=True) + active = fields.Boolean('Active') + code = fields.Text( + 'Python Code', + help="Python code executed to check if the exception apply or " + "not. The code must apply block = True to apply the " + "exception.", + default=""" +# Python code. Use failed = True to block the base.exception. +# You can use the following variables : +# - self: ORM model of the record which is checked +# - "rule_group" or "rule_group_"line: +# browse_record of the base.exception or +# base.exception line (ex rule_group = sale for sale order) +# - object: same as order or line, browse_record of the base.exception or +# base.exception line +# - pool: ORM model pool (i.e. self.pool) +# - time: Python time module +# - cr: database cursor +# - uid: current user id +# - context: current context +""") + + +class BaseException(models.AbstractModel): + _name = 'base.exception' + + _order = 'main_exception_id asc' + + main_exception_id = fields.Many2one( + 'exception.rule', + compute='_compute_main_error', + string='Main Exception', + store=True) + rule_group = fields.Selection( + [], + readonly=True, + ) + exception_ids = fields.Many2many( + 'exception.rule', + string='Exceptions') + ignore_exception = fields.Boolean('Ignore Exceptions', copy=False) + + @api.depends('exception_ids', 'ignore_exception') + def _compute_main_error(self): + for obj in self: + if not obj.ignore_exception and obj.exception_ids: + obj.main_exception_id = obj.exception_ids[0] + else: + obj.main_exception_id = False + + @api.multi + def _popup_exceptions(self): + action = self._get_popup_action() + action = action.read()[0] + action.update({ + 'context': { + 'active_id': self.ids[0], + 'active_ids': self.ids + } + }) + return action + + @api.model + def _get_popup_action(self): + action = self.env.ref('base_exception.action_exception_rule_confirm') + return action + + @api.model + def _check_exception(self): + """ + This method must be used in a constraint that must be created in the + object that inherits for base.exception. + for sale : + @api.constrains('ignore_exception',) + def sale_check_exception(self): + ... + ... + self._check_exception + """ + exception_ids = self.detect_exceptions() + if exception_ids: + exceptions = self.env['exception.rule'].browse(exception_ids) + raise ValidationError('\n'.join(exceptions.mapped('name'))) + + @api.multi + def test_exceptions(self): + """ + Condition method for the workflow from draft to confirm + """ + if self.detect_exceptions(): + return False + return True + + @api.multi + def detect_exceptions(self): + """returns the list of exception_ids for all the considered base.exceptions + """ + exception_obj = self.env['exception.rule'] + all_exceptions = exception_obj.sudo().search( + [('rule_group', '=', self[0].rule_group)]) + model_exceptions = all_exceptions.filtered( + lambda ex: ex.model == self._name) + sub_exceptions = all_exceptions.filtered( + lambda ex: ex.model != self._name) + + all_exception_ids = [] + for obj in self: + if obj.ignore_exception: + continue + exception_ids = obj._detect_exceptions( + model_exceptions, sub_exceptions) + obj.exception_ids = [(6, 0, exception_ids)] + all_exception_ids += exception_ids + return all_exception_ids + + @api.model + def _exception_rule_eval_context(self, obj_name, rec): + user = self.env['res.users'].browse(self._uid) + return {obj_name: rec, + 'self': self.pool.get(rec._name), + 'object': rec, + 'obj': rec, + 'pool': self.pool, + 'cr': self._cr, + 'uid': self._uid, + 'user': user, + 'time': time, + # copy context to prevent side-effects of eval + 'context': self._context.copy()} + + @api.model + def _rule_eval(self, rule, obj_name, rec): + expr = rule.code + space = self._exception_rule_eval_context(obj_name, rec) + try: + safe_eval(expr, + space, + mode='exec', + nocopy=True) # nocopy allows to return 'result' + except Exception, e: + raise UserError( + _('Error when evaluating the exception.rule ' + 'rule:\n %s \n(%s)') % (rule.name, e)) + return space.get('failed', False) + + @api.multi + def _detect_exceptions(self, model_exceptions, + sub_exceptions): + self.ensure_one() + exception_ids = [] + for rule in model_exceptions: + if self._rule_eval(rule, self.rule_group, self): + exception_ids.append(rule.id) + if sub_exceptions: + for obj_line in self._get_lines(): + for rule in sub_exceptions: + if rule.id in exception_ids: + # we do not matter if the exception as already been + # found for an line of this object + # (ex sale order line if obj is sale order) + continue + group_line = self.rule_group + '_line' + if self._rule_eval(rule, group_line, obj_line): + exception_ids.append(rule.id) + return exception_ids + + @implemented_by_base_exception + def _get_lines(self): + pass + + def _default_get_lines(self): + return [] diff --git a/base_exception/security/base_exception_security.xml b/base_exception/security/base_exception_security.xml new file mode 100644 index 000000000..d69d3669b --- /dev/null +++ b/base_exception/security/base_exception_security.xml @@ -0,0 +1,9 @@ + + + + + Exception manager + + + + diff --git a/base_exception/security/ir.model.access.csv b/base_exception/security/ir.model.access.csv new file mode 100644 index 000000000..ee49a23e8 --- /dev/null +++ b/base_exception/security/ir.model.access.csv @@ -0,0 +1,5 @@ +"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 diff --git a/base_exception/static/description/icon.png b/base_exception/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/base_exception/views/base_exception_view.xml b/base_exception/views/base_exception_view.xml new file mode 100644 index 000000000..4f30a0f05 --- /dev/null +++ b/base_exception/views/base_exception_view.xml @@ -0,0 +1,51 @@ + + + + + exception.rule.tree + exception.rule + + + + + + + + + + + + + exception.rule.form + exception.rule + +
+ + + + + + + + + + + + + +
+
+
+ + + Exception Rules + exception.rule + form + tree,form + + {'active_test': False} + + + + +
diff --git a/base_exception/wizard/__init__.py b/base_exception/wizard/__init__.py new file mode 100644 index 000000000..613ae2e30 --- /dev/null +++ b/base_exception/wizard/__init__.py @@ -0,0 +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). + +from . import base_exception_confirm diff --git a/base_exception/wizard/base_exception_confirm.py b/base_exception/wizard/base_exception_confirm.py new file mode 100644 index 000000000..d9a7b6844 --- /dev/null +++ b/base_exception/wizard/base_exception_confirm.py @@ -0,0 +1,35 @@ +# -*- 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 odoo import api, fields, models + + +class ExceptionRuleConfirm(models.AbstractModel): + + _name = 'exception.rule.confirm' + + related_model_id = fields.Many2one('base.exception',) + exception_ids = fields.Many2many('exception.rule', + string='Exceptions to resolve', + readonly=True) + ignore = fields.Boolean('Ignore Exceptions') + + @api.model + def default_get(self, field_list): + res = super(ExceptionRuleConfirm, self).default_get(field_list) + current_model = self._context.get('active_model') + model_except_obj = self.env[current_model] + active_ids = self._context.get('active_ids') + assert len(active_ids) == 1, "Only 1 ID accepted, got %r" % active_ids + active_id = active_ids[0] + related_model_except = model_except_obj.browse(active_id) + exception_ids = [e.id for e in related_model_except.exception_ids] + res.update({'exception_ids': [(6, 0, exception_ids)]}) + res.update({'related_model_id': active_id}) + return res + + @api.multi + def action_confirm(self): + self.ensure_one() + return {'type': 'ir.actions.act_window_close'} diff --git a/base_exception/wizard/base_exception_confirm_view.xml b/base_exception/wizard/base_exception_confirm_view.xml new file mode 100644 index 000000000..daffb29d5 --- /dev/null +++ b/base_exception/wizard/base_exception_confirm_view.xml @@ -0,0 +1,39 @@ + + + + + + Exceptions Rules + exception.rule.confirm + +
+ + + + + + + + + + +
+
+
+
+
+ + + Blocked in draft due to exceptions + ir.actions.act_window + exception.rule.confirm + form + form + + new + + +
+