From c5931f94fb0aeed333b8071f8ff5f93c5b209ebb Mon Sep 17 00:00:00 2001 From: Carmen Bianca BAKKER Date: Fri, 28 Jun 2024 11:53:56 +0200 Subject: [PATCH 1/2] [FIX] hr_timesheet_begin_end: Test uses timesheet lines now Timesheet lines are characterised by having a project. Signed-off-by: Carmen Bianca BAKKER --- .../readme/newsfragments/693.bugfix.1.rst | 1 + .../tests/test_timesheet_begin_end.py | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 hr_timesheet_activity_begin_end/readme/newsfragments/693.bugfix.1.rst diff --git a/hr_timesheet_activity_begin_end/readme/newsfragments/693.bugfix.1.rst b/hr_timesheet_activity_begin_end/readme/newsfragments/693.bugfix.1.rst new file mode 100644 index 0000000000..056c17a06b --- /dev/null +++ b/hr_timesheet_activity_begin_end/readme/newsfragments/693.bugfix.1.rst @@ -0,0 +1 @@ +Fixed the test to use timesheet lines instead of bare analytic lines. diff --git a/hr_timesheet_activity_begin_end/tests/test_timesheet_begin_end.py b/hr_timesheet_activity_begin_end/tests/test_timesheet_begin_end.py index 620e6d8c1f..a8e2c88958 100644 --- a/hr_timesheet_activity_begin_end/tests/test_timesheet_begin_end.py +++ b/hr_timesheet_activity_begin_end/tests/test_timesheet_begin_end.py @@ -9,17 +9,16 @@ class TestBeginEnd(common.TransactionCase): def setUp(self): super(TestBeginEnd, self).setUp() self.timesheet_line_model = self.env['account.analytic.line'] - self.analytic = self.env.ref('analytic.analytic_administratif') - self.user = self.env.ref('base.user_root') + self.project = self.env.ref('project.project_project_1') + self.employee = self.env.ref('hr.employee_qdp') self.base_line = { 'name': 'test', 'date': fields.Date.today(), 'time_start': 10., 'time_stop': 12., - 'user_id': self.user.id, 'unit_amount': 2., - 'account_id': self.analytic.id, - 'amount': -60., + 'project_id': self.project.id, + 'employee_id': self.employee.id, } def test_onchange(self): @@ -27,6 +26,7 @@ def test_onchange(self): 'name': 'test', 'time_start': 10., 'time_stop': 12., + 'project_id': self.project.id, }) line.onchange_hours_start_stop() self.assertEquals(line.unit_amount, 2) From 14c76a764cb46e793f9a9dc869fc8c6e88c8c6b3 Mon Sep 17 00:00:00 2001 From: Carmen Bianca BAKKER Date: Fri, 21 Jun 2024 12:00:34 +0200 Subject: [PATCH 2/2] [REF] hr_timesheet_activity_begin_end: Make more extensible The separation of checks into separate methods is needed because I want to disable one check in another module. This makes the module more extensible. The unit_amount_from_start_stop method also makes the module more extensible. I have also moved the onchange to the top of the file, according to the OCA contribution guidelines. Signed-off-by: Carmen Bianca BAKKER --- .../models/account_analytic_line.py | 130 +++++++++++------- .../readme/newsfragments/693.feature.1.rst | 1 + 2 files changed, 84 insertions(+), 47 deletions(-) create mode 100644 hr_timesheet_activity_begin_end/readme/newsfragments/693.feature.1.rst diff --git a/hr_timesheet_activity_begin_end/models/account_analytic_line.py b/hr_timesheet_activity_begin_end/models/account_analytic_line.py index ebac126754..7e7a1014be 100644 --- a/hr_timesheet_activity_begin_end/models/account_analytic_line.py +++ b/hr_timesheet_activity_begin_end/models/account_analytic_line.py @@ -1,10 +1,11 @@ # Copyright 2015 Camptocamp SA - Guewen Baconnier # Copyright 2017 Tecnativa, S.L. - Luis M. Ontalba +# Copyright 2024 Coop IT Easy SC - Carmen Bianca BAKKER # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html from datetime import timedelta -from odoo import models, fields, api, exceptions, _ +from odoo import _, api, exceptions, fields, models from odoo.tools.float_utils import float_compare @@ -15,55 +16,90 @@ class AccountAnalyticLine(models.Model): time_start = fields.Float(string='Begin Hour') time_stop = fields.Float(string='End Hour') - @api.constrains('time_start', 'time_stop', 'unit_amount') - def _check_time_start_stop(self): + @api.onchange("time_start", "time_stop", "project_id") + def onchange_hours_start_stop(self): + self.unit_amount = self.unit_amount_from_start_stop() + + def _validate_start_before_stop(self): + value_to_html = self.env["ir.qweb.field.float_time"].value_to_html + for line in self: + if line.time_stop < line.time_start: + raise exceptions.ValidationError( + _("The beginning hour (%s) must " "precede the ending hour (%s).") + % ( + value_to_html(line.time_start, None), + value_to_html(line.time_stop, None), + ) + ) + + def _validate_unit_amount_equal_to_time_diff(self): + value_to_html = self.env["ir.qweb.field.float_time"].value_to_html + for line in self: + hours = line.unit_amount_from_start_stop() + rounding = self.env.ref("uom.product_uom_hour").rounding + if hours and float_compare( + hours, line.unit_amount, precision_rounding=rounding + ): + raise exceptions.ValidationError( + _( + "The duration (%s) must be equal to the difference " + "between the hours (%s)." + ) + % ( + value_to_html(line.unit_amount, None), + value_to_html(hours, None), + ) + ) + + def _overlap_domain(self): self.ensure_one() - value_to_html = self.env['ir.qweb.field.float_time'].value_to_html - start = timedelta(hours=self.time_start) - stop = timedelta(hours=self.time_stop) - if stop < start: - raise exceptions.ValidationError( - _('The beginning hour (%s) must ' - 'precede the ending hour (%s).') % - (value_to_html(self.time_start, None), - value_to_html(self.time_stop, None)) - ) - hours = (stop - start).seconds / 3600 - rounding = self.env.ref("uom.product_uom_hour").rounding - if (hours and - float_compare(hours, self.unit_amount, precision_rounding=rounding)): - raise exceptions.ValidationError( - _('The duration (%s) must be equal to the difference ' - 'between the hours (%s).') % - (value_to_html(self.unit_amount, None), - value_to_html(hours, None)) - ) - # check if lines overlap - others = self.search([ - ('id', '!=', self.id), - ('user_id', '=', self.user_id.id), - ('date', '=', self.date), - ('time_start', '<', self.time_stop), - ('time_stop', '>', self.time_start), - ]) - if others: - message = _("Lines can't overlap:\n") - message += '\n'.join(['%s - %s' % - (value_to_html(line.time_start, None), - value_to_html(line.time_stop, None)) - for line - in (self + others).sorted( - lambda l: l.time_start - )]) - raise exceptions.ValidationError(message) + return [ + ("id", "!=", self.id), + ("user_id", "=", self.user_id.id), + ("date", "=", self.date), + ("time_start", "<", self.time_stop), + ("time_stop", ">", self.time_start), + ] - @api.onchange('time_start', 'time_stop') - def onchange_hours_start_stop(self): - start = timedelta(hours=self.time_start) - stop = timedelta(hours=self.time_stop) + def _validate_no_overlap(self): + value_to_html = self.env["ir.qweb.field.float_time"].value_to_html + for line in self: + others = self.search(line._overlap_domain()) + if others: + message = _("Lines can't overlap:\n") + message += "\n".join( + [ + "%s - %s" + % ( + value_to_html(line.time_start, None), + value_to_html(line.time_stop, None), + ) + for line in (line + others).sorted(lambda l: l.time_start) + ] + ) + raise exceptions.ValidationError(message) + + @api.constrains("time_start", "time_stop", "unit_amount") + def _check_time_start_stop(self): + self._validate_start_before_stop() + self._validate_unit_amount_equal_to_time_diff() + self._validate_no_overlap() + + @api.model + def _hours_from_start_stop(self, time_start, time_stop): + start = timedelta(hours=time_start) + stop = timedelta(hours=time_stop) if stop < start: - return - self.unit_amount = (stop - start).seconds / 3600 + # Invalid case, but return something sensible. + return 0 + return (stop - start).seconds / 3600 + + def unit_amount_from_start_stop(self): + self.ensure_one() + # Don't handle non-timesheet lines. + if not self.project_id: + return 0 + return self._hours_from_start_stop(self.time_start, self.time_stop) def merge_timesheets(self): # pragma: no cover """This method is needed in case hr_timesheet_sheet is installed""" diff --git a/hr_timesheet_activity_begin_end/readme/newsfragments/693.feature.1.rst b/hr_timesheet_activity_begin_end/readme/newsfragments/693.feature.1.rst new file mode 100644 index 0000000000..366ce40d00 --- /dev/null +++ b/hr_timesheet_activity_begin_end/readme/newsfragments/693.feature.1.rst @@ -0,0 +1 @@ +Refactored the module to be more extensible.