diff --git a/addons/hr_timesheet/__manifest__.py b/addons/hr_timesheet/__manifest__.py index c9e8db62f0f..01e53c8457d 100644 --- a/addons/hr_timesheet/__manifest__.py +++ b/addons/hr_timesheet/__manifest__.py @@ -20,7 +20,7 @@ It is completely integrated with the cost accounting module. It allows you to se up a management by affair. """, 'website': 'https://www.odoo.com/page/timesheet-mobile-app', - 'depends': ['hr', 'analytic', 'project', 'uom'], + 'depends': ['hr', 'analytic', 'project', 'uom', 'timer'], 'data': [ 'security/hr_timesheet_security.xml', 'security/ir.model.access.csv', diff --git a/addons/hr_timesheet/models/__init__.py b/addons/hr_timesheet/models/__init__.py index 343cff74edd..ee021539a0f 100644 --- a/addons/hr_timesheet/models/__init__.py +++ b/addons/hr_timesheet/models/__init__.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import timer_mixin from . import hr_employee from . import hr_timesheet from . import ir_http diff --git a/addons/hr_timesheet/models/hr_timesheet.py b/addons/hr_timesheet/models/hr_timesheet.py index e58daf247d6..324ece59f25 100644 --- a/addons/hr_timesheet/models/hr_timesheet.py +++ b/addons/hr_timesheet/models/hr_timesheet.py @@ -199,42 +199,33 @@ class AccountAnalyticLine(models.Model): def _compute_display_timer(self): uom_hour = self.env.ref('uom.product_uom_hour') for analytic_line in self: - analytic_line.display_timer = analytic_line.encoding_uom_id == uom_hour + analytic_line.display_timer = analytic_line.encoding_uom_id == uom_hour and analytic_line.project_id.allow_timesheet_timer def action_timer_start(self): - """ Start timer and search if another timer hasn't been launched. - If yes, then stop the timer before launch this timer. + """ Start a timer if it isn't already started and the + timesheets allow to track time """ - if not self.timer_start and self.display_timer: - self._stop_running_timers() + if not self.user_timer_id.timer_start and self.display_timer: super().action_timer_start() - def _stop_running_timers(self): - """ Search if a timesheet has a timer activated and stop the timer. - Check if a timer is activated for another timesheet - if yes, then update unit_amount field and stop timer, - otherwise, do nothing. - """ - analytic_line = self.search([('timer_start', '!=', False), ('user_id', '=', self.env.uid)]) - if analytic_line: - analytic_line.action_timer_stop() - def action_timer_stop(self): - """ Action stop the timer of the current timesheet. - When the timer must be stopped, we must calculate the new - unit_amount based on the timer and the previous value of - unit_amount for the current timesheet. + """ Stop the current timer """ - if self.timer_start and self.display_timer: - minutes_spent = self._get_minutes_spent() - if self.unit_amount == 0 and minutes_spent < 1: - # Check if unit_amount equals 0 and minutes_spent is less than 1 minute, - # if yes, then remove the timesheet - self.unlink() + if self.user_timer_id.timer_start and self.display_timer: + minutes_spent = super().action_timer_stop() + self._add_timesheet_time(minutes_spent) + + def _add_timesheet_time(self, minutes_spent): + if self.unit_amount == 0 and minutes_spent < 1: + # Check if unit_amount equals 0 and minutes_spent is less than 1 minute, + # if yes, then remove the timesheet + self.unlink() + else: + if minutes_spent < 1: + amount = self.unit_amount else: - if minutes_spent < 1: - amount = self.unit_amount - else: - amount = self.unit_amount + minutes_spent * 60 / 3600 - self.write({'unit_amount': amount}) - super().action_timer_stop() + amount = self.unit_amount + minutes_spent * 60 / 3600 + self.write({'unit_amount': amount}) + + def _action_interrupt_user_timers(self): + self.action_timer_stop() \ No newline at end of file diff --git a/addons/hr_timesheet/models/project.py b/addons/hr_timesheet/models/project.py index c44381ef79a..bc82a9866a3 100644 --- a/addons/hr_timesheet/models/project.py +++ b/addons/hr_timesheet/models/project.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from math import ceil from odoo import models, fields, api, _ from odoo.exceptions import UserError, ValidationError @@ -79,7 +78,6 @@ class Project(models.Model): def _init_data_analytic_account(self): self.search([('analytic_account_id', '=', False), ('allow_timesheets', '=', True)])._create_analytic_account() - class Task(models.Model): _name = "project.task" _inherit = ["project.task", "timer.mixin"] @@ -94,32 +92,22 @@ class Task(models.Model): subtask_effective_hours = fields.Float("Sub-tasks Hours Spent", compute='_compute_subtask_effective_hours', store=True, help="Sum of actually spent hours on the subtask(s)") timesheet_ids = fields.One2many('account.analytic.line', 'task_id', 'Timesheets') - timer_start = fields.Datetime("Timesheet Timer Start") - timer_pause = fields.Datetime("Timesheet Timer Last Pause") # YTI FIXME: Those field seems quite useless timesheet_timer_first_start = fields.Datetime("Timesheet Timer First Use", readonly=True) timesheet_timer_last_stop = fields.Datetime("Timesheet Timer Last Use", readonly=True) display_timesheet_timer = fields.Boolean("Display Timesheet Time", compute='_compute_display_timesheet_timer') - display_timer_start_primary = fields.Boolean(compute='_compute_display_timer_buttons') + display_timer_start_secondary = fields.Boolean(compute='_compute_display_timer_buttons') - display_timer_stop = fields.Boolean(compute='_compute_display_timer_buttons') - display_timer_pause = fields.Boolean(compute='_compute_display_timer_buttons') - display_timer_resume = fields.Boolean(compute='_compute_display_timer_buttons') @api.depends('display_timesheet_timer', 'timer_start', 'timer_pause', 'total_hours_spent') def _compute_display_timer_buttons(self): for task in self: - start_p, start_s, stop, pause, resume = True, True, True, True, True + displays = super()._compute_display_timer_buttons() + start_p, start_s, stop, pause, resume = displays['start_p'], displays['start_p'], displays['stop'], displays['pause'], displays['resume'] if not task.display_timesheet_timer: start_p, start_s, stop, pause, resume = False, False, False, False, False else: - if task.timer_start: - start_p, start_s = False, False - if task.timer_pause: - pause = False - else: - resume = False - else: + if not task.timer_start: stop, pause, resume = False, False, False if not task.total_hours_spent: start_s = False @@ -213,27 +201,19 @@ class Task(models.Model): return result def action_timer_start(self): - self.ensure_one() - if not self.timesheet_timer_first_start: - self.write({'timesheet_timer_first_start': fields.Datetime.now()}) - super(Task, self).action_timer_start() + if not self.user_timer_id.timer_start and self.display_timesheet_timer: + super(Task, self).action_timer_start() def action_timer_stop(self): - self.ensure_one() - if self.timer_start: # timer was either running or paused - minutes_spent = self._get_minutes_spent() - minutes_spent = self._timer_rounding(minutes_spent) + # timer was either running or paused + if self.user_timer_id.timer_start and self.display_timesheet_timer: + minutes_spent = super().action_timer_stop() + minimum_duration = int(self.env['ir.config_parameter'].sudo().get_param('hr_timesheet.timesheet_min_duration', 0)) + rounding = int(self.env['ir.config_parameter'].sudo().get_param('hr_timesheet.timesheet_rounding', 0)) + minutes_spent = self._timer_rounding(minutes_spent, minimum_duration, rounding) return self._action_create_timesheet(minutes_spent * 60 / 3600) return False - def _timer_rounding(self, minutes_spent): - minimum_duration = int(self.env['ir.config_parameter'].sudo().get_param('hr_timesheet.timesheet_min_duration', 0)) - rounding = int(self.env['ir.config_parameter'].sudo().get_param('hr_timesheet.timesheet_rounding', 0)) - minutes_spent = max(minimum_duration, minutes_spent) - if rounding and ceil(minutes_spent % rounding) != 0: - minutes_spent = ceil(minutes_spent / rounding) * rounding - return minutes_spent - def _action_create_timesheet(self, time_spent): return { "name": _("Validate Spent Time"), @@ -244,7 +224,7 @@ class Task(models.Model): "context": { **self.env.context, 'active_id': self.id, - 'active_model': 'project.task', + 'active_model': self._name, 'default_time_spent': time_spent, }, } diff --git a/addons/hr_timesheet/static/src/js/timesheet_uom.js b/addons/hr_timesheet/static/src/js/timesheet_uom.js index a462b1d463b..9c7f9a869f7 100644 --- a/addons/hr_timesheet/static/src/js/timesheet_uom.js +++ b/addons/hr_timesheet/static/src/js/timesheet_uom.js @@ -6,7 +6,7 @@ const fieldUtils = require('web.field_utils'); const fieldRegistry = require('web.field_registry'); -const Timer = require('hr_timesheet.Timer'); +const Timer = require('timer.Timer'); // We need the field registry to be populated, as we bind the // timesheet_uom widget on existing field widgets. @@ -92,9 +92,9 @@ const FieldTimesheetTime = basicFields.FieldFloatTime.extend({ // Check if the timer_start exists and it's not false // In other word, when user clicks on play button, this button // launches the "action_timer_start". - if (this.recordData.timer_start) { + if (this.recordData.timer_start && !this.recordData.timer_pause) { const time = await this._rpc({ - model: this.model, + model: 'timer.timer', method: 'get_server_time', args: [] }); diff --git a/addons/hr_timesheet/tests/test_timesheet.py b/addons/hr_timesheet/tests/test_timesheet.py index eb7e71fddd7..ff786bc268b 100644 --- a/addons/hr_timesheet/tests/test_timesheet.py +++ b/addons/hr_timesheet/tests/test_timesheet.py @@ -4,6 +4,8 @@ from odoo.tests.common import TransactionCase from odoo.exceptions import AccessError, UserError +from datetime import datetime, timedelta + class TestCommonTimesheet(TransactionCase): @@ -35,6 +37,7 @@ class TestCommonTimesheet(TransactionCase): 'allow_timesheets': True, 'partner_id': self.partner.id, 'analytic_account_id': self.analytic_account.id, + 'allow_timesheet_timer': True }) self.task1 = self.env['project.task'].create({ 'name': 'Task One', @@ -82,7 +85,6 @@ class TestCommonTimesheet(TransactionCase): 'user_id': self.user_manager.id, }) - class TestTimesheet(TestCommonTimesheet): def setUp(self): @@ -287,3 +289,29 @@ class TestTimesheet(TestCommonTimesheet): }, { 'amount': -12.0, }]) + + def test_minutes_computing_after_timer_stop(self): + """ Test if unit_amount is updated after stoping a timer """ + Timesheet = self.env['account.analytic.line'] + timesheet_1 = Timesheet.with_user(self.user_employee).create({ + 'project_id': self.project_customer.id, + 'task_id': self.task1.id, + 'name': '/', + 'unit_amount': 1, + }) + + # When the timer is less than 1 minute + now = datetime.now() + timesheet_1.with_user(self.user_employee).action_timer_start() + timesheet_1.with_user(self.user_employee).user_timer_id.timer_start = now - timedelta(seconds=28) + timesheet_1.with_user(self.user_employee).action_timer_stop() + + self.assertEqual(timesheet_1.unit_amount, 1, 'unit_amount still should have the same value') + + # When the timer is greater than 1 minute + now = datetime.now() + timesheet_1.with_user(self.user_employee).action_timer_start() + timesheet_1.with_user(self.user_employee).user_timer_id.timer_start = now - timedelta(minutes=1, seconds=28) + timesheet_1.with_user(self.user_employee).action_timer_stop() + + self.assertGreater(timesheet_1.unit_amount, 1, 'unit_amount should be greated than his last value') diff --git a/addons/hr_timesheet/views/assets.xml b/addons/hr_timesheet/views/assets.xml index cf8f06e6802..34f25b9ab14 100644 --- a/addons/hr_timesheet/views/assets.xml +++ b/addons/hr_timesheet/views/assets.xml @@ -4,10 +4,7 @@ -