diff --git a/core/templates/core/payroll_dashboard.html b/core/templates/core/payroll_dashboard.html index 4f6f983..1e08b54 100644 --- a/core/templates/core/payroll_dashboard.html +++ b/core/templates/core/payroll_dashboard.html @@ -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) { diff --git a/core/views.py b/core/views.py index 8d0090d..0f80c73 100644 --- a/core/views.py +++ b/core/views.py @@ -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