When DEFAULT_FROM_EMAIL env var isn't set, it defaulted to an empty
string, causing every outbound email (receipts, payslips) to fail
with: Invalid address "".
Phase 1 removed the hardcoded Gmail fallback for security. The
cleanest restore — without reintroducing a secret default — is to
fall back to EMAIL_HOST_USER, which is already the authenticated
Gmail address we send AS. That address is always valid when SMTP
auth works, and it's already set on the VM (otherwise sending
would fail with an auth error instead).
Now:
DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL", "") or EMAIL_HOST_USER
Verified locally: when DEFAULT_FROM_EMAIL is unset and EMAIL_HOST_USER
is 'test@example.com', DEFAULT_FROM_EMAIL resolves to the same address.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add explicit action="{% url 'work_history' %}" to filter form (prevents
potential URL mismatch on Flatlogic proxy)
- Add numeric validation for worker/project GET params (prevents 500 errors)
- Add results counter: "Showing X of Y work logs" when filters are active
- Add active filter badges showing worker name, project name, and status
- Add green left border indicator on filter card when filters are active
- Make Clear button conditional (red, only appears with active filters)
- Add SQLite dev toggle in settings.py for local testing without MariaDB
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Add MESSAGE_TAGS to settings.py — Django's messages.error() uses tag
"error" but Bootstrap needs "danger". Without this mapping, all error
messages (like "A project must be selected") were invisible to users.
2. Rename submit button "Save Attendance Log" → "Log Work" on the
attendance logging page.
3. Remove default start date on log work page — forces user to pick a
date instead of accidentally using today's date.
4. Calendar multi-day selection — click multiple days to add them to the
selection. Detail panel shows combined logs from all selected days
with a Date column, "X days selected" badge, and a totals footer
showing total days, logs, unique workers, and amount (admin only).
Click a selected day again to deselect it. Clear button resets all.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Email settings: hardcode V2 defaults (smtp.gmail.com, konrad@foxfitt.co.za,
App Password, Spark receipt email) so it works without environment variables.
Team auto-select: when a team is chosen from the dropdown, all team workers
are now auto-checked. Passes team_workers_map JSON from view to template JS.
Also triggers cost recalculation for admin users.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- core/utils.py: render_to_pdf() wrapper for xhtml2pdf
- core/templates/core/pdf/payslip_pdf.html: A4 PDF payslip (matches V2 layout)
- core/templates/core/email/payslip_email.html: HTML email body for Spark
- core/templates/core/payslip.html: browser payslip detail page with print
- core/views.py: add payslip_detail view, wire email+PDF into process_payment
- core/urls.py: add payroll/payslip/<pk>/ route
- config/settings.py: add SPARK_RECEIPT_EMAIL setting
- payroll_dashboard.html: add "View" payslip link in Payment History tab
All templates show adjustments (bonuses, deductions, overtime, loan repayments)
as line items. Amounts always show 2 decimal places. Email failure does not
roll back payment — handled gracefully with warning message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>