diff --git a/core/templates/core/payroll_dashboard.html b/core/templates/core/payroll_dashboard.html index f1fb24a..7400177 100644 --- a/core/templates/core/payroll_dashboard.html +++ b/core/templates/core/payroll_dashboard.html @@ -3266,10 +3266,16 @@ document.addEventListener('DOMContentLoaded', function() { }); // --- Shared filter function (team + loan filters combined) --- + // SAFETY RULE: a filter change may EXCLUDE workers from the + // batch (hidden rows are unticked) but must NEVER silently + // re-tick a worker the admin deliberately unticked — that + // would pay someone the admin chose to skip. Use Select All + // to re-include everyone after changing filters. function applyBatchFilters() { var selectedTeam = filterSelect.value; var loanMode = batchLoanFilter ? batchLoanFilter.value : ''; var rows = tbody.querySelectorAll('tr'); + var allChecked = true; for (var i = 0; i < rows.length; i++) { var row = rows[i]; var teamMatch = !selectedTeam || row.dataset.team === selectedTeam; @@ -3278,13 +3284,17 @@ document.addEventListener('DOMContentLoaded', function() { || (loanMode === 'without' && row.dataset.hasLoan !== 'true'); if (teamMatch && loanMatch) { row.style.display = ''; - row.querySelector('.batch-worker-cb').checked = true; + // keep the admin's manual tick/untick choice + if (!row.querySelector('.batch-worker-cb').checked) { + allChecked = false; + } } else { row.style.display = 'none'; row.querySelector('.batch-worker-cb').checked = false; } } - selectAllCb.checked = true; + // Reflect the real state of the visible rows (not forced on) + selectAllCb.checked = allChecked; updateBatchSummary(data, summary); } @@ -3374,17 +3384,27 @@ document.addEventListener('DOMContentLoaded', function() { 'X-CSRFToken': '{{ csrf_token }}', }, body: JSON.stringify({ workers: workers }), - }).then(function() { + }).then(function(resp) { + // fetch() does NOT reject on HTTP errors (4xx/5xx) — only on + // network failure. A 500 here can mean the batch died partway + // (each worker pays in its own transaction), so surface it + // instead of redirecting as if everything succeeded. + if (!resp.ok) { + throw new Error('server returned ' + resp.status); + } // Redirect to refresh page and show Django success messages window.location.href = '/payroll/'; - }).catch(function() { + }).catch(function(err) { btn.disabled = false; while (btn.firstChild) btn.removeChild(btn.firstChild); var retryIcon = document.createElement('i'); retryIcon.className = 'fas fa-money-bill-wave me-1'; btn.appendChild(retryIcon); btn.appendChild(document.createTextNode('Confirm & Pay All')); - alert('Batch payment failed. Please try again.'); + alert('Batch payment may have partially failed (' + + (err && err.message ? err.message : 'network error') + + '). Check the History tab to see which workers were paid ' + + 'before retrying — do not blindly re-pay everyone.'); }); }); }