test_unique_employee_date_schedule -> test_multiple_shifts_per_day_allowed; test_invalid_same_day_range_is_rejected -> test_overnight_range_is_accepted; add required reason to the recurrence leave-skip test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
96 lines
4.5 KiB
Python
96 lines
4.5 KiB
Python
# -*- 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, 'reason': 'Vacation',
|
|
'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())
|