From 4724c60a3542701c3ec5123ff6f4d35235f04317 Mon Sep 17 00:00:00 2001 From: Cyril VINH-TUNG Date: Mon, 24 Aug 2026 07:34:57 +0000 Subject: [PATCH] hr_shift: current_shift_id must be the shift active right now The compute filled current_shift_id with any shift scheduled today using a UTC-day window. In non-UTC timezones (GMT-10), the day boundary splits across two UTC days and same-day shifts can raise a SingletonError on the Many2one assignment. Switch _shift_of_date's domain from containment to overlap and call it with (now, now) so the field matches the shift covering this instant, as its name implies. Night shifts crossing midnight are covered as a side effect. _shift_of_date is kept as a public hook. Assisted-by: Claude Opus 4.7 --- hr_shift/models/hr_employee.py | 9 ++---- hr_shift/tests/test_hr_shift.py | 52 +++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/hr_shift/models/hr_employee.py b/hr_shift/models/hr_employee.py index 5adba80..db23f1c 100644 --- a/hr_shift/models/hr_employee.py +++ b/hr_shift/models/hr_employee.py @@ -26,20 +26,17 @@ def _shift_of_date(self, min_time, max_time): [ ("employee_id", "=", self.id), ("state", "=", "assigned"), - ("start_time", ">=", min_time), - ("end_time", "<=", max_time), + ("start_time", "<=", max_time), + ("end_time", ">=", min_time), ] ) ) def _compute_current_shift_id(self): """Current shift for a given employee if any""" - today = fields.Date.today() now = fields.Datetime.now() - min_time = fields.datetime.combine(today, now.min.time()) - max_time = fields.datetime.combine(today, now.max.time()) for employee in self: - employee.current_shift_id = employee._shift_of_date(min_time, max_time) + employee.current_shift_id = employee._shift_of_date(now, now) def _get_employee_working_now(self): # Get shift info if available diff --git a/hr_shift/tests/test_hr_shift.py b/hr_shift/tests/test_hr_shift.py index ae4fa2a..a596082 100644 --- a/hr_shift/tests/test_hr_shift.py +++ b/hr_shift/tests/test_hr_shift.py @@ -3,6 +3,7 @@ from datetime import datetime import pytz +from freezegun import freeze_time from odoo import fields from odoo.tests import Form @@ -155,3 +156,54 @@ def test_hr_shift_planning_full(self): shift_b_line_1 = shift_b.line_ids.filtered(lambda x: x.day_number == "1") self.assertEqual(shift_b_line_1.state, "assigned") self.assertEqual(shift_b_line_1.template_id, self.template_afternoon) + + @freeze_time("2025-01-13 08:00:00") + def test_hr_shift_current_shift_id_two_shifts_same_day_morning(self): + self.planning.generate_shifts() + shift = self.planning.shift_ids.filtered( + lambda x: x.employee_id == self.employee_a + ) + line_morning = shift.line_ids.filtered(lambda x: x.day_number == "0") + line_morning.template_id = self.template_morning + self.env["hr.shift.planning.line"].create( + { + "shift_id": shift.id, + "day_number": "0", + "template_id": self.template_afternoon.id, + } + ) + self.assertEqual(self.employee_a.current_shift_id, line_morning) + + @freeze_time("2025-01-13 14:00:00") + def test_hr_shift_current_shift_id_two_shifts_same_day_afternoon(self): + self.planning.generate_shifts() + shift = self.planning.shift_ids.filtered( + lambda x: x.employee_id == self.employee_a + ) + line_morning = shift.line_ids.filtered(lambda x: x.day_number == "0") + line_morning.template_id = self.template_morning + line_afternoon = self.env["hr.shift.planning.line"].create( + { + "shift_id": shift.id, + "day_number": "0", + "template_id": self.template_afternoon.id, + } + ) + self.assertEqual(self.employee_a.current_shift_id, line_afternoon) + + @freeze_time("2025-01-13 20:30:00") + def test_hr_shift_current_shift_id_two_shifts_same_day_after_both(self): + self.planning.generate_shifts() + shift = self.planning.shift_ids.filtered( + lambda x: x.employee_id == self.employee_a + ) + line_morning = shift.line_ids.filtered(lambda x: x.day_number == "0") + line_morning.template_id = self.template_morning + self.env["hr.shift.planning.line"].create( + { + "shift_id": shift.id, + "day_number": "0", + "template_id": self.template_afternoon.id, + } + ) + self.assertFalse(self.employee_a.current_shift_id)