From 55face9e6a9c99e5a832d837b401bf4c77471c1c Mon Sep 17 00:00:00 2001 From: Moritz Neeb Date: Thu, 21 May 2026 12:22:22 +0200 Subject: [PATCH] feat: #704 extend shareowner view with capability experience filter The filter checks whether the user completed a shift in the last 6 months that required the selected capability. --- tapir/accounts/models.py | 11 ++++++++++- tapir/coop/config.py | 1 + tapir/coop/views/shareowner.py | 16 ++++++++++++++++ tapir/shifts/apps.py | 4 ++++ tapir/shifts/utils.py | 17 ++++++++++++++++- 5 files changed, 47 insertions(+), 2 deletions(-) diff --git a/tapir/accounts/models.py b/tapir/accounts/models.py index 6a07900f2..bcd1e2daa 100644 --- a/tapir/accounts/models.py +++ b/tapir/accounts/models.py @@ -14,7 +14,10 @@ from phonenumber_field.modelfields import PhoneNumberField from tapir import utils, settings -from tapir.coop.config import get_ids_of_users_registered_to_a_shift_with_capability +from tapir.coop.config import ( + get_ids_of_users_registered_to_a_shift_with_capability, + get_ids_of_users_recently_completed_a_shift_with_capability, +) from tapir.core.config import help_text_displayed_name from tapir.log.models import UpdateModelLogEntry from tapir.settings import ( @@ -52,6 +55,12 @@ def has_capability(self, capability: str): shift_user_data__capabilities__contains=[capability] ).distinct() + def has_recent_capability_experience(self, capability: str): + user_ids = get_ids_of_users_recently_completed_a_shift_with_capability[0]( + capability + ) + return self.filter(id__in=user_ids).distinct() + class TapirUserManager(UserManager.from_queryset(TapirUserQuerySet)): use_in_migrations = True diff --git a/tapir/coop/config.py b/tapir/coop/config.py index 8c9ed96df..bc519bc8d 100644 --- a/tapir/coop/config.py +++ b/tapir/coop/config.py @@ -10,6 +10,7 @@ # This is not very clean, we need a better solution to inject member filters from the shift app into the coop app # without adding a dependency from the coop app to the shift app get_ids_of_users_registered_to_a_shift_with_capability = [] +get_ids_of_users_recently_completed_a_shift_with_capability = [] URL_MEMBER_MANUAL = "https://wiki.supercoop.de/wiki/Member_Manual" diff --git a/tapir/coop/views/shareowner.py b/tapir/coop/views/shareowner.py index c53e4f52c..b264fee59 100644 --- a/tapir/coop/views/shareowner.py +++ b/tapir/coop/views/shareowner.py @@ -631,6 +631,14 @@ def __init__(self, *args, **kwargs): method="has_capability_filter", label=_("Has qualification"), ) + has_recent_capability_experience = ChoiceFilter( + choices=[ + (capability, capability_name) + for capability, capability_name in SHIFT_USER_CAPABILITY_CHOICES.items() + ], + method="has_recent_capability_experience_filter", + label=_("Has completed a shift with this qualification in the last 6 months"), + ) not_has_capability = ChoiceFilter( choices=[ (capability, capability_name) @@ -734,6 +742,14 @@ def has_capability_filter( user__in=TapirUser.objects.has_capability(value) ).distinct() + @staticmethod + def has_recent_capability_experience_filter( + queryset: ShareOwner.ShareOwnerQuerySet, name, value: str + ): + return queryset.filter( + user__in=TapirUser.objects.has_recent_capability_experience(value) + ).distinct() + @staticmethod def not_has_capability_filter( queryset: ShareOwner.ShareOwnerQuerySet, name, value: str diff --git a/tapir/shifts/apps.py b/tapir/shifts/apps.py index 08c74bd73..30c7e0604 100644 --- a/tapir/shifts/apps.py +++ b/tapir/shifts/apps.py @@ -5,6 +5,7 @@ from tapir.coop.config import ( on_welcome_session_attendance_update, get_ids_of_users_registered_to_a_shift_with_capability, + get_ids_of_users_recently_completed_a_shift_with_capability, ) from tapir.core.config import sidebar_link_groups, feature_flag_solidarity_shifts from tapir.settings import PERMISSION_SHIFTS_MANAGE @@ -30,6 +31,9 @@ def ready(self): get_ids_of_users_registered_to_a_shift_with_capability.append( utils.get_ids_of_users_registered_to_a_shift_with_capability ) + get_ids_of_users_recently_completed_a_shift_with_capability.append( + utils.get_ids_of_users_recently_completed_a_shift_with_capability + ) @classmethod def register_sidebar_links(cls): diff --git a/tapir/shifts/utils.py b/tapir/shifts/utils.py index 7b83fb15e..42f311b75 100644 --- a/tapir/shifts/utils.py +++ b/tapir/shifts/utils.py @@ -1,5 +1,5 @@ from calendar import HTMLCalendar, month_name, day_abbr -from datetime import datetime, date +from datetime import datetime, date, timedelta from django.utils import timezone from django.utils.translation import gettext_lazy as _ @@ -145,6 +145,21 @@ def get_ids_of_users_registered_to_a_shift_with_capability( ) +def get_ids_of_users_recently_completed_a_shift_with_capability( + capability: ShiftUserCapability, +): + six_months_ago = timezone.now().date() - timedelta(weeks=26) + return ( + ShiftAttendance.objects.filter( + slot__required_capabilities__contains=[capability], + state__in=[ShiftAttendance.State.DONE], + slot__shift__start_time__gt=six_months_ago, + ) + .distinct() + .values_list("user__id", flat=True) + ) + + def get_attendance_mode_display(attendance_mode: str) -> str: for mode_choice in SHIFT_ATTENDANCE_MODE_CHOICES: if mode_choice[0] == attendance_mode: