` and read it via `row.dataset.workerId`. On production this hid ALL workers when a team was selected — likely a stale-template / template-render mismatch. The proven pattern (used by `attendance_log.html` for years) is to read `row.querySelector('input[name="workers"]').value`. The form widget's `
` is the source of truth; data attributes are an unnecessary indirection.
## Project Overview
Django payroll management system for FoxFitt Construction, a civil works contractor specializing in solar farm foundation installations. Manages field worker attendance, payroll processing, employee loans, and business expenses for solar farm projects.
@@ -335,7 +340,7 @@ numbers on hot pages.
- Quick Adjust Button: Each pending payments row has an "Adjust" button (slider icon) that opens the Add Adjustment modal with that worker pre-checked and their most recent project pre-selected. The header "Add Adjustment" button resets the modal to a clean state. Uses `_quickAdjustOpen` flag to distinguish between the two open paths.
- Worker Lookup Modal: Clicking any worker name on the payroll dashboard (or using the "Worker Lookup" button) opens a modal with a comprehensive report card — amount payable, outstanding loans, paid this month/year, loans this year, recent activity (last payslip, loan, repayment, advance), active loans table, current project + days on project, PPE sizing, drivers license, and notes. Uses `worker_lookup_ajax` AJAX endpoint. Worker dropdown in modal allows switching workers without closing.
- Team & Project Management UIs: Friendlier alternatives to `/admin/core/team/` and `/admin/core/project/`. Reachable via the "Resources" dropdown in the topbar (admin only). **Team pages**: `/teams/` (list + search/filter), `/teams/
/` (detail with Profile/Pay Schedule/Workers/History tabs — Pay Schedule tab uses the existing `get_pay_period()` helper to show current + next 2 periods), `/teams//edit/` (single-page form for name, supervisor, pay schedule, and workers M2M). **Project pages**: `/projects/`, `/projects//` (tabs: Profile/Supervisors/Teams/Workers/History), `/projects//edit/` (form for name, description, dates, supervisors M2M). Uses `TeamForm` and `ProjectForm` from `core/forms.py` (both simple ModelForms, no inline formsets). Batch reports at `/teams/report/` and `/projects/report/` with CSV exports; PDF exports deferred as a follow-up. Dashboard "Manage Resources" card now has "Manage All Workers/Projects/Teams" footer links on each tab. Django admin remains fully functional as a fallback.
-- Worker Management UI: A friendlier alternative to `/admin/core/worker/`. Reachable via the "Resources" topbar dropdown → Workers (admin-only). Pages: `/workers/` (list with search + status filter), `/workers//` (detail with Profile/Certifications/Warnings/History tabs), `/workers//edit/` or `/workers/new/` (single-page form with sections for Personal & Pay, PPE, Documents, Driver's License, plus inline formsets for certifications and warnings). Uses `WorkerForm`, `WorkerCertificateFormSet`, `WorkerWarningFormSet` from `core/forms.py`. The "+ Add Certification" / "+ Add Warning" buttons clone a `` element via `content.cloneNode()` (DOM-safe, no innerHTML) and rewrite `__PREFIX__` in input names to the next formset index. File uploads validated at 5 MB max via `validate_max_5mb()` in `forms.py`. Django admin (`/admin/core/worker/`) remains fully functional as a fallback — both UIs coexist.
+- Worker Management UI: A friendlier alternative to `/admin/core/worker/`. Reachable via the "Resources" topbar dropdown → Workers (admin-only). Pages: `/workers/` (list with search + status + team filter — team filter uses Team.workers M2M membership, special value `none` matches workers not assigned to any team), `/workers//` (detail with Profile/Certifications/Warnings/**Absences**/History tabs — Absences tab shows YTD totals chip row + 50 most-recent absence rows), `/workers//edit/` or `/workers/new/` (single-page form with sections for Personal & Pay, PPE, Documents, Driver's License, plus inline formsets for certifications and warnings). Uses `WorkerForm`, `WorkerCertificateFormSet`, `WorkerWarningFormSet` from `core/forms.py`. The "+ Add Certification" / "+ Add Warning" buttons clone a `` element via `content.cloneNode()` (DOM-safe, no innerHTML) and rewrite `__PREFIX__` in input names to the next formset index. File uploads validated at 5 MB max via `validate_max_5mb()` in `forms.py`. Django admin (`/admin/core/worker/`) remains fully functional as a fallback — both UIs coexist.
- Worker Batch Report: `/workers/report/` shows every worker with aggregated lifetime history — days worked, projects worked on, teams, first/last payslip dates, total paid, cert status (active/total + expired/expiring counts), warning count. Filter by status, project, team. CSV export via `/workers/report/csv/`, PDF via `/workers/report/pdf/` (landscape A4, same amber-accent typography as the payroll report). Built on the reusable `_build_worker_report_context()` helper which uses `annotate(Min/Max/Count/Sum)` + prefetch for efficient aggregation.
- Dashboard cert-expiry card: The admin dashboard shows a "Certifications Need Attention" stat card with count of expired + expiring-within-30-days certs (active workers only). Card is CONDITIONAL — renders only when count > 0, so it disappears when everything is in good standing. Clicking it goes to the worker batch report. Counts come from `index()` view adding `certs_expired_count`, `certs_expiring_count`, `certs_alert_total` to context.
- **Inline Filters on the Report page**: `/report/` has three pill-buttons (Date / Projects / Teams) in a sticky strip. Clicking a pill opens an inline popover with the editor for that filter. Popover's OK rebuilds the URL and navigates — no "dirty state", no global Apply. Date popover has a Single/Range/Custom mode toggle; Projects + Teams use Choices.js multi-select. Bidirectional project↔team cross-filter disables workers/projects that never paired in the selected date range. Context key `project_team_pairs_json` is consumed via `|json_script` (raw Python list — NEVER `json.dumps` it first; the filter does the serialisation and double-encoding silently breaks `.forEach(...)`). Deleted the old `_report_config_modal.html`; Dashboard "Generate Report" button is now a plain link with the current month pre-filled.
@@ -348,7 +353,7 @@ numbers on hot pages.
|------|------|---------|
| `/` | `index` | Dashboard (admin stats / supervisor work view) |
| `/attendance/log/` | `attendance_log` | Log daily work with date range support |
-| `/history/` | `work_history` | Work logs table with filters |
+| `/history/` | `work_history` | Work logs table with filters. Query params: `?worker=`, `?project=`, `?team=` (digit = WorkLog.team FK match; `none` = logs with no team set), `?status=paid|unpaid`. CSV export at `/history/export/` honors the same filters. |
| `/history/export/` | `export_work_log_csv` | Download filtered logs as CSV |
| `/site-report//edit/` | `site_report_edit` | Create-or-update the optional SiteReport for a WorkLog (auto-redirected here after `/attendance/log/` POST) |
| `/site-report//` | `site_report_detail` | Read-only view of the SiteReport (404 if none — use the edit URL to create) |
@@ -359,7 +364,7 @@ numbers on hot pages.
| `/absences//delete/` | `absence_delete` | POST-only; cascades unpaid adjustment; refuses if paid. |
| `/absences/export/` | `absence_export_csv` | Admin-only CSV; honors all list filters. |
| `/workers/export/` | `export_workers_csv` | Admin: export all workers to CSV |
-| `/workers/` | `worker_list` | Admin: friendly worker list with search + status filter |
+| `/workers/` | `worker_list` | Admin: friendly worker list. Query params: `?q=` (name/ID/phone search), `?status=active\|inactive\|all`, `?team=` (digit = Team.workers M2M membership; `none` = workers not on any team). |
| `/workers/new/` | `worker_edit` | Admin: blank worker-create form |
| `/workers//` | `worker_detail` | Admin: worker profile with profile/certs/warnings/history tabs |
| `/workers//edit/` | `worker_edit` | Admin: edit worker + inline cert/warning formsets |
diff --git a/docs/plans/parked-work.md b/docs/plans/parked-work.md
index 7766b48..5575545 100644
--- a/docs/plans/parked-work.md
+++ b/docs/plans/parked-work.md
@@ -1,67 +1,38 @@
# Parked / deferred work
-> Updated 14 May 2026 (late evening). A small index of features that
-> are designed, half-built, blocked on input, or pending an operator
-> step. When a fresh session opens, glance here first to see what's
-> already on the workbench.
+> Updated 15 May 2026. A small index of features that are designed,
+> half-built, blocked on input, or pending an operator step. When a
+> fresh session opens, glance here first to see what's already on
+> the workbench.
---
-## ⚠ Needs operator action (production)
+## ⚠ Pending pull-and-restart on production
-### Production deploy of SiteReport + Absences (pending Konrad)
+**Status:** The big absences/SiteReport deploy (migrations
+`0013`/`0014`/`0015` + collectstatic + service restart) was
+completed on 14 May 2026 — `/history/` no longer crashes,
+`/absences/*` is reachable, badge CSS is collected. ✓
-**Status:** All code committed and pushed to `origin/ai-dev` (HEAD
-`27fe05e` as of 14 May 2026 late evening). Production at
-`https://foxlog.flatlogic.app/` has pulled the new code but is
-crashing on `/history/` with:
+Since that deploy, four small commits have shipped to `origin/ai-dev`
+that are NOT yet on production:
-```
-ProgrammingError: (1146, "Table 'app_38686.core_sitereport' doesn't exist")
-```
+| Commit | What it does |
+|---|---|
+| `4368e53` | Fixes the absence-form team filter (workers were all disappearing when a team was selected — read input.value instead of data-attr) |
+| `02c6d4d` | Fixes the Reasons multi-checkbox dropdown stacking-context bug on `/absences/` (lifted the filter card above the table card) |
+| `4b57cff` | Adds a Team filter to `/workers/` |
+| `398a5b2` | Adds a Team filter to `/history/` (and CSV export) |
-**Why:** Flatlogic auto-pulled the new code but didn't run migrations.
-The new template logic references `log.site_report`, which queries a
-table that doesn't exist yet on the production MySQL.
+**To deploy these:**
+- No migrations needed (pure template + view changes; no schema changes).
+- No `collectstatic` needed (no new CSS or JS files).
+- Just: ask Gemini in Flatlogic to `git pull origin ai-dev` and then `sudo systemctl restart django-dev.service`.
-**The fix (~2 minutes total):**
-
-1. **Backup first** (safety net) — visit
- `https://foxlog.flatlogic.app/backup-data/` while logged in as
- admin. Download the `.json` file to a safe location.
-2. **Run pending migrations** — visit
- `https://foxlog.flatlogic.app/run-migrate/`. Applies three
- migrations:
- - `0013_add_site_report` — creates `core_sitereport` table (fixes
- the immediate /history/ error)
- - `0014_add_absence` — creates `core_absence` table
- - `0015_absence_project` — adds `project` FK to absence
-3. **Refresh static files + restart service** — ask Gemini in
- Flatlogic to run:
- ```
- python3 manage.py collectstatic --noinput
- sudo systemctl restart django-dev.service
- ```
- Needed because `static/css/custom.css` has new
- `.badge-absence-*` rules — without `collectstatic`, the reason
- badges on the new pages will render with no color.
-4. **Smoke test (incognito)** — visit `foxlog.flatlogic.app/history/`
- (should load), `/absences/` (should load), `Resources →
- Absences` (admin dropdown should now have it). Log one test
- absence end-to-end.
-
-**Rollback if anything breaks:** visit
-`https://foxlog.flatlogic.app/restore-data/`, upload the backup
-from step 1, ask Gemini to restart the service.
-
-**Why this is parked, not urgent:** The /history/ error only fires
-when someone actively uses the History page. The dashboard,
-attendance log, and payroll pages all work fine. Konrad chose to
-defer the deploy step — that's the right call when you don't have
-time to babysit a deploy properly.
-
-**Reference:** the deployment hazard is documented in CLAUDE.md
-under "Migrations" in the Flatlogic/AppWizzy Deployment section.
+That's the whole sequence. Production should pick up all four
+improvements instantly. Rollback is `git reset --hard `
+on the VM if anything looks wrong (no data migrations were involved,
+so rollback is safe).
---
@@ -162,6 +133,25 @@ From Q9, Q4 of the Site Work Logging brainstorm:
## Recently shipped (for context, so a fresh session knows what just landed)
+- **Team filters on `/workers/` and `/history/`** (commits `4b57cff`,
+ `398a5b2`, 15 May 2026): Both pages now have a Team dropdown in
+ their filter row. `/workers/?team=` filters by Team.workers
+ M2M membership (who's CURRENTLY on the team); `/history/?team=`
+ filters by WorkLog.team FK (logs TAGGED with the team when
+ created — different semantics, intentional). Both accept
+ `team=none` for the "no team assigned" / "ad-hoc work log" case.
+ CSV export at `/history/export/` honours the same param.
+ +8 regression tests.
+- **Absences UX polish** (commits `4368e53`, `02c6d4d`, 15 May 2026):
+ Two production-found bugs fixed. (1) Absence-form team filter was
+ hiding ALL workers when a team was selected — switched the JS to
+ read ` [value]` directly instead of going
+ through `data-worker-id` (proven attendance-form pattern). (2)
+ Reasons multi-checkbox dropdown on `/absences/` was rendering
+ behind the table — lifted the filter card with
+ `position: relative; z-index: 10` so the whole card sits above
+ its sibling table card in the stacking order. See the Coding
+ Style section in CLAUDE.md for both gotchas.
- **Worker Absences feature** (commits `bf6f0a5` → `27fe05e`,
14 May 2026): Complete absence-tracking system. 8 reason choices,
optional project FK (auto-attributes paid-absence Bonus
@@ -172,9 +162,11 @@ From Q9, Q4 of the Site Work Logging brainstorm:
export. Worker-detail "Absences" tab with YTD totals. Dashboard
alert card "X absent in last 7 days". Bidirectional cascade with
PayrollAdjustment via `_sync_absence_payroll_adjustment` helper
- (single-chokepoint design, transaction.atomic-wrapped). All 12
+ (single-chokepoint design, transaction.atomic-wrapped). 12
commits pushed to `origin/ai-dev`. Test count: 85 → 149 (+64
- tests). All ai-dev tests green.
+ tests). Migrations `0013_add_site_report`, `0014_add_absence`,
+ `0015_absence_project` are LIVE on production as of late 14 May
+ 2026.
- **Phase A.1 — SiteReport** (commit `864ae72`, 14 May 2026):
Model, migration `0013_add_site_report.py`, form, two-step flow
from attendance log, 16 new tests. `CLAUDE.md` updated with model