docs: refresh CLAUDE.md + parked-work for 15 May session wins

CLAUDE.md changes:
- 'What's mid-flight' breadcrumb updated: SiteReport/Absences
  migrations are LIVE on prod; only the 4 latest UX commits await
  a pull-and-restart (no migration / collectstatic needed).
- URL Routes table entries for /workers/ and /history/ now document
  the new ?team= filter (and team=none for unassigned / no-team cases).
- Worker Management UI inline description mentions the team filter
  + Absences tab on the worker detail page.
- Two new Coding Style gotchas captured: (1) Bootstrap dropdowns
  inside .card elements get clipped by sibling cards — fix is to
  lift the wrapping card with position:relative + z-index;
  (2) JS reading from data-worker-id was unreliable on production —
  read input[name="workers"][value] directly (the attendance-form
  pattern that's been working for years).

parked-work.md changes:
- Pending-deploy section rewritten: the BIG deploy (migrations +
  collectstatic) is DONE; only 4 small commits await a pull +
  service restart.
- 'Recently shipped' grew two new entries: the team filters on
  /workers/ + /history/, and the two absences UX polish fixes.
- Updated timestamp to 15 May 2026.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Konrad du Plessis 2026-05-15 00:50:47 +02:00
parent 398a5b21ab
commit bde6f24bb1
2 changed files with 64 additions and 67 deletions

View File

@ -3,17 +3,20 @@
## What's mid-flight — read this first
**Parked / deferred work:** see `docs/plans/parked-work.md`.
**⚠ Operator action pending:** SiteReport (`0013`) + Absences (`0014`
+ `0015`) migrations need to run on production. Visit
`https://foxlog.flatlogic.app/run-migrate/` once + ask Gemini to
run `collectstatic` + restart `django-dev.service`. The /history/
page is currently 500ing on production until this is done — see
parked-work.md "Production deploy" section for the full sequence.
**Production status (15 May 2026):** migrations `0013_add_site_report`,
`0014_add_absence`, `0015_absence_project` are deployed; `/history/`
is no longer crashing on the production VM. The Worker Absences
feature shipped on 14 May 2026 (commits `bf6f0a5``27fe05e` on
`ai-dev`). Subsequent UX polish (multi-checkbox-dropdown stacking
fix, absence-form team-filter bug fix, team filter added to
`/workers/` and `/history/`) is on `ai-dev` HEAD but not yet on
production — needs a `git pull` + `sudo systemctl restart
django-dev.service` whenever convenient (no migrations or
collectstatic required for those commits).
Phase A.2 (manual JournalEntry UI) and Phase B (Letterly inbound
webhook) from the Site Work Logging design are parked pending Q5 / Q7
answers. The Worker Absences feature shipped on 14 May 2026 (commits
`bf6f0a5``27fe05e` on `ai-dev`).
answers — see `docs/plans/parked-work.md`.
## Coding Style
- Always add clear section header comments using the format: # === SECTION NAME ===
@ -22,6 +25,8 @@ answers. The Worker Absences feature shipped on 14 May 2026 (commits
- When creating or editing code, maintain the existing comment structure
- **Django template comments `{# ... #}` are SINGLE-LINE only.** Multi-line blocks need `{% comment %}...{% endcomment %}`. A `{#` on line N with no closing `#}` on the same line renders the whole block as literal text onto the page (and silently — no error). This bit us 4× during the Adjustments feature. Also: the literal tokens `{#` and `#}` cannot appear inside a `{% comment %}` block — they'll be parsed as a nested comment marker. Rephrase meta-notes about comment syntax OUTSIDE the block.
- **Duplicate `id=""` attributes cause silent bugs.** `document.getElementById()` returns only the FIRST match in DOM order, so adding a second element with an existing id silently steals the handler from the original. Grep the template before assigning any new id (caught `adjSelectAll` collision in Task 6 — header checkbox stole the Add-Adjustment modal's Select-All handler).
- **Bootstrap dropdowns inside `.card` elements get clipped by sibling cards.** A `.dropdown-menu` with `z-index: 1050` rendered inside a filter `.card` will STILL appear behind a sibling table `.card` that follows in document order. Bootstrap's `transform: translate(...)` Popper positioning creates a new stacking context — the z-index is measured INSIDE the parent card, not globally. The fix: lift the wrapping element (e.g. the filter `<form class="card">`) with `style="position: relative; z-index: 10;"` so the entire card sits above its siblings. The dropdown's local z-index then resolves correctly. Bit us on the Absences filter dropdown (May 2026).
- **JS reading from `data-worker-id` was unreliable; read from `<input name="workers">[value]` directly.** Round A's first absence-form team filter rendered `data-worker-id="{{ worker.choice_value }}"` on the row `<div>` 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 `<input value="<pk>">` 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/<id>/` (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/<id>/edit/` (single-page form for name, supervisor, pay schedule, and workers M2M). **Project pages**: `/projects/`, `/projects/<id>/` (tabs: Profile/Supervisors/Teams/Workers/History), `/projects/<id>/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/<id>/` (detail with Profile/Certifications/Warnings/History tabs), `/workers/<id>/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 `<template>` 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/<id>/` (detail with Profile/Certifications/Warnings/**Absences**/History tabs — Absences tab shows YTD totals chip row + 50 most-recent absence rows), `/workers/<id>/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 `<template>` 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/<work_log_id>/edit/` | `site_report_edit` | Create-or-update the optional SiteReport for a WorkLog (auto-redirected here after `/attendance/log/` POST) |
| `/site-report/<work_log_id>/` | `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/<id>/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/<id>/` | `worker_detail` | Admin: worker profile with profile/certs/warnings/history tabs |
| `/workers/<id>/edit/` | `worker_edit` | Admin: edit worker + inline cert/warning formsets |

View File

@ -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 <previous SHA>`
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=<id>` filters by Team.workers
M2M membership (who's CURRENTLY on the team); `/history/?team=<id>`
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 `<input name="workers">[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