fix: close inline team-map manager-exclusion gap + add cost-rate exclusion test
This commit is contained in:
parent
65df9f817e
commit
0f45d64eea
@ -3432,3 +3432,71 @@ class ManagerSalariedAttendanceExclusionTests(TestCase):
|
||||
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)
|
||||
|
||||
# === Point 4: attendance GET cost-estimate map (worker_rates) ===
|
||||
# The admin attendance form renders a {worker_id: daily_rate} map for
|
||||
# the live cost estimator. A fixed-salary manager must NOT appear in it
|
||||
# (they never go on a WorkLog, so they have no daily attendance cost).
|
||||
# This is a regression GUARD — point 4 was already fixed in 65df9f8, so
|
||||
# this test is expected to pass immediately; it locks the behaviour in.
|
||||
def test_attendance_cost_rates_exclude_fixed(self):
|
||||
self.client.force_login(self.admin)
|
||||
resp = self.client.get(reverse('attendance_log'))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
# Assert on the actual context map (precise — not a whole-page string
|
||||
# scan that could collide with the id appearing elsewhere).
|
||||
worker_rates = resp.context['worker_rates_json']
|
||||
self.assertIn(str(self.daily.id), worker_rates)
|
||||
self.assertNotIn(str(self.mgr.id), worker_rates)
|
||||
|
||||
# === Fix 1: the two inline tw_map re-render branches ===
|
||||
# attendance_log POST has two error/re-render branches that used to build
|
||||
# the team->workers map INLINE without excluding pay_type='fixed'. POST a
|
||||
# form that is VALID but yields zero loggable dates (single Saturday with
|
||||
# the Saturday box unchecked) to hit the "no valid dates" re-render branch,
|
||||
# then assert the rendered team_workers_json excludes the manager.
|
||||
def test_no_valid_dates_rerender_team_map_excludes_fixed(self):
|
||||
import json as _json
|
||||
self.client.force_login(self.admin)
|
||||
project = Project.objects.create(name='MSA Proj')
|
||||
# 2026-05-16 is a Saturday. With include_saturday unchecked and no
|
||||
# end_date, dates_to_log is empty -> "no valid dates" re-render.
|
||||
resp = self.client.post(reverse('attendance_log'), {
|
||||
'date': '2026-05-16',
|
||||
'project': project.id,
|
||||
'team': self.team.id,
|
||||
'workers': [self.daily.id],
|
||||
'overtime_amount': '0.00',
|
||||
})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
tw_map = _json.loads(resp.context['team_workers_json'])
|
||||
ids = tw_map.get(str(self.team.id)) or tw_map.get(self.team.id) or []
|
||||
self.assertIn(self.daily.id, ids)
|
||||
self.assertNotIn(self.mgr.id, ids)
|
||||
|
||||
# === Fix 1: the conflict-warning re-render branch ===
|
||||
# The second inline tw_map builder lives in the conflict-warning branch.
|
||||
# Pre-create a WorkLog so the same worker+date collides, forcing the
|
||||
# conflict re-render, then assert the manager is excluded there too.
|
||||
def test_conflict_rerender_team_map_excludes_fixed(self):
|
||||
import json as _json
|
||||
self.client.force_login(self.admin)
|
||||
project = Project.objects.create(name='MSA Proj 2')
|
||||
# 2026-05-18 is a Monday (a valid loggable weekday).
|
||||
existing = WorkLog.objects.create(
|
||||
date=datetime.date(2026, 5, 18), project=project)
|
||||
existing.workers.add(self.daily)
|
||||
resp = self.client.post(reverse('attendance_log'), {
|
||||
'date': '2026-05-18',
|
||||
'project': project.id,
|
||||
'team': self.team.id,
|
||||
'workers': [self.daily.id],
|
||||
'overtime_amount': '0.00',
|
||||
})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
# Confirm we actually hit the conflict branch (it sets `conflicts`).
|
||||
self.assertTrue(resp.context.get('conflicts'))
|
||||
tw_map = _json.loads(resp.context['team_workers_json'])
|
||||
ids = tw_map.get(str(self.team.id)) or tw_map.get(self.team.id) or []
|
||||
self.assertIn(self.daily.id, ids)
|
||||
self.assertNotIn(self.mgr.id, ids)
|
||||
|
||||
@ -637,10 +637,11 @@ def attendance_log(request):
|
||||
|
||||
if not dates_to_log:
|
||||
messages.warning(request, 'No valid dates in the selected range.')
|
||||
# Still need team_workers_json for the JS even on error re-render
|
||||
tw_map = {}
|
||||
for t in Team.objects.filter(active=True).prefetch_related('workers'):
|
||||
tw_map[t.id] = list(t.workers.filter(active=True).values_list('id', flat=True))
|
||||
# Still need team_workers_json for the JS even on error
|
||||
# re-render. Use the shared helper so this branch applies the
|
||||
# SAME admin/supervisor scoping AND manager (pay_type='fixed')
|
||||
# exclusion as the GET path — no duplicated, drift-prone loop.
|
||||
tw_map = _build_team_workers_map(user)
|
||||
return render(request, 'core/attendance_log.html', {
|
||||
'form': form,
|
||||
'is_admin': is_admin(user),
|
||||
@ -669,10 +670,11 @@ def attendance_log(request):
|
||||
conflict_action = request.POST.get('conflict_action', '')
|
||||
if conflicts and not conflict_action:
|
||||
# Show the conflict warning — let user choose Skip or Overwrite
|
||||
# Still need team_workers_json for the JS even on conflict re-render
|
||||
tw_map = {}
|
||||
for t in Team.objects.filter(active=True).prefetch_related('workers'):
|
||||
tw_map[t.id] = list(t.workers.filter(active=True).values_list('id', flat=True))
|
||||
# Still need team_workers_json for the JS even on conflict
|
||||
# re-render. Use the shared helper so this branch applies the
|
||||
# SAME admin/supervisor scoping AND manager (pay_type='fixed')
|
||||
# exclusion as the GET path — no duplicated, drift-prone loop.
|
||||
tw_map = _build_team_workers_map(user)
|
||||
|
||||
# Pass the selected worker IDs explicitly for the conflict
|
||||
# re-submission forms. We can't use form.data.workers in the
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user