diff --git a/.theia/launch.json b/.theia/launch.json index 287b3398..ab5720b4 100644 --- a/.theia/launch.json +++ b/.theia/launch.json @@ -68,6 +68,26 @@ "--test-enable", "--no-xmlrpc", "--stop-after-init"], "console": "integratedTerminal" }, + { + "name": "Odoo: INIT 'project_exception'", + "type": "python", + "request": "launch", + "program": "/opt/odoo/hibou-suite/odoo-run.py", + "args": ["-i", "project_exception", + "-u", "project_exception", + "--stop-after-init"], + "console": "integratedTerminal" + }, + { + "name": "Odoo: TEST 'project_exception'", + "type": "python", + "request": "launch", + "program": "/opt/odoo/hibou-suite/odoo-run.py", + "args": ["-i", "project_exception", + "-u", "project_exception", + "--test-enable", "--no-xmlrpc", "--stop-after-init"], + "console": "integratedTerminal" + }, { "name": "Odoo: server", "type": "python", diff --git a/project_exception/__init__.py b/project_exception/__init__.py new file mode 100644 index 00000000..c7120225 --- /dev/null +++ b/project_exception/__init__.py @@ -0,0 +1,4 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from . import models +from . import wizard diff --git a/project_exception/__manifest__.py b/project_exception/__manifest__.py new file mode 100644 index 00000000..2adb4ea1 --- /dev/null +++ b/project_exception/__manifest__.py @@ -0,0 +1,29 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +{ + 'name': "Project Exception Rule", + 'version': '15.0.1.0.0', + 'author': "Hibou Corp.", + 'license': 'OPL-1', + 'category': 'Generic Modules', + 'summary': """ + Module for Project Exception Rule""", + 'description': """ + The ability to run and trigger exceptions to block the moving of a task based on rules + """, + 'website': "http://www.hibou.io/", + 'depends': [ + 'base_exception_user', + 'project' + ], + 'data': [ + 'security/ir.model.access.csv', + 'views/project_views.xml', + 'wizard/project_exception_confirm_views.xml', + ], + 'demo': [ + 'demo/project_exception_demo.xml', + ], + 'installable': True, + 'auto_install': False, +} diff --git a/project_exception/demo/project_exception_demo.xml b/project_exception/demo/project_exception_demo.xml new file mode 100644 index 00000000..2dc5644d --- /dev/null +++ b/project_exception/demo/project_exception_demo.xml @@ -0,0 +1,13 @@ + + + + + No Project Id on task + No Project Id on task + 50 + project.task + if not task.project_id.name: failed=True + + + + diff --git a/project_exception/models/__init__.py b/project_exception/models/__init__.py new file mode 100644 index 00000000..449e23fb --- /dev/null +++ b/project_exception/models/__init__.py @@ -0,0 +1,3 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from . import project \ No newline at end of file diff --git a/project_exception/models/project.py b/project_exception/models/project.py new file mode 100644 index 00000000..de2e2fae --- /dev/null +++ b/project_exception/models/project.py @@ -0,0 +1,59 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from odoo import api, models, fields + + +class ExceptionRule(models.Model): + _inherit = 'exception.rule' + + model = fields.Selection( + selection_add=[ + ('project.task', 'Task'), + ], + ondelete={ + 'project.task': 'cascade', + }, + ) + task_ids = fields.Many2many( + 'project.task', + string="Task") + + +class Task(models.Model): + _inherit = ['project.task', 'base.exception'] + _name = 'project.task' + _order = 'main_exception_id asc, sequence, name, id' + + @api.model + def create(self, values): + res = super().create(values) + res.detect_exceptions() + return res + + @api.model + def _exception_rule_eval_context(self, rec): + res = super(Task, self)._exception_rule_eval_context(rec) + res['task'] = rec + return res + + @api.model + def _reverse_field(self): + return 'task_ids' + + def write(self, vals): + if not vals.get('ignore_exception'): + for task in self: + if task.detect_exceptions(): + return self._popup_exceptions() + return super().write(vals) + + @api.model + def _get_popup_action(self): + return self.env.ref('project_exception.action_project_exception_confirm') + + def detect_exceptions(self): + res = False + if not self._context.get("detect_exceptions"): + self = self.with_context(detect_exceptions=True) + res = super(Task, self).detect_exceptions() + return res diff --git a/project_exception/security/ir.model.access.csv b/project_exception/security/ir.model.access.csv new file mode 100644 index 00000000..09d80192 --- /dev/null +++ b/project_exception/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_project_task_exception_confirm_user,project.exception.confirm user,model_project_exception_confirm,project.group_project_user,1,1,1,1 \ No newline at end of file diff --git a/project_exception/tests/__init__.py b/project_exception/tests/__init__.py new file mode 100644 index 00000000..e40da629 --- /dev/null +++ b/project_exception/tests/__init__.py @@ -0,0 +1,2 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. +from . import test_project_exception diff --git a/project_exception/tests/test_project_exception.py b/project_exception/tests/test_project_exception.py new file mode 100644 index 00000000..9fa95c5f --- /dev/null +++ b/project_exception/tests/test_project_exception.py @@ -0,0 +1,35 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from odoo.tests import common, Form + + +class TestProjectException(common.TransactionCase): + + def setUp(self): + super().setUp() + self.env = self.env(context=dict(self.env.context, tracking_disable=True)) + + def test_project_task_creation_exception(self): + exception = self.env.ref('project_exception.except_no_project_id') + exception.active = True + + task = self.env['project.task'].create({ + 'name': 'Test Task', + }) + # Created exceptions on create. + self.assertTrue(task.exception_ids) + + # Will return action on write, which may or not be followed. + action = task.write({ + 'name': 'Test Task - Test Written', + }) + self.assertTrue(task.exception_ids) + self.assertTrue(action) + self.assertEqual(action.get('res_model'), 'project.exception.confirm') + + # Simulation the opening of the wizard task_exception_confirm and + # set ignore_exception to True + project_exception_confirm = Form(self.env[action['res_model']].with_context(action['context'])).save() + project_exception_confirm.ignore = True + project_exception_confirm.action_confirm() + self.assertTrue(task.ignore_exception) diff --git a/project_exception/views/project_views.xml b/project_exception/views/project_views.xml new file mode 100644 index 00000000..81298481 --- /dev/null +++ b/project_exception/views/project_views.xml @@ -0,0 +1,66 @@ + + + + + Project Exception Rules + exception.rule + + tree,form + + [('model', '=', 'project.task')] + {'active_test': False, 'default_model' : 'project.task'} + + + + + + project.task.form.inherit.exception + project.task + + + + + There are exceptions blocking the confirmation of this Task: + + + + + + + + + + + + project.task.tree.inherit.exception + project.task + + + + + + + + + + project.task.search.form.inherit.exception + project.task + + + + + + + + + + diff --git a/project_exception/wizard/__init__.py b/project_exception/wizard/__init__.py new file mode 100644 index 00000000..f10ee64b --- /dev/null +++ b/project_exception/wizard/__init__.py @@ -0,0 +1,3 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from . import project_exception_confim diff --git a/project_exception/wizard/project_exception_confim.py b/project_exception/wizard/project_exception_confim.py new file mode 100644 index 00000000..56d16935 --- /dev/null +++ b/project_exception/wizard/project_exception_confim.py @@ -0,0 +1,26 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from odoo import api, fields, models + + +class ProjectExceptionConfirm(models.TransientModel): + _name = 'project.exception.confirm' + _inherit = ['exception.rule.confirm'] + _description = 'Project Exception Confirm Wizard' + + related_model_id = fields.Many2one('project.task', 'Task') + + def action_confirm(self): + self.ensure_one() + if self.ignore: + self.related_model_id.ignore_exception = True + res = super().action_confirm() + if self.ignore: + return True + else: + return res + + def _action_ignore(self): + self.related_model_id.ignore_exception = True + super()._action_ignore() + return True diff --git a/project_exception/wizard/project_exception_confirm_views.xml b/project_exception/wizard/project_exception_confirm_views.xml new file mode 100644 index 00000000..5adeb0ec --- /dev/null +++ b/project_exception/wizard/project_exception_confirm_views.xml @@ -0,0 +1,25 @@ + + + + + Project Exceptions Rules + project.exception.confirm + + primary + + + + + + + + + Blocked due to exceptions + ir.actions.act_window + project.exception.confirm + form + + new + + +
There are exceptions blocking the confirmation of this Task: