diff --git a/tapir/shifts/config.py b/tapir/shifts/config.py index 378fbb30..02afd422 100644 --- a/tapir/shifts/config.py +++ b/tapir/shifts/config.py @@ -1,7 +1,5 @@ import datetime -from tapir.utils.shortcuts import get_timezone_aware_datetime - cycle_start_dates = [ datetime.date(year=2022, month=4, day=11), ] @@ -18,8 +16,3 @@ FEATURE_FLAG_FLYING_MEMBERS_REGISTRATION_REMINDER = ( "feature_flags.shifts.flying_members_registration_reminder" ) - -ATTENDANCE_MODE_REFACTOR_DATE = datetime.date(year=2024, month=11, day=11) -ATTENDANCE_MODE_REFACTOR_DATETIME = get_timezone_aware_datetime( - ATTENDANCE_MODE_REFACTOR_DATE, datetime.time(hour=0, minute=0) -) diff --git a/tapir/shifts/services/frozen_status_history_service.py b/tapir/shifts/services/frozen_status_history_service.py index 9aa41da1..892ac4fd 100644 --- a/tapir/shifts/services/frozen_status_history_service.py +++ b/tapir/shifts/services/frozen_status_history_service.py @@ -12,10 +12,8 @@ from django.db.models.functions import Coalesce from django.utils import timezone -from tapir.shifts.config import ATTENDANCE_MODE_REFACTOR_DATETIME from tapir.shifts.models import ( ShiftUserData, - ShiftAttendanceMode, UpdateShiftUserDataLogEntry, ) from tapir.utils.shortcuts import ensure_datetime @@ -59,77 +57,6 @@ def annotate_shift_user_data_queryset_with_is_frozen_at_datetime( queryset = queryset.annotate( **{cls.ANNOTATION_IS_FROZEN_DATE_CHECK: Value(at_datetime)} ) - if at_datetime < ATTENDANCE_MODE_REFACTOR_DATETIME: - return cls._annotate_shift_user_data_queryset_with_is_frozen_at_datetime_before_refactor( - queryset, at_datetime, attendance_mode_prefix - ) - return cls._annotate_shift_user_data_queryset_with_is_frozen_at_datetime_after_refactor( - queryset, at_datetime, attendance_mode_prefix - ) - - @classmethod - def _annotate_shift_user_data_queryset_with_is_frozen_at_datetime_before_refactor( - cls, - queryset: QuerySet, - at_datetime: datetime.datetime = None, - attendance_mode_prefix=None, - ): - queryset = queryset.annotate( - attendance_mode_from_log_entry=Subquery( - UpdateShiftUserDataLogEntry.objects.filter( - user_id=OuterRef("user_id"), - created_date__gte=at_datetime, - old_values__attendance_mode__isnull=False, - ) - .order_by("created_date") - .values("old_values__attendance_mode")[:1], - output_field=CharField(), - ) - ) - - queryset = queryset.annotate( - is_frozen_from_log_entry=Subquery( - UpdateShiftUserDataLogEntry.objects.filter( - user_id=OuterRef("user_id"), - created_date__gte=at_datetime, - ) - .order_by("created_date") - .values("old_values__is_frozen")[:1], - output_field=CharField(), - ) - ) - - queryset = queryset.annotate( - is_frozen_at_date=Case( - When( - attendance_mode_from_log_entry=ShiftAttendanceMode.FROZEN, - then=Value(True), - ), - When( - is_frozen_from_log_entry="True", - then=Value(True), - ), - When( - is_frozen_from_log_entry="False", - then=Value(False), - ), - default=( - "is_frozen" - if not attendance_mode_prefix - else f"{attendance_mode_prefix}__is_frozen" - ), - ) - ) - - return queryset - - @classmethod - def _annotate_shift_user_data_queryset_with_is_frozen_at_datetime_after_refactor( - cls, - queryset: QuerySet, - at_datetime: datetime.datetime = None, - attendance_mode_prefix=None, - ): queryset = queryset.annotate( is_frozen_from_log_entry_as_string=Subquery( UpdateShiftUserDataLogEntry.objects.filter( @@ -151,19 +78,20 @@ def _annotate_shift_user_data_queryset_with_is_frozen_at_datetime_after_refactor ) ) - queryset = queryset.annotate( - is_frozen_at_date=Coalesce( - "is_frozen_from_log_entry_as_bool", - ( - "is_frozen" - if attendance_mode_prefix is None - else f"{attendance_mode_prefix}__is_frozen" + return queryset.annotate( + **{ + cls.ANNOTATION_IS_FROZEN_DATE_CHECK: Value(at_datetime), + cls.ANNOTATION_IS_FROZEN_AT_DATE: Coalesce( + "is_frozen_from_log_entry_as_bool", + ( + "is_frozen" + if attendance_mode_prefix is None + else f"{attendance_mode_prefix}__is_frozen" + ), ), - ), + } ) - return queryset - @classmethod def annotate_share_owner_queryset_with_is_frozen_at_datetime( cls, queryset: QuerySet, at_datetime: datetime.datetime = None diff --git a/tapir/shifts/services/frozen_status_management_service.py b/tapir/shifts/services/frozen_status_management_service.py index 523b7f61..ae92a9ac 100644 --- a/tapir/shifts/services/frozen_status_management_service.py +++ b/tapir/shifts/services/frozen_status_management_service.py @@ -17,7 +17,6 @@ from tapir.shifts.emails.unfreeze_notification_email import UnfreezeNotificationEmail from tapir.shifts.models import ( ShiftUserData, - ShiftAttendanceMode, UpdateShiftUserDataLogEntry, ShiftAttendanceTemplate, ShiftAccountEntry, @@ -179,20 +178,3 @@ def unfreeze_and_send_notification_email( ) email = UnfreezeNotificationEmail() email.send_to_tapir_user(actor=actor, recipient=shift_user_data.user) - - @staticmethod - def _get_last_attendance_mode_before_frozen(shift_user_data: ShiftUserData): - last_freeze_log_entry = ( - UpdateShiftUserDataLogEntry.objects.filter( - new_values__attendance_mode=ShiftAttendanceMode.FROZEN, - user=shift_user_data.user, - ) - .order_by("-created_date") - .first() - ) - - return ( - last_freeze_log_entry.old_values["attendance_mode"] - if last_freeze_log_entry - else ShiftAttendanceMode.FLYING - ) diff --git a/tapir/shifts/tests/test_frozen_status_history_service.py b/tapir/shifts/tests/test_frozen_status_history_service.py index 657d778a..ffa1fcba 100644 --- a/tapir/shifts/tests/test_frozen_status_history_service.py +++ b/tapir/shifts/tests/test_frozen_status_history_service.py @@ -5,11 +5,9 @@ from tapir.accounts.models import TapirUser from tapir.accounts.tests.factories.factories import TapirUserFactory -from tapir.shifts import config from tapir.shifts.models import ( ShiftUserData, UpdateShiftUserDataLogEntry, - ShiftAttendanceMode, ) from tapir.shifts.services.frozen_status_history_service import ( FrozenStatusHistoryService, @@ -92,123 +90,8 @@ def test_isFrozenAtDatetime_annotatedNotFrozen_returnsFalse(self): ) ) - @patch.object( - FrozenStatusHistoryService, - "_annotate_shift_user_data_queryset_with_is_frozen_at_datetime_before_refactor", - ) - @patch.object( - FrozenStatusHistoryService, - "_annotate_shift_user_data_queryset_with_is_frozen_at_datetime_after_refactor", - ) - def test_annotateShiftUserDataQuerysetWithIsFrozenAtDatetime_givenDateIsBeforeRefactor_useCorrectAnnotationMethod( - self, - mock_annotate_shift_user_data_queryset_with_is_frozen_at_datetime_after_refactor: Mock, - mock_annotate_shift_user_data_queryset_with_is_frozen_at_datetime_before_refactor: Mock, - ): - expected_result = Mock() - mock_annotate_shift_user_data_queryset_with_is_frozen_at_datetime_before_refactor.return_value = ( - expected_result - ) - reference_datetime = ( - config.ATTENDANCE_MODE_REFACTOR_DATETIME - datetime.timedelta(days=1) - ) - - actual_result = FrozenStatusHistoryService.annotate_shift_user_data_queryset_with_is_frozen_at_datetime( - ShiftUserData.objects.none(), - reference_datetime, - ) - - self.assertEqual(actual_result, expected_result) - mock_annotate_shift_user_data_queryset_with_is_frozen_at_datetime_before_refactor.assert_called_once() - mock_annotate_shift_user_data_queryset_with_is_frozen_at_datetime_after_refactor.assert_not_called() - - @patch.object( - FrozenStatusHistoryService, - "_annotate_shift_user_data_queryset_with_is_frozen_at_datetime_before_refactor", - ) - @patch.object( - FrozenStatusHistoryService, - "_annotate_shift_user_data_queryset_with_is_frozen_at_datetime_after_refactor", - ) - def test_annotateShiftUserDataQuerysetWithIsFrozenAtDatetime_givenDateIsAfterRefactor_useCorrectAnnotationMethod( - self, - mock_annotate_shift_user_data_queryset_with_is_frozen_at_datetime_after_refactor: Mock, - mock_annotate_shift_user_data_queryset_with_is_frozen_at_datetime_before_refactor: Mock, - ): - expected_result = Mock() - mock_annotate_shift_user_data_queryset_with_is_frozen_at_datetime_after_refactor.return_value = ( - expected_result - ) - reference_datetime = ( - config.ATTENDANCE_MODE_REFACTOR_DATETIME + datetime.timedelta(days=1) - ) - - actual_result = FrozenStatusHistoryService.annotate_shift_user_data_queryset_with_is_frozen_at_datetime( - ShiftUserData.objects.none(), - reference_datetime, - ) - - self.assertEqual(actual_result, expected_result) - mock_annotate_shift_user_data_queryset_with_is_frozen_at_datetime_before_refactor.assert_not_called() - mock_annotate_shift_user_data_queryset_with_is_frozen_at_datetime_after_refactor.assert_called_once() - - def test_annotateShiftUserDataQuerysetWithIsFrozenAtDatetimeBeforeRefactor_noRelevantLogEntry_annotatesCurrentValue( - self, - ): - tapir_user: TapirUser = TapirUserFactory.create() - tapir_user.shift_user_data.is_frozen = False - tapir_user.shift_user_data.save() - reference_datetime = timezone.now() - log_entry_in_the_past = UpdateShiftUserDataLogEntry.objects.create( - user=tapir_user, - old_values={"attendance_mode": ShiftAttendanceMode.FROZEN}, - new_values={}, - ) - log_entry_in_the_past.created_date = reference_datetime - datetime.timedelta( - days=1 - ) - log_entry_in_the_past.save() - - log_entry_irrelevant = UpdateShiftUserDataLogEntry.objects.create( - user=tapir_user, - old_values={"shift_partner": "12"}, - new_values={"shift_partner": "13"}, - ) - log_entry_irrelevant.created_date = reference_datetime + datetime.timedelta( - days=1 - ) - log_entry_irrelevant.save() - - queryset = FrozenStatusHistoryService._annotate_shift_user_data_queryset_with_is_frozen_at_datetime_before_refactor( - ShiftUserData.objects.all(), reference_datetime - ) - - shift_user_data = queryset.first() - self.assertEqual( - getattr( - shift_user_data, - FrozenStatusHistoryService.ANNOTATION_IS_FROZEN_AT_DATE, - ), - False, - ) - - def test_annotateShiftUserDataQuerysetWithIsFrozenAtDatetimeBeforeRefactor_hasRelevantLogEntry_annotatesLogEntryValue( - self, - ): - tapir_user: TapirUser = TapirUserFactory.create() - tapir_user.shift_user_data.is_frozen = False - tapir_user.shift_user_data.save() - reference_datetime = timezone.now() - relevant_log_entry = UpdateShiftUserDataLogEntry.objects.create( - user=tapir_user, - old_values={"attendance_mode": ShiftAttendanceMode.FROZEN}, - new_values={}, - ) - relevant_log_entry.created_date = reference_datetime + datetime.timedelta( - days=5 - ) - relevant_log_entry.save() - + @staticmethod + def create_irrelevant_log_entry(tapir_user, reference_datetime): not_relevant_log_entry = UpdateShiftUserDataLogEntry.objects.create( user=tapir_user, old_values={"shift_partner": 182}, @@ -219,20 +102,7 @@ def test_annotateShiftUserDataQuerysetWithIsFrozenAtDatetimeBeforeRefactor_hasRe ) not_relevant_log_entry.save() - queryset = FrozenStatusHistoryService._annotate_shift_user_data_queryset_with_is_frozen_at_datetime_before_refactor( - ShiftUserData.objects.all(), reference_datetime - ) - - shift_user_data = queryset.first() - self.assertEqual( - getattr( - shift_user_data, - FrozenStatusHistoryService.ANNOTATION_IS_FROZEN_AT_DATE, - ), - True, - ) - - def test_annotateShiftUserDataQuerysetWithIsFrozenAtDatetimeAfterRefactor_noRelevantLogEntry_annotatesCurrentValue( + def test_annotateShiftUserDataQuerysetWithIsFrozenAtDatetime_noRelevantLogEntry_annotatesCurrentValue( self, ): tapir_user: TapirUser = TapirUserFactory.create() @@ -249,17 +119,9 @@ def test_annotateShiftUserDataQuerysetWithIsFrozenAtDatetimeAfterRefactor_noRele ) log_entry_in_the_past.save() - not_relevant_log_entry = UpdateShiftUserDataLogEntry.objects.create( - user=tapir_user, - old_values={"shift_partner": 182}, - new_values={"shift_partner": 25}, - ) - not_relevant_log_entry.created_date = reference_datetime + datetime.timedelta( - days=3 - ) - not_relevant_log_entry.save() + self.create_irrelevant_log_entry(tapir_user, reference_datetime) - queryset = FrozenStatusHistoryService._annotate_shift_user_data_queryset_with_is_frozen_at_datetime_after_refactor( + queryset = FrozenStatusHistoryService.annotate_shift_user_data_queryset_with_is_frozen_at_datetime( ShiftUserData.objects.all(), reference_datetime ) @@ -272,7 +134,7 @@ def test_annotateShiftUserDataQuerysetWithIsFrozenAtDatetimeAfterRefactor_noRele False, ) - def test_annotateShiftUserDataQuerysetWithIsFrozenAtDatetimeAfterRefactor_hasRelevantLogEntry_annotatesLogEntryValue( + def test_annotateShiftUserDataQuerysetWithIsFrozenAtDatetime_hasRelevantLogEntry_annotatesLogEntryValue( self, ): tapir_user: TapirUser = TapirUserFactory.create() @@ -289,17 +151,9 @@ def test_annotateShiftUserDataQuerysetWithIsFrozenAtDatetimeAfterRefactor_hasRel ) log_entry_in_the_past.save() - not_relevant_log_entry = UpdateShiftUserDataLogEntry.objects.create( - user=tapir_user, - old_values={"shift_partner": 182}, - new_values={"shift_partner": 25}, - ) - not_relevant_log_entry.created_date = reference_datetime + datetime.timedelta( - days=3 - ) - not_relevant_log_entry.save() + self.create_irrelevant_log_entry(tapir_user, reference_datetime) - queryset = FrozenStatusHistoryService._annotate_shift_user_data_queryset_with_is_frozen_at_datetime_after_refactor( + queryset = FrozenStatusHistoryService.annotate_shift_user_data_queryset_with_is_frozen_at_datetime( ShiftUserData.objects.all(), reference_datetime ) diff --git a/tapir/shifts/tests/test_frozen_status_management_service.py b/tapir/shifts/tests/test_frozen_status_management_service.py index 174c6c0b..e02b5c13 100644 --- a/tapir/shifts/tests/test_frozen_status_management_service.py +++ b/tapir/shifts/tests/test_frozen_status_management_service.py @@ -584,10 +584,6 @@ def test_shouldUnfreezeMember_memberNotRegisteredToEnoughShifts_returnsFalse( @patch( "tapir.shifts.services.frozen_status_management_service.UnfreezeNotificationEmail" ) - @patch.object( - FrozenStatusManagementService, - "_get_last_attendance_mode_before_frozen", - ) @patch.object( FrozenStatusManagementService, "_update_frozen_status_and_create_log_entry", @@ -595,15 +591,11 @@ def test_shouldUnfreezeMember_memberNotRegisteredToEnoughShifts_returnsFalse( def test_unfreezeAndSendNotificationEmail( self, mock_update_frozen_status_and_create_log_entry: Mock, - mock_get_last_attendance_mode_before_frozen: Mock, mock_unfreeze_notification_email_class: Mock, ): shift_user_data = ShiftUserData() shift_user_data.user = TapirUser() actor = TapirUser() - mock_get_last_attendance_mode_before_frozen.return_value = ( - ShiftAttendanceMode.REGULAR - ) FrozenStatusManagementService.unfreeze_and_send_notification_email( shift_user_data, actor @@ -619,59 +611,3 @@ def test_unfreezeAndSendNotificationEmail( mock_email.send_to_tapir_user.assert_called_once_with( actor=actor, recipient=shift_user_data.user ) - - @patch( - "tapir.shifts.services.frozen_status_management_service.UpdateShiftUserDataLogEntry.objects.filter" - ) - def test_getLastAttendanceModeBeforeFrozen_noLogEntryFound_returnsFlying( - self, mock_filter: Mock - ): - shift_user_data = ShiftUserData() - shift_user_data.user = TapirUser() - mock_order_by = mock_filter.return_value.order_by - mock_first = mock_order_by.return_value.first - mock_first.return_value = None - - self.assertEqual( - ShiftAttendanceMode.FLYING, - FrozenStatusManagementService._get_last_attendance_mode_before_frozen( - shift_user_data - ), - ) - - mock_filter.assert_called_once_with( - new_values__attendance_mode=ShiftAttendanceMode.FROZEN, - user=shift_user_data.user, - ) - mock_order_by.assert_called_once_with("-created_date") - mock_first.assert_called_once_with() - - @patch( - "tapir.shifts.services.frozen_status_management_service.UpdateShiftUserDataLogEntry.objects.filter" - ) - def test_getLastAttendanceModeBeforeFrozen_default_returnsLastMode( - self, mock_filter: Mock - ): - shift_user_data = ShiftUserData() - shift_user_data.user = TapirUser() - mock_order_by = mock_filter.return_value.order_by - mock_first = mock_order_by.return_value.first - mock_first.return_value = Mock() - mock_first.return_value.old_values = dict() - mock_first.return_value.old_values["attendance_mode"] = ( - ShiftAttendanceMode.REGULAR - ) - - self.assertEqual( - ShiftAttendanceMode.REGULAR, - FrozenStatusManagementService._get_last_attendance_mode_before_frozen( - shift_user_data - ), - ) - - mock_filter.assert_called_once_with( - new_values__attendance_mode=ShiftAttendanceMode.FROZEN, - user=shift_user_data.user, - ) - mock_order_by.assert_called_once_with("-created_date") - mock_first.assert_called_once_with() diff --git a/tapir/statistics/views/fancy_graph/number_of_frozen_members_view.py b/tapir/statistics/views/fancy_graph/number_of_frozen_members_view.py index ce4b5820..77e8be0a 100644 --- a/tapir/statistics/views/fancy_graph/number_of_frozen_members_view.py +++ b/tapir/statistics/views/fancy_graph/number_of_frozen_members_view.py @@ -1,9 +1,6 @@ import datetime from tapir.coop.models import ShareOwner, MemberStatus -from tapir.coop.services.investing_status_service import InvestingStatusService -from tapir.coop.services.membership_pause_service import MembershipPauseService -from tapir.coop.services.number_of_shares_service import NumberOfSharesService from tapir.settings import PERMISSION_COOP_MANAGE from tapir.shifts.services.frozen_status_history_service import ( FrozenStatusHistoryService, @@ -19,25 +16,9 @@ def calculate_datapoint(self, reference_time: datetime.datetime) -> int: @staticmethod def get_members_frozen_at_datetime(reference_time): - reference_date = reference_time.date() - share_owners = ( - ShareOwner.objects.all() - .prefetch_related("user") - .prefetch_related("user__shift_user_data") - .prefetch_related("share_ownerships") + share_owners = ShareOwner.objects.with_status( + MemberStatus.ACTIVE, reference_time ) - share_owners = NumberOfSharesService.annotate_share_owner_queryset_with_nb_of_active_shares( - share_owners, reference_date - ) - share_owners = ( - MembershipPauseService.annotate_share_owner_queryset_with_has_active_pause( - share_owners, reference_date - ) - ) - share_owners = InvestingStatusService.annotate_share_owner_queryset_with_investing_status_at_datetime( - share_owners, reference_time - ) - share_owners = share_owners.with_status(MemberStatus.ACTIVE) share_owners = FrozenStatusHistoryService.annotate_share_owner_queryset_with_is_frozen_at_datetime( share_owners, reference_time diff --git a/tapir/statistics/views/fancy_graph/number_of_long_term_frozen_members_view.py b/tapir/statistics/views/fancy_graph/number_of_long_term_frozen_members_view.py index 027f5d26..1e6e5a1d 100644 --- a/tapir/statistics/views/fancy_graph/number_of_long_term_frozen_members_view.py +++ b/tapir/statistics/views/fancy_graph/number_of_long_term_frozen_members_view.py @@ -1,7 +1,7 @@ import datetime from tapir.settings import PERMISSION_COOP_MANAGE -from tapir.shifts.models import UpdateShiftUserDataLogEntry, ShiftAttendanceMode +from tapir.shifts.models import UpdateShiftUserDataLogEntry from tapir.statistics.views.fancy_graph.base_view import DatapointView from tapir.statistics.views.fancy_graph.number_of_frozen_members_view import ( NumberOfFrozenMembersAtDateView, @@ -28,31 +28,11 @@ def calculate_datapoint(self, reference_time: datetime.datetime) -> int: .first() ) - if status_change_log_entry: - if ( - reference_time - status_change_log_entry.created_date - ).days > 30 * 6: - count += 1 - continue + if not status_change_log_entry: + # could not find any log entry, we assume the member is frozen long-term + count += 1 - status_change_log_entry = ( - UpdateShiftUserDataLogEntry.objects.filter( - user=share_owner.user, - created_date__lte=reference_time, - new_values__attendance_mode=ShiftAttendanceMode.FROZEN, - ) - .order_by("-created_date") - .first() - ) - - if status_change_log_entry: - if ( - reference_time - status_change_log_entry.created_date - ).days > 30 * 6: - count += 1 - continue - - # could not find any log entry, we assume the member is frozen long-term - count += 1 + if (reference_time - status_change_log_entry.created_date).days > 30 * 6: + count += 1 return count