diff --git a/occurrences/models.py b/occurrences/models.py index 1f2b451e..ba71c331 100644 --- a/occurrences/models.py +++ b/occurrences/models.py @@ -291,11 +291,12 @@ def has_enrolments_system(self) -> bool: def has_space_for_enrolments(self) -> Optional[bool]: """ - Determines whether any (upcoming) occurrence has any space left + Determines whether any upcoming or ongoing occurrence has any space left for enrolments. Returns: - - True: If at least one upcoming occurrence has space for enrolments. + - True: If at least one upcoming or ongoing occurrence has space for + enrolments. - False: If no upcoming occurrences have space for enrolments. - None: If the event doesn't utilize the enrolments system or relies on an external enrolments system. @@ -303,11 +304,20 @@ def has_space_for_enrolments(self) -> Optional[bool]: if not self.has_enrolments_system() or self.has_external_enrolments_system(): return None - return any( + has_upcoming_space = any( occurrence.has_space_left() for occurrence in self.occurrences.filter_upcoming() ) + if self.enrolment_end_days is None: + # No limit to when enrolments can be made before the occurrence start. + return has_upcoming_space or any( + occurrence.has_space_left() + for occurrence in self.occurrences.filter_ongoing() + ) + + return has_upcoming_space + class OccurrenceQueryset(models.QuerySet): def delete(self, *args, **kwargs): @@ -315,11 +325,14 @@ def delete(self, *args, **kwargs): obj.delete() def filter_upcoming(self, *args, **kwargs): - """Filter occurrences that are not not cancelled and - are held in the future. - """ + """Filter occurrences that are not cancelled and are held in the future.""" return self.filter(cancelled=False, start_time__gte=timezone.now()) + def filter_ongoing(self, *args, **kwargs): + """Filter occurrences that are not cancelled and are ongoing at the moment.""" + now = timezone.now() + return self.filter(cancelled=False, start_time__lte=now, end_time__gte=now) + class Occurrence(GDPRModel, SerializableMixin, TimestampedModel): OCCURRENCE_SEAT_TYPE_CHILDREN_COUNT = "children_count" diff --git a/occurrences/schema.py b/occurrences/schema.py index 92555044..e08e0e19 100644 --- a/occurrences/schema.py +++ b/occurrences/schema.py @@ -219,7 +219,8 @@ def resolve_last_occurrence_datetime(self, info, **kwargs): def resolve_has_space_for_enrolments(self, info, **kwargs): """ - Determines whether any upcoming occurrence has any space left for enrolments. + Determines whether any upcoming or ongoing occurrence has any space left for + enrolments. This method leverages the internal `has_space_for_enrolments` logic to ascertain if there are any future event occurrences with available spaces for enrollment. @@ -231,7 +232,8 @@ def resolve_has_space_for_enrolments(self, info, **kwargs): (may not be used in this specific implementation). Returns: - - True: If at least one upcoming occurrence has space for enrolments. + - True: If at least one upcoming or ongoing occurrence has space for + enrolments. - False: If no upcoming occurrences have space for enrolments. - None: If the event doesn't utilize the enrolments system or relies on an external enrolments system. diff --git a/occurrences/tests/test_models.py b/occurrences/tests/test_models.py index a1bda387..231856e9 100644 --- a/occurrences/tests/test_models.py +++ b/occurrences/tests/test_models.py @@ -980,20 +980,90 @@ def test_palvelutarjotin_event_has_space_for_enrolments_returning_boolean( mock_get_event_data, ): p_event = PalvelutarjotinEventFactory() + # Past occurrences + OccurrenceFactory.create_batch( + 4, + p_event=p_event, + cancelled=False, + start_time=timezone.now() - timedelta(days=2), + end_time=timezone.now() - timedelta(days=1), + ) + # Ongoing occurrences + OccurrenceFactory.create_batch( + 2, + p_event=p_event, + cancelled=False, + start_time=timezone.now() - timedelta(days=1), + end_time=timezone.now() + timedelta(days=1), + ) + # Future occurrences OccurrenceFactory.create_batch( 3, p_event=p_event, cancelled=False, start_time=timezone.now() + timedelta(days=1), + end_time=timezone.now() + timedelta(days=2), + ) + assert p_event.occurrences.count() == 9 + assert p_event.occurrences.filter_upcoming().count() == 3 + assert p_event.occurrences.filter_ongoing().count() == 2 + with patch( + "occurrences.models.PalvelutarjotinEvent.has_external_enrolments_system", + return_value=False, + ): + with patch( + "occurrences.models.PalvelutarjotinEvent.has_internal_enrolments_system", + return_value=True, + ): + with patch( + "occurrences.models.Occurrence.has_space_left" + ) as mock_has_space_left: + mock_has_space_left.return_value = False + assert p_event.has_space_for_enrolments() is False + assert mock_has_space_left.call_count == 3 + with patch( + "occurrences.models.PalvelutarjotinEvent.has_external_enrolments_system", + return_value=False, + ): + with patch( + "occurrences.models.PalvelutarjotinEvent.has_internal_enrolments_system", + return_value=True, + ): + with patch( + "occurrences.models.Occurrence.has_space_left" + ) as mock_has_space_left: + # 2nd occurrence returns True, so result is True + # and any-call returns after 2nd round. + mock_has_space_left.side_effect = [False, True, False] + assert p_event.has_space_for_enrolments() is True + assert mock_has_space_left.call_count == 2 + + +@pytest.mark.django_db +def test_palvelutarjotin_event_has_space_for_enrolments_ongoing_events( + mock_get_event_data, +): + """Ongoing events are not considered""" + p_event = PalvelutarjotinEventFactory(enrolment_end_days=None) + # Past occurrences + OccurrenceFactory.create_batch( + 2, + p_event=p_event, + cancelled=False, + start_time=timezone.now() - timedelta(days=2), + end_time=timezone.now() - timedelta(days=1), ) + # Ongoing occurrences OccurrenceFactory.create_batch( 3, p_event=p_event, cancelled=False, start_time=timezone.now() - timedelta(days=1), + end_time=timezone.now() + timedelta(days=1), ) - assert p_event.occurrences.count() == 6 - assert p_event.occurrences.filter_upcoming().count() == 3 + assert p_event.occurrences.count() == 5 + assert p_event.occurrences.filter_upcoming().count() == 0 + assert p_event.occurrences.filter_ongoing().count() == 3 with patch( "occurrences.models.PalvelutarjotinEvent.has_external_enrolments_system", return_value=False,