fix(fusion_clock): code-review hardening [19.0.5.0.1]

- _cron_generate: per-rule savepoint isolation (one bad rule can't abort the
  whole daily batch)
- fclk_attach_recurrence: clear an existing recurrence first (no orphaned rule
  generating forever)
- fclk_apply_planner_cell: collapse split rows (search was limit=1 after the
  UNIQUE drop, orphaning extras)
- fclk_release_shift: reject non-posted/open shifts (raw-POST guard)
- delete_open_shift: report success=false when nothing was deleted + JS surfaces it
- _generate: log before removing an empty recurrence
Tests added for collapse, re-attach, draft-release.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-06-04 22:32:44 -04:00
parent 53c292083f
commit 19d484680d
8 changed files with 83 additions and 11 deletions

View File

@@ -75,6 +75,15 @@ class TestOpenShift(TransactionCase):
self.assertTrue(sch.is_open)
self.assertFalse(sch.employee_id)
def test_release_draft_shift_rejected(self):
emp = self.env['hr.employee'].create({'name': 'Drafty'})
future = date.today() + timedelta(days=30)
sch = self.S.create({
'employee_id': emp.id, 'schedule_date': future,
'start_time': 9.0, 'end_time': 17.0, 'state': 'draft'})
with self.assertRaises(ValidationError):
self.S.fclk_release_shift(sch, emp)
def test_bulk_apply_to_many_employees(self):
e1 = self.env['hr.employee'].create({'name': 'Bulk A'})
e2 = self.env['hr.employee'].create({'name': 'Bulk B'})

View File

@@ -83,6 +83,21 @@ class TestRecurrence(TransactionCase):
self.assertNotIn(date(2026, 6, 8), dates, "Leave day should be skipped")
self.assertIn(date(2026, 6, 15), dates)
def test_reattach_recurrence_replaces_old_rule(self):
seed = self._seed(date(2026, 6, 1))
rule1 = self.Schedule.fclk_attach_recurrence(seed, {
'repeat_interval': 1, 'repeat_unit': 'week',
'repeat_type': 'x_times', 'repeat_number': 3})
rule1_id = rule1.id
rule2 = self.Schedule.fclk_attach_recurrence(seed, {
'repeat_interval': 1, 'repeat_unit': 'week',
'repeat_type': 'x_times', 'repeat_number': 2})
# The old rule must be gone (not left generating forever) and the seed
# must point at the new rule.
self.assertFalse(
self.env['fusion.clock.schedule.recurrence'].browse(rule1_id).exists())
self.assertEqual(seed.recurrence_id, rule2)
def test_clear_recurrence_unlinks_rule_when_empty(self):
seed = self._seed(date(2026, 6, 1))
rule = self.Schedule.fclk_attach_recurrence(seed, {

View File

@@ -67,6 +67,23 @@ class TestShiftPlannerModels(TransactionCase):
self.assertEqual(schedule.planned_hours, 8.0)
self.assertEqual(self.Schedule.fclk_hours_display(schedule.planned_hours), '8:00')
def test_planner_cell_collapses_split_rows(self):
# Editing a day's planner cell must collapse any split rows into one
# (the planner cell is the authoritative single entry for the day).
d = date(2026, 1, 20)
self.Schedule.create({
'employee_id': self.employee.id, 'schedule_date': d,
'start_time': 8.0, 'end_time': 12.0, 'state': 'posted'})
self.Schedule.create({
'employee_id': self.employee.id, 'schedule_date': d,
'start_time': 13.0, 'end_time': 17.0, 'state': 'posted'})
self.assertEqual(self.Schedule.search_count([
('employee_id', '=', self.employee.id), ('schedule_date', '=', d)]), 2)
self.Schedule.fclk_apply_planner_cell(self.employee, d, {'input': '9-5'})
self.assertEqual(self.Schedule.search_count([
('employee_id', '=', self.employee.id), ('schedule_date', '=', d)]), 1,
"planner edit should collapse split rows to one")
def test_overnight_range_is_accepted(self):
# Overnight shifts (end on/before start) are supported as of 19.0.5.0.0.
sch = self.Schedule.create({