diff --git a/engine/apps/schedules/models/custom_on_call_shift.py b/engine/apps/schedules/models/custom_on_call_shift.py index e39dd336..26ecc5b1 100644 --- a/engine/apps/schedules/models/custom_on_call_shift.py +++ b/engine/apps/schedules/models/custom_on_call_shift.py @@ -324,12 +324,14 @@ class CustomOnCallShift(models.Model): break last_start = start day = CustomOnCallShift.ICAL_WEEKDAY_MAP[start.weekday()] - if (user_group_id, day, i) in combinations: - all_rotations_checked = True - break + # double-check day is valid (when until is set, we may get unexpected days) + if day in self.by_day: + if (user_group_id, day, i) in combinations: + all_rotations_checked = True + break - starting_dates.append(start) - combinations.append((user_group_id, day, i)) + starting_dates.append(start) + combinations.append((user_group_id, day, i)) # get next event date following the original rule event_ical = self.generate_ical(start, 1, None, 1, time_zone, custom_rrule=day_by_day_rrule) start = self.get_rotation_date(event_ical, get_next_date=True, interval=1) @@ -386,7 +388,10 @@ class CustomOnCallShift(models.Model): if self.frequency is not None and self.by_day and start is not None: start_day = CustomOnCallShift.ICAL_WEEKDAY_MAP[start.weekday()] if start_day not in self.by_day: - expected_start_day = min(CustomOnCallShift.ICAL_WEEKDAY_REVERSE_MAP[d] for d in self.by_day) + # when calculating first start date, make sure to sort days using week_start + sorted_days = [i % 7 for i in range(self.week_start, self.week_start + 7)] + selected_days = [CustomOnCallShift.ICAL_WEEKDAY_REVERSE_MAP[d] for d in self.by_day] + expected_start_day = [d for d in sorted_days if d in selected_days][0] delta = (expected_start_day - start.weekday()) % 7 start = start + datetime.timedelta(days=delta) diff --git a/engine/apps/schedules/tests/test_custom_on_call_shift.py b/engine/apps/schedules/tests/test_custom_on_call_shift.py index 3dff4c72..b6c769b7 100644 --- a/engine/apps/schedules/tests/test_custom_on_call_shift.py +++ b/engine/apps/schedules/tests/test_custom_on_call_shift.py @@ -1712,3 +1712,80 @@ def test_until_rrule_must_be_utc( expected_rrule = f"RRULE:FREQ=WEEKLY;UNTIL={ical_rrule_until}Z;INTERVAL=4;WKST=SU" assert expected_rrule in ical_data + + +@pytest.mark.django_db +def test_week_start_changed_daily_shift( + make_organization_and_user, + make_schedule, + make_on_call_shift, +): + organization, user_1 = make_organization_and_user() + schedule = make_schedule(organization, schedule_class=OnCallScheduleWeb, time_zone="Europe/Warsaw") + + now = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0) + today_weekday = now.weekday() + last_sunday = now - timezone.timedelta(days=7 + (today_weekday + 1) % 7) + last_saturday = last_sunday - timezone.timedelta(days=1) + + # set week start to Sunday, so first event should be on last_sunday itself + data = { + "priority_level": 1, + "start": last_saturday, + "rotation_start": last_sunday, + "duration": timezone.timedelta(seconds=3600), + "frequency": CustomOnCallShift.FREQUENCY_DAILY, + "by_day": ["MO", "SU"], + "week_start": 5, # SU + "interval": 1, + "schedule": schedule, + } + on_call_shift = make_on_call_shift( + organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data + ) + rolling_users = [[user_1]] + on_call_shift.add_rolling_users(rolling_users) + + ical_data = on_call_shift.convert_to_ical() + expected_start = "DTSTART;VALUE=DATE-TIME:{}T000000Z".format(last_sunday.strftime("%Y%m%d")) + assert expected_start in ical_data + + +@pytest.mark.django_db +def test_week_start_changed_daily_shift_until( + make_organization_and_user, + make_schedule, + make_on_call_shift, +): + organization, user_1 = make_organization_and_user() + schedule = make_schedule(organization, schedule_class=OnCallScheduleWeb, time_zone="Europe/Warsaw") + + now = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0) + today_weekday = now.weekday() + last_sunday = now - timezone.timedelta(days=7 + (today_weekday + 1) % 7) + last_saturday = last_sunday - timezone.timedelta(days=1) + thursday = last_sunday + timezone.timedelta(days=4) + + data = { + "priority_level": 1, + "start": last_saturday, + "rotation_start": last_sunday, + "duration": timezone.timedelta(seconds=3600), + "frequency": CustomOnCallShift.FREQUENCY_DAILY, + "by_day": ["MO", "SU"], + "week_start": 5, # SU + "interval": 1, + "until": thursday, + "schedule": schedule, + } + on_call_shift = make_on_call_shift( + organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data + ) + rolling_users = [[user_1]] + on_call_shift.add_rolling_users(rolling_users) + + ical_data = on_call_shift.convert_to_ical() + # setting UNTIL to Thursday was generating extra events for current week Wednesday and Thursday + unexpected_by_days = ("BYDAY=WE", "BYDAY=TH") + for unexpected in unexpected_by_days: + assert unexpected not in ical_data