From 368770f8143e97d2a9e13ce0fd3e566455df7ed0 Mon Sep 17 00:00:00 2001 From: percyhibou Date: Sat, 1 Oct 2022 04:11:39 +0000 Subject: [PATCH] [ADD] timesheet_exception: timesheet created should not exceed 8 in spent hours H11047 --- timesheet_exception/__init__.py | 4 + timesheet_exception/__manifest__.py | 30 ++++++ .../demo/timesheet_exception_demo.xml | 22 +++++ timesheet_exception/models/__init__.py | 3 + timesheet_exception/models/analytic.py | 59 +++++++++++ .../security/ir.model.access.csv | 2 + timesheet_exception/tests/__init__.py | 2 + .../tests/test_timesheet_exception.py | 99 +++++++++++++++++++ timesheet_exception/views/timesheet_views.xml | 65 ++++++++++++ timesheet_exception/wizard/__init__.py | 3 + .../wizard/timesheet_exception_confirm.py | 26 +++++ .../timesheet_exception_confirm_views.xml | 25 +++++ 12 files changed, 340 insertions(+) create mode 100644 timesheet_exception/__init__.py create mode 100644 timesheet_exception/__manifest__.py create mode 100644 timesheet_exception/demo/timesheet_exception_demo.xml create mode 100644 timesheet_exception/models/__init__.py create mode 100644 timesheet_exception/models/analytic.py create mode 100644 timesheet_exception/security/ir.model.access.csv create mode 100644 timesheet_exception/tests/__init__.py create mode 100644 timesheet_exception/tests/test_timesheet_exception.py create mode 100644 timesheet_exception/views/timesheet_views.xml create mode 100644 timesheet_exception/wizard/__init__.py create mode 100644 timesheet_exception/wizard/timesheet_exception_confirm.py create mode 100644 timesheet_exception/wizard/timesheet_exception_confirm_views.xml diff --git a/timesheet_exception/__init__.py b/timesheet_exception/__init__.py new file mode 100644 index 00000000..c7120225 --- /dev/null +++ b/timesheet_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/timesheet_exception/__manifest__.py b/timesheet_exception/__manifest__.py new file mode 100644 index 00000000..08640789 --- /dev/null +++ b/timesheet_exception/__manifest__.py @@ -0,0 +1,30 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +{ + 'name': "Timesheet Exception Rule", + 'version': '15.0.1.0.0', + 'author': "Hibou Corp.", + 'license': 'OPL-1', + 'category': 'Generic Modules', + 'summary': """ + Module for Timesheet Exception Rule""", + 'description': """ + The ability to run and trigger exceptions to block the moving of a timesheet based on rules + """, + 'website': "http://www.hibou.io/", + 'depends': [ + 'base_exception_user', + 'timesheet_grid', + 'sale_timesheet' # We need to clarify if this module has to be installed/present to run the Tests + ], + 'data': [ + 'security/ir.model.access.csv', + 'views/timesheet_views.xml', + 'wizard/timesheet_exception_confirm_views.xml', + ], + 'demo': [ + 'demo/timesheet_exception_demo.xml', + ], + 'installable': True, + 'auto_install': False, +} diff --git a/timesheet_exception/demo/timesheet_exception_demo.xml b/timesheet_exception/demo/timesheet_exception_demo.xml new file mode 100644 index 00000000..a51ee7a8 --- /dev/null +++ b/timesheet_exception/demo/timesheet_exception_demo.xml @@ -0,0 +1,22 @@ + + + + + Unit amount exceeds 8 hrs + Unit amount over 8 hrs + 50 + account.analytic.line + if timesheet.unit_amount > 8: failed=True + + + + + SO delivered qty plus unit amount exceeds ordered qty + SO delivered qty plus unit amount exceeds ordered qty + 50 + account.analytic.line + if timesheet.so_line.product_template_id.service_policy == 'ordered_timesheet' and timesheet.so_line.qty_delivered + timesheet.unit_amount > timesheet.so_line.product_uom_qty: failed=True + + + + diff --git a/timesheet_exception/models/__init__.py b/timesheet_exception/models/__init__.py new file mode 100644 index 00000000..24dc9525 --- /dev/null +++ b/timesheet_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 analytic diff --git a/timesheet_exception/models/analytic.py b/timesheet_exception/models/analytic.py new file mode 100644 index 00000000..65baf1e7 --- /dev/null +++ b/timesheet_exception/models/analytic.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=[ + ('account.analytic.line', 'AnalyticLine'), + ], + ondelete={ + 'account.analytic.line': 'cascade', + }, + ) + timesheet_ids = fields.Many2many( + 'account.analytic.line', + string="Timesheet") + + +class AnalyticLine(models.Model): + _inherit = ['account.analytic.line', 'base.exception'] + _name = 'account.analytic.line' + _order = 'main_exception_id asc' + + @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(AnalyticLine, self)._exception_rule_eval_context(rec) + res['timesheet'] = rec + return res + + @api.model + def _reverse_field(self): + return 'timesheet_ids' + + def write(self, vals): + if not vals.get('ignore_exception'): + for timesheet in self: + if timesheet.detect_exceptions(): + return self._popup_exceptions() + return super().write(vals) + + @api.model + def _get_popup_action(self): + return self.env.ref('timesheet_exception.action_timesheet_exception_confirm') + + def detect_exceptions(self): + res = False + if not self._context.get("detect_exceptions"): + self = self.with_context(detect_exceptions=True) + res = super(AnalyticLine, self).detect_exceptions() + return res diff --git a/timesheet_exception/security/ir.model.access.csv b/timesheet_exception/security/ir.model.access.csv new file mode 100644 index 00000000..b1a5c3fb --- /dev/null +++ b/timesheet_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 +timesheet_exception.access_timesheet_exception_confirm,access_timesheet_exception_confirm,timesheet_exception.model_timesheet_exception_confirm,base.group_user,1,1,1,1 diff --git a/timesheet_exception/tests/__init__.py b/timesheet_exception/tests/__init__.py new file mode 100644 index 00000000..9a29d5ec --- /dev/null +++ b/timesheet_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_timesheet_exception diff --git a/timesheet_exception/tests/test_timesheet_exception.py b/timesheet_exception/tests/test_timesheet_exception.py new file mode 100644 index 00000000..cb53f94a --- /dev/null +++ b/timesheet_exception/tests/test_timesheet_exception.py @@ -0,0 +1,99 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from odoo import fields +from odoo.tests import common, Form + + +class TestTimesheetException(common.TransactionCase): + + def setUp(self): + super().setUp() + self.env = self.env(context=dict(self.env.context, tracking_disable=True)) + + def test_timesheet_create_exception(self): + exception = self.env.ref('timesheet_exception.except_unit_amount_over_eight_hours') + exception.active = True + project = self.env['project.project'].with_context(tracking_disable=True).create({ + 'name': 'Project for selling timesheets', + }) + today = fields.Date.today() + service_category_id = self.env['product.category'].create({ + 'name': 'Services', + 'parent_id': self.env.ref('product.product_category_1').id, + }).id + uom_hour_id = self.env.ref('uom.product_uom_hour').id + product = self.env['product.product'].create({ + 'name': 'Service Product (Prepaid Hours)', + 'categ_id': service_category_id, + 'type': 'service', + 'list_price': 250.00, + 'standard_price': 190.00, + 'invoice_policy': 'delivery', + 'uom_id': uom_hour_id, + 'uom_po_id': uom_hour_id, + 'default_code': 'SERV-DELI1', + 'taxes_id': False, + }) + product.product_tmpl_id.service_policy = 'ordered_timesheet' + partner = self.env['res.partner'].create({ + 'name': 'Test Partner', + 'company_id': False, + }) + so = self.env['sale.order'].with_context(mail_notrack=True, mail_create_nolog=True).create({ + 'partner_id': partner.id, + 'project_id': project.id, + }) + so_line = self.env['sale.order.line'].create([{ + 'order_id': so.id, + 'name': product.name, + 'product_id': product.id, + 'product_uom_qty': 10, + 'qty_delivered': 2, + 'price_unit': product.list_price, + 'project_id': project.id, + }]) + timesheet = self.env['account.analytic.line'].with_user(self.env.user).create({ + 'name': "my timesheet 1", + 'project_id': project.id, + 'date': today, + 'unit_amount': 9.0, + }) + + # Created exceptions on create. + self.assertTrue(timesheet.exception_ids) + + # Will return action on write, which may or not be followed. + action = timesheet.write({ + 'name': 'Test Timesheet - Test Written', + }) + self.assertTrue(timesheet.exception_ids) + self.assertTrue(action) + self.assertEqual(action.get('res_model'), 'timesheet.exception.confirm') + + # Simulation the opening of the wizard timesheet_exception_confirm and + # set ignore_exception to True + timesheet_exception_confirm = Form(self.env[action['res_model']].with_context(action['context'])).save() + timesheet_exception_confirm.ignore = True + timesheet_exception_confirm.action_confirm() + self.assertTrue(timesheet.ignore_exception) + + # No exceptions should be on create with a lower than 8 in unit_amount + # Also no exceptions because timesheet.so_line.product_template_id.service_policy == 'ordered_timesheet' and timesheet.so_line.qty_delivered + timesheet.unit_amount < timesheet.so_line.product_uom_qty + timesheet = self.env['account.analytic.line'].with_user(self.env.user).create({ + 'name': "my timesheet 1", + 'project_id': project.id, + 'date': today, + 'unit_amount': 7.0, + }) + self.assertFalse(timesheet.exception_ids) + + # timesheet.unit_amount = 17.0 + timesheet = self.env['account.analytic.line'].with_user(self.env.user).create({ + 'name': "my timesheet 1", + 'project_id': project.id, + 'date': today, + 'unit_amount': 17.0, + }) + + # Exceptions because timesheet.so_line.product_template_id.service_policy == 'ordered_timesheet' and timesheet.so_line.qty_delivered + timesheet.unit_amount > timesheet.so_line.product_uom_qty + self.assertTrue(timesheet.exception_ids) diff --git a/timesheet_exception/views/timesheet_views.xml b/timesheet_exception/views/timesheet_views.xml new file mode 100644 index 00000000..9f290955 --- /dev/null +++ b/timesheet_exception/views/timesheet_views.xml @@ -0,0 +1,65 @@ + + + + + Timesheet Exception Rules + exception.rule + tree,form + + [('model', '=', 'account.analytic.line')] + {'active_test': False, 'default_model' : 'account.analytic.line'} + + + + + + account.analytic.line.form.inherit.exception + account.analytic.line + + + + + + + + + + + + + taccount.analytic.line.tree.inherit.exception + account.analytic.line + + + + + + + + + + taccount.analytic.line.search.form.inherit.exception + account.analytic.line + + + + + + + + + + diff --git a/timesheet_exception/wizard/__init__.py b/timesheet_exception/wizard/__init__.py new file mode 100644 index 00000000..43aeb061 --- /dev/null +++ b/timesheet_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 timesheet_exception_confirm diff --git a/timesheet_exception/wizard/timesheet_exception_confirm.py b/timesheet_exception/wizard/timesheet_exception_confirm.py new file mode 100644 index 00000000..8b0a64af --- /dev/null +++ b/timesheet_exception/wizard/timesheet_exception_confirm.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 TimesheetExceptionConfirm(models.TransientModel): + _name = 'timesheet.exception.confirm' + _inherit = ['exception.rule.confirm'] + _description = 'Timesheet Exception Confirm Wizard' + + related_model_id = fields.Many2one('account.analytic.line', 'AnalyticLine') + + 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/timesheet_exception/wizard/timesheet_exception_confirm_views.xml b/timesheet_exception/wizard/timesheet_exception_confirm_views.xml new file mode 100644 index 00000000..07b99018 --- /dev/null +++ b/timesheet_exception/wizard/timesheet_exception_confirm_views.xml @@ -0,0 +1,25 @@ + + + + + Timesheet Exceptions Rules + timesheet.exception.confirm + + primary + + +