Update batch pay modal: 3-option loan filter + radio button fix
- Replace "Exclude workers with loans" checkbox with dropdown (All Workers / With loans only / Without loans) in batch pay modal, matching the pending payments table filter style - Fix radio button visual state when switching between "Until Last Paydate" and "Pay All" modes (set checked after DOM append) - Update CLAUDE.md with pending table filter and overdue badge docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3bb75c5615
commit
72d40971f1
@ -103,9 +103,10 @@ python manage.py check # System check
|
||||
- Advance Payment auto-processing: `add_adjustment` immediately creates PayrollRecord + sends payslip when an advance is created. Also auto-creates an "Advance Repayment" adjustment for the next salary cycle. Uses `_send_payslip_email()` helper (shared with `process_payment`)
|
||||
- Advance-to-loan conversion: When an Advance Repayment is only partially paid, `process_payment` changes the Loan's `loan_type` from 'advance' to 'loan' so the remainder is tracked as a regular loan
|
||||
- Split Payslip: Preview modal has checkboxes on work logs and adjustments (all checked by default). `process_payment()` accepts optional `selected_log_ids` / `selected_adj_ids` POST params to pay only selected items. Falls back to "pay all" if no IDs provided (backward compatible with the quick Pay button).
|
||||
- Team Pay Schedules: Teams have optional `pay_frequency` + `pay_start_date` fields. `get_pay_period(team)` calculates current period boundaries by stepping forward from the anchor date. The preview modal shows a "Split at Pay Date" button that auto-unchecks items outside the current pay period. `get_worker_active_team(worker)` returns the worker's first active team.
|
||||
- Team Pay Schedules: Teams have optional `pay_frequency` + `pay_start_date` fields. `get_pay_period(team)` calculates current period boundaries by stepping forward from the anchor date. The preview modal shows a "Split at Pay Date" button that auto-unchecks items after the `cutoff_date` (end of last completed period — includes ALL overdue work, not just one period). `get_worker_active_team(worker)` returns the worker's first active team.
|
||||
- Pay period calculation: `pay_start_date` is an anchor (never needs updating). Weekly=7 days, Fortnightly=14 days, Monthly=calendar month stepping. Uses `calendar.monthrange()` for month-length edge cases (no `dateutil` dependency).
|
||||
- Batch Pay: "Batch Pay" button on payroll dashboard opens a modal with two radio modes — **"Until Last Paydate"** (default, splits at last completed pay period per team schedule) and **"Pay All"** (includes all unpaid items regardless of date). Preview fetches from `batch_pay_preview` with `?mode=schedule|all`. Workers without team pay schedules are skipped in schedule mode but included in Pay All mode. `batch_pay` POST endpoint processes each worker in independent atomic transactions; emails are sent after all payments complete. Uses `_process_single_payment()` shared helper (same logic as individual `process_payment`).
|
||||
- Batch Pay: "Batch Pay" button on payroll dashboard opens a modal with two radio modes — **"Until Last Paydate"** (default, splits at last completed pay period per team schedule) and **"Pay All"** (includes all unpaid items regardless of date). Preview fetches from `batch_pay_preview` with `?mode=schedule|all`. Workers without team pay schedules are skipped in schedule mode but included in Pay All mode. `batch_pay` POST endpoint processes each worker in independent atomic transactions; emails are sent after all payments complete. Uses `_process_single_payment()` shared helper (same logic as individual `process_payment`). Modal includes team filter dropdown and 3-option loan filter (All / With loans only / Without loans).
|
||||
- Pending Payments Table: Shows overdue badges (red) for workers with unpaid work from completed pay periods, and loan badges (yellow) for workers with active loans/advances. Filter bar has: team dropdown, "Overdue only" checkbox, and loan dropdown (All Workers / With loans only / Without loans). Overdue detection uses `get_pay_period()` cutoff logic.
|
||||
|
||||
## URL Routes
|
||||
| Path | View | Purpose |
|
||||
|
||||
@ -2254,12 +2254,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Re-fetch preview when radio changes
|
||||
radioSchedule.addEventListener('change', function() { loadBatchPreview('schedule'); });
|
||||
radioAll.addEventListener('change', function() { loadBatchPreview('all'); });
|
||||
} else {
|
||||
// Update checked state using the saved reference (not getElementById — element is detached)
|
||||
_batchRadioGroup.querySelector('#batchModeSchedule').checked = (mode === 'schedule');
|
||||
_batchRadioGroup.querySelector('#batchModeAll').checked = (mode === 'all');
|
||||
}
|
||||
// Re-append radio group to DOM first, THEN set checked state.
|
||||
// Radio buttons only properly toggle siblings when attached to the DOM.
|
||||
body.appendChild(_batchRadioGroup);
|
||||
_batchRadioGroup.querySelector('#batchModeSchedule').checked = (mode === 'schedule');
|
||||
_batchRadioGroup.querySelector('#batchModeAll').checked = (mode === 'all');
|
||||
|
||||
// Show loading spinner below radio buttons
|
||||
var loadDiv = document.createElement('div');
|
||||
@ -2339,22 +2339,29 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
filterRow.appendChild(filterSelect);
|
||||
|
||||
// Only show the "Exclude loans" checkbox if any worker has a loan
|
||||
// Loan filter dropdown (All / With loans only / Without loans)
|
||||
var batchLoanFilter = null;
|
||||
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);
|
||||
loanDiv.className = 'd-flex align-items-center gap-2 ms-3';
|
||||
var loanLabel = document.createElement('label');
|
||||
loanLabel.className = 'form-check-label text-muted small';
|
||||
loanLabel.setAttribute('for', 'batchExcludeLoans');
|
||||
loanLabel.textContent = 'Exclude workers with loans';
|
||||
loanLabel.className = 'text-muted small mb-0';
|
||||
loanLabel.textContent = 'Loans:';
|
||||
loanLabel.setAttribute('for', 'batchLoanFilter');
|
||||
loanDiv.appendChild(loanLabel);
|
||||
batchLoanFilter = document.createElement('select');
|
||||
batchLoanFilter.id = 'batchLoanFilter';
|
||||
batchLoanFilter.className = 'form-select form-select-sm';
|
||||
batchLoanFilter.style.width = 'auto';
|
||||
var opts = [['', 'All Workers'], ['with', 'With loans only'], ['without', 'Without loans']];
|
||||
opts.forEach(function(o) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = o[0];
|
||||
opt.textContent = o[1];
|
||||
batchLoanFilter.appendChild(opt);
|
||||
});
|
||||
loanDiv.appendChild(batchLoanFilter);
|
||||
filterRow.appendChild(loanDiv);
|
||||
}
|
||||
|
||||
@ -2447,12 +2454,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// --- Shared filter function (team + loan filters combined) ---
|
||||
function applyBatchFilters() {
|
||||
var selectedTeam = filterSelect.value;
|
||||
var excludeLoans = excludeLoansCheck ? excludeLoansCheck.checked : false;
|
||||
var loanMode = batchLoanFilter ? batchLoanFilter.value : '';
|
||||
var rows = tbody.querySelectorAll('tr');
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
var row = rows[i];
|
||||
var teamMatch = !selectedTeam || row.dataset.team === selectedTeam;
|
||||
var loanMatch = !excludeLoans || row.dataset.hasLoan !== 'true';
|
||||
var loanMatch = !loanMode
|
||||
|| (loanMode === 'with' && row.dataset.hasLoan === 'true')
|
||||
|| (loanMode === 'without' && row.dataset.hasLoan !== 'true');
|
||||
if (teamMatch && loanMatch) {
|
||||
row.style.display = '';
|
||||
row.querySelector('.batch-worker-cb').checked = true;
|
||||
@ -2466,8 +2475,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
|
||||
filterSelect.addEventListener('change', applyBatchFilters);
|
||||
if (excludeLoansCheck) {
|
||||
excludeLoansCheck.addEventListener('change', applyBatchFilters);
|
||||
if (batchLoanFilter) {
|
||||
batchLoanFilter.addEventListener('change', applyBatchFilters);
|
||||
}
|
||||
|
||||
// --- Skipped workers (collapsible) ---
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user