# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) from datetime import date from odoo.tests import tagged from odoo.tests.common import TransactionCase @tagged('-at_install', 'post_install', 'fusion_clock') class TestRecurrence(TransactionCase): def setUp(self): super().setUp() self.emp = self.env['hr.employee'].create({'name': 'Rita'}) self.Schedule = self.env['fusion.clock.schedule'] def _seed(self, day): return self.Schedule.create({ 'employee_id': self.emp.id, 'schedule_date': day, 'start_time': 9.0, 'end_time': 17.0, 'break_minutes': 30.0, 'state': 'posted', }) def test_weekly_until_generates_inclusive_series(self): seed = self._seed(date(2026, 6, 1)) rule = self.Schedule.fclk_attach_recurrence(seed, { 'repeat_interval': 1, 'repeat_unit': 'week', 'repeat_type': 'until', 'repeat_until': date(2026, 6, 29)}) rows = self.Schedule.search([('recurrence_id', '=', rule.id)], order='schedule_date') self.assertEqual( rows.mapped('schedule_date'), [date(2026, 6, 1), date(2026, 6, 8), date(2026, 6, 15), date(2026, 6, 22), date(2026, 6, 29)]) # Generated (non-seed) rows are draft until posted. generated = rows.filtered(lambda r: r.schedule_date != date(2026, 6, 1)) self.assertTrue(all(r.state == 'draft' for r in generated)) def test_x_times_counts_seed(self): seed = self._seed(date(2026, 6, 1)) rule = self.Schedule.fclk_attach_recurrence(seed, { 'repeat_interval': 1, 'repeat_unit': 'week', 'repeat_type': 'x_times', 'repeat_number': 3}) rows = self.Schedule.search([('recurrence_id', '=', rule.id)]) self.assertEqual(len(rows), 3, "3 repetitions = seed + 2 generated") def test_interval_two_weeks(self): seed = self._seed(date(2026, 6, 1)) rule = self.Schedule.fclk_attach_recurrence(seed, { 'repeat_interval': 2, 'repeat_unit': 'week', 'repeat_type': 'until', 'repeat_until': date(2026, 7, 1)}) rows = self.Schedule.search([('recurrence_id', '=', rule.id)], order='schedule_date') self.assertEqual(rows.mapped('schedule_date'), [date(2026, 6, 1), date(2026, 6, 15), date(2026, 6, 29)]) def test_stop_deletes_future_drafts_keeps_posted(self): seed = self._seed(date(2026, 6, 1)) rule = self.Schedule.fclk_attach_recurrence(seed, { 'repeat_interval': 1, 'repeat_unit': 'week', 'repeat_type': 'x_times', 'repeat_number': 4}) # Post one generated occurrence. gen = self.Schedule.search([ ('recurrence_id', '=', rule.id), ('schedule_date', '=', date(2026, 6, 8))]) gen.state = 'posted' rule._stop(date(2026, 6, 2)) remaining = self.Schedule.search([('recurrence_id', '=', rule.id)]).mapped('schedule_date') self.assertIn(date(2026, 6, 1), remaining) # seed, before cutoff self.assertIn(date(2026, 6, 8), remaining) # posted, kept self.assertNotIn(date(2026, 6, 15), remaining) # future draft, removed self.assertNotIn(date(2026, 6, 22), remaining) def test_leave_day_skipped(self): self.env['fusion.clock.leave.request'].create({ 'employee_id': self.emp.id, 'leave_date': date(2026, 6, 8), 'date_to': date(2026, 6, 8)}) seed = self._seed(date(2026, 6, 1)) rule = self.Schedule.fclk_attach_recurrence(seed, { 'repeat_interval': 1, 'repeat_unit': 'week', 'repeat_type': 'until', 'repeat_until': date(2026, 6, 15)}) dates = self.Schedule.search([('recurrence_id', '=', rule.id)]).mapped('schedule_date') self.assertNotIn(date(2026, 6, 8), dates, "Leave day should be skipped") self.assertIn(date(2026, 6, 15), dates) def test_clear_recurrence_unlinks_rule_when_empty(self): seed = self._seed(date(2026, 6, 1)) rule = self.Schedule.fclk_attach_recurrence(seed, { 'repeat_interval': 1, 'repeat_unit': 'week', 'repeat_type': 'x_times', 'repeat_number': 3}) rule_id = rule.id self.Schedule.fclk_clear_recurrence(seed) # Seed kept (it's posted), future drafts gone, seed detached. self.assertFalse(seed.recurrence_id) self.assertFalse(self.env['fusion.clock.schedule.recurrence'].browse(rule_id).exists())