Problem: Supervisors on slow mobile connections sometimes double-click
the "Pay" button, causing two PayrollRecords + two payslip emails to
be sent to Spark Receipt for the same worker.
Backend fix (the critical part):
- Moved unpaid_logs and pending_adjs queries INSIDE transaction.atomic()
- Added select_for_update() on Worker row — this database-level lock
forces the second concurrent request to WAIT until the first commits
- After the lock is acquired, the second request re-queries and finds
no unpaid logs (already paid by first request), so it bails out
Frontend fix (defence-in-depth):
- Pay button now shows a Bootstrap spinner + "Processing..." text
- Second click is blocked with e.preventDefault() if button is
already disabled (handles edge case where form resubmits)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>