Add 'Exclude workers with loans' checkbox to batch pay modal

Backend adds has_loan flag per worker (checks active Loans).
Frontend shows checkbox only when any eligible worker has a loan.
Combined with team filter in a shared applyBatchFilters() function
that shows/hides rows based on both filters simultaneously.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Konrad du Plessis 2026-03-24 22:45:49 +02:00
parent 00f16df8b1
commit 695b7cb3f1
2 changed files with 37 additions and 5 deletions

View File

@ -2079,6 +2079,7 @@ document.addEventListener('DOMContentLoaded', function() {
function buildWorkerRow(w, idx) {
var tr = document.createElement('tr');
tr.dataset.team = w.team_name; // Used by team filter
tr.dataset.hasLoan = w.has_loan ? 'true' : 'false'; // Used by loan filter
// Checkbox cell
var tdCb = document.createElement('td');
@ -2272,6 +2273,26 @@ document.addEventListener('DOMContentLoaded', function() {
filterSelect.appendChild(opt);
});
filterRow.appendChild(filterSelect);
// Only show the "Exclude loans" checkbox if any worker has a loan
var anyHasLoan = data.eligible.some(function(w) { return w.has_loan; });
var excludeLoansCheck = null;
if (anyHasLoan) {
var loanDiv = document.createElement('div');
loanDiv.className = 'form-check ms-3 mb-0';
excludeLoansCheck = document.createElement('input');
excludeLoansCheck.type = 'checkbox';
excludeLoansCheck.className = 'form-check-input';
excludeLoansCheck.id = 'batchExcludeLoans';
loanDiv.appendChild(excludeLoansCheck);
var loanLabel = document.createElement('label');
loanLabel.className = 'form-check-label text-muted small';
loanLabel.setAttribute('for', 'batchExcludeLoans');
loanLabel.textContent = 'Exclude workers with loans';
loanDiv.appendChild(loanLabel);
filterRow.appendChild(loanDiv);
}
body.appendChild(filterRow);
// --- Summary header ---
@ -2358,14 +2379,16 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
// --- Team filter behavior ---
// Shows/hides rows, checks matching workers, unchecks hidden ones
filterSelect.addEventListener('change', function() {
// --- Shared filter function (team + loan filters combined) ---
function applyBatchFilters() {
var selectedTeam = filterSelect.value;
var excludeLoans = excludeLoansCheck ? excludeLoansCheck.checked : false;
var rows = tbody.querySelectorAll('tr');
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
if (!selectedTeam || row.dataset.team === selectedTeam) {
var teamMatch = !selectedTeam || row.dataset.team === selectedTeam;
var loanMatch = !excludeLoans || row.dataset.hasLoan !== 'true';
if (teamMatch && loanMatch) {
row.style.display = '';
row.querySelector('.batch-worker-cb').checked = true;
} else {
@ -2375,7 +2398,12 @@ document.addEventListener('DOMContentLoaded', function() {
}
selectAllCb.checked = true;
updateBatchSummary(data, summary);
});
}
filterSelect.addEventListener('change', applyBatchFilters);
if (excludeLoansCheck) {
excludeLoansCheck.addEventListener('change', applyBatchFilters);
}
// --- Skipped workers (collapsible) ---
if (data.skipped.length > 0) {

View File

@ -1505,6 +1505,9 @@ def batch_pay_preview(request):
else:
period_display = "All unpaid"
# Check if worker has any active loans or advances
has_loan = Loan.objects.filter(worker=worker, active=True).exists()
eligible.append({
'worker_id': worker.id,
'worker_name': worker.name,
@ -1516,6 +1519,7 @@ def batch_pay_preview(request):
'net_pay': float(net),
'log_ids': unpaid_log_ids,
'adj_ids': unpaid_adj_ids,
'has_loan': has_loan,
})
total_amount += net