feat: 'Managers only' client-side filter on Add-Adjustment picker

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Konrad du Plessis 2026-05-16 13:55:41 +02:00
parent fb8952a323
commit 3a18ea008a
2 changed files with 42 additions and 1 deletions

View File

@ -1079,12 +1079,17 @@
<option value="{{ team.id }}">{{ team.name }}</option>
{% endfor %}
</select>
<select id="addAdjPayTypeFilter" class="form-select form-select-sm" style="max-width: 170px;" aria-label="Filter picker by pay type">
<option value="">All pay types</option>
<option value="daily">Daily only</option>
<option value="fixed">Managers only</option>
</select>
<a href="#" id="adjSelectAll" class="small text-primary text-decoration-none text-nowrap">Select All</a>
<a href="#" id="adjDeselectAll" class="small text-muted text-decoration-none text-nowrap">Clear</a>
</div>
<div style="max-height: 200px; overflow-y: auto; border: 1px solid var(--border-default); border-radius: var(--radius-sm); padding: 8px; background: var(--bg-inset);">
{% for w in all_workers %}
<div class="form-check">
<div class="form-check" data-pay-type="{{ w.pay_type }}">
<input class="form-check-input add-adj-worker" type="checkbox"
name="workers" value="{{ w.id }}" id="addW{{ w.id }}">
<label class="form-check-label" for="addW{{ w.id }}">{{ w.name }}</label>
@ -1998,6 +2003,24 @@ document.addEventListener('DOMContentLoaded', function() {
});
}
// Pay-type filter: show/hide picker rows by data-pay-type.
// Display-only — selection state still lives on the checkboxes;
// hidden rows keep whatever checked state they had. "All" (empty
// value) reveals every row again. Same pattern as the pending-table
// team/loan client-side filters.
var addAdjPayTypeFilter = document.getElementById('addAdjPayTypeFilter');
if (addAdjPayTypeFilter) {
addAdjPayTypeFilter.addEventListener('change', function() {
var want = this.value; // '', 'daily', or 'fixed'
addAdjWorkerCheckboxes.forEach(function(cb) {
var row = cb.closest('.form-check');
if (!row) return;
var rowType = row.getAttribute('data-pay-type') || '';
row.style.display = (!want || rowType === want) ? '' : 'none';
});
});
}
// === QUICK ADJUST BUTTON ===
// Opens the Add Adjustment modal with one worker pre-checked
// and their most recent project pre-selected.

View File

@ -3826,6 +3826,24 @@ class ManagerSalariedPayUITests(TestCase):
self.assertContains(resp, 'Manager / Salaried')
self.assertContains(resp, '>Daily</span>')
def test_add_adjustment_modal_has_pay_type_scaffolding(self):
# The Add-Adjustment modal must (a) still include a manager in
# the picker (the must-stay-payable invariant), (b) tag that
# row with data-pay-type="fixed", and (c) render the client-side
# "pay type" filter <select>. The toggle's runtime hide/show is
# verified by Konrad's manual checklist (it's vanilla JS).
mgr = Worker.objects.create(
name='Modal Mgr', id_number='MM-1',
monthly_salary=Decimal('40000'), pay_type='fixed')
resp = self.client.get('/payroll/')
self.assertEqual(resp.status_code, 200)
# (a) invariant: manager present in the modal picker queryset
self.assertIn(mgr, resp.context['all_workers'])
# (b) the row carries the DB pay_type value as a data attribute
self.assertContains(resp, 'data-pay-type="fixed"')
# (c) the client-side filter control exists
self.assertContains(resp, 'id="addAdjPayTypeFilter"')
def test_salary_immediate_payslip_has_no_zero_days_line(self):
# Absorbed Task-5 review #2: an immediate Salary payment's payslip
# must use the clean single-line layout — NOT the generic