From 65df9f817e3209b816cba3510ba5b1bd925b65a6 Mon Sep 17 00:00:00 2001 From: Konrad du Plessis Date: Fri, 15 May 2026 19:36:33 +0200 Subject: [PATCH] feat: exclude fixed-salary managers from attendance pickers --- core/forms.py | 7 ++++--- core/tests.py | 37 +++++++++++++++++++++++++++++++++++++ core/views.py | 6 ++++-- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/core/forms.py b/core/forms.py index 82e100e..fe7c0fa 100644 --- a/core/forms.py +++ b/core/forms.py @@ -105,13 +105,14 @@ class AttendanceLogForm(forms.ModelForm): supervised_teams = Team.objects.filter(supervisor=self.user, active=True) self.fields['workers'].queryset = Worker.objects.filter( active=True, - teams__in=supervised_teams - ).distinct() + teams__in=supervised_teams, + ).exclude(pay_type='fixed').distinct() # Only show teams this supervisor manages self.fields['team'].queryset = supervised_teams else: # Admins see everything - self.fields['workers'].queryset = Worker.objects.filter(active=True) + self.fields['workers'].queryset = Worker.objects.filter( + active=True).exclude(pay_type='fixed') self.fields['project'].queryset = Project.objects.filter(active=True) self.fields['team'].queryset = Team.objects.filter(active=True) diff --git a/core/tests.py b/core/tests.py index 3a36e46..d7add91 100644 --- a/core/tests.py +++ b/core/tests.py @@ -3395,3 +3395,40 @@ class ManagerSalariedPayTypeRegistrationTests(TestCase): def test_salary_is_additive(self): from core.views import ADDITIVE_TYPES self.assertIn('Salary', ADDITIVE_TYPES) + + +# === MANAGER / SALARIED PAY - attendance picker exclusion === +# A pay_type='fixed' manager must be impossible to add to a WorkLog. +# They are excluded at the attendance picker + supporting maps so +# they can never be selected for / auto-checked into a WorkLog or +# counted in the attendance cost estimate. +class ManagerSalariedAttendanceExclusionTests(TestCase): + def setUp(self): + self.admin = User.objects.create_user('msa_admin', password='x', is_staff=True) + self.sup = User.objects.create_user('msa_sup', password='x') + self.daily = Worker.objects.create( + name='Daily Del', id_number='MSA-D', monthly_salary=Decimal('6000')) + self.mgr = Worker.objects.create( + name='Mgr Mo', id_number='MSA-M', monthly_salary=Decimal('40000'), + pay_type='fixed') + self.team = Team.objects.create(name='MSA Team', supervisor=self.sup) + self.team.workers.add(self.daily, self.mgr) + + def test_attendance_admin_picker_excludes_fixed(self): + from core.forms import AttendanceLogForm + qs = AttendanceLogForm(user=self.admin).fields['workers'].queryset + self.assertIn(self.daily, qs) + self.assertNotIn(self.mgr, qs) + + def test_attendance_supervisor_picker_excludes_fixed(self): + from core.forms import AttendanceLogForm + qs = AttendanceLogForm(user=self.sup).fields['workers'].queryset + self.assertIn(self.daily, qs) + self.assertNotIn(self.mgr, qs) + + def test_team_workers_map_excludes_fixed(self): + from core.views import _build_team_workers_map + m = _build_team_workers_map(self.admin) + ids = m.get(self.team.id) or m.get(str(self.team.id)) or [] + self.assertIn(self.daily.id, ids) + self.assertNotIn(self.mgr.id, ids) diff --git a/core/views.py b/core/views.py index 3402337..b838bac 100644 --- a/core/views.py +++ b/core/views.py @@ -586,7 +586,8 @@ def _build_team_workers_map(user): teams_qs = Team.objects.filter(active=True).prefetch_related( Prefetch( 'workers', - queryset=Worker.objects.filter(active=True), + # Managers (pay_type='fixed') never go on a WorkLog. + queryset=Worker.objects.filter(active=True).exclude(pay_type='fixed'), to_attr='active_workers_cached', ) ) @@ -789,7 +790,8 @@ def attendance_log(request): # (admins only — supervisors don't see the cost card) worker_rates = {} if is_admin(user): - for w in Worker.objects.filter(active=True): + # Managers (pay_type='fixed') never go on a WorkLog. + for w in Worker.objects.filter(active=True).exclude(pay_type='fixed'): worker_rates[str(w.id)] = str(w.daily_rate) # Build team→workers mapping so the JS can auto-check workers when a