docs: TDD plan for SiteReport removal (3 tasks, HARD STOP)

Task 1 revert attendance flow (TDD via 2 rewritten cross-ref tests),
Task 2 delete the SiteReport surface + 5 test classes + autogenerate
0018_delete_sitereport (suite 209->193), Task 3 docs. HARD STOP +
grep-clean/makemigrations-check gate before any push. Local-only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Konrad du Plessis 2026-05-17 01:27:40 +02:00
parent 777c7c6dcc
commit a502bac8ec

View File

@ -0,0 +1,375 @@
# Remove "Log Today's Work" / SiteReport — Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task (in-session, fresh subagent + 2-stage review per task). Each subagent uses superpowers:test-driven-development where a behavioural test exists.
**Goal:** Completely remove the SiteReport / "Log Today's Work" feature — model, table, code, UI, routes, tests, docs — with zero live remnants and nothing broken; revert the post-attendance flow to redirect→home; drop `core_sitereport` via a new migration.
**Architecture:** Three tasks: (1) revert the attendance redirect — genuine TDD via the two cross-ref flow tests; (2) delete the SiteReport surface + its 5 test classes + autogenerate `0018_delete_sitereport`; (3) docs. Then a HARD-STOP verification gate.
**Tech Stack:** Django 5.2.7, Python 3.13, SQLite local; the deletion migration is autogenerated (`makemigrations`), never hand-written.
**Design doc:** `docs/plans/2026-05-17-site-report-removal-design.md` (commit `777c7c6`). **Capture doc:** `docs/plans/2026-05-17-site-report-removed-capture.md` (knowledge preserved for the future rebuild).
**Branch / baseline:** `ai-dev`, local HEAD `777c7c6`, origin `aaca0b3` (production caught up & verified). **209/209 tests passing.**
**Test command (Git Bash, per CLAUDE.md):**
```bash
USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests -v 2
```
**Exact test-count math (pin this):** baseline **209**. Task 1 *rewrites* 2 tests (count unchanged → still 209). Task 2 *deletes* **16** SiteReport-only tests (`SiteReportModelTests` 4 + `SiteReportFormTests` 4 + `SiteReportEditViewTests` 5 + `SiteReportDetailViewTests` 2 + `AttendanceLogRedirectsToSiteReportTests` 1). **Final suite = 209 16 = 193 OK.** No new tests added (YAGNI — design §3; the 404 check is on Konrad's manual checklist, not automated).
> ⛔ **HARD STOP after Task 3 + the verification gate.** Do NOT `git push`, do NOT deploy. Konrad runs the local verification checklist (design doc) — including `makemigrations --check`, the route-404 checks, and the grep-clean — then pushes himself. Deploy (his call): pull → `migrate` (applies `0018`, drops `core_sitereport`) → restart. **No collectstatic** (no `static/` change).
> **Destructive:** `0018_delete_sitereport` drops `core_sitereport` and all its rows. **No backup** — Konrad's explicit, recorded choice (design "Decision"). Do not add a backup step.
---
### Task 1: Revert the post-attendance flow (TDD via the 2 cross-ref tests)
**Files:**
- Test: `core/tests.py``AbsenceAttendanceShortcutTests` (class at line 2835): `test_default_attendance_submit_unchanged` (2858) + `test_log_only_explicit_value_still_goes_to_site_report` (2873)
- Modify: `core/views.py``attendance_log` POST tail (lines ~694-785)
**Step 1: Rewrite the two failing tests (expect the NEW contract)**
In `core/tests.py`, replace `test_default_attendance_submit_unchanged` (currently lines ~2858-2871) with:
```python
def test_default_attendance_submit_redirects_home(self):
"""A plain attendance submit (no next_action) now redirects to
the dashboard — the old forced Site Report redirect was removed
(SiteReport feature deleted 17 May 2026)."""
resp = self.client.post(reverse('attendance_log'), data={
'date': '2026-05-14',
'project': self.project.id,
'team': self.team.id,
'workers': [self.worker.id],
'overtime_amount': '0.00',
'notes': '',
# next_action omitted on purpose — default path → home.
})
self.assertEqual(resp.status_code, 302)
self.assertEqual(resp.url, reverse('home'))
```
Replace `test_log_only_explicit_value_still_goes_to_site_report`
(currently lines ~2873-2886) with:
```python
def test_log_only_explicit_value_redirects_home(self):
"""An explicit next_action='log_only' also lands on the
dashboard (same default path; no Site Report anymore)."""
resp = self.client.post(reverse('attendance_log'), data={
'date': '2026-05-14',
'project': self.project.id,
'team': self.team.id,
'workers': [self.worker.id],
'overtime_amount': '0.00',
'notes': '',
'next_action': 'log_only',
})
self.assertEqual(resp.status_code, 302)
self.assertEqual(resp.url, reverse('home'))
```
Leave `test_log_absences_button_redirects_to_absence_log` (line ~2888)
**untouched**.
**Step 2: Run them — verify they FAIL**
```bash
USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests.AbsenceAttendanceShortcutTests -v 2
```
Expected: the two rewritten tests FAIL (`resp.url` is `/site-report/<id>/edit/`, not `/`); `test_log_absences_button_redirects_to_absence_log` still PASSES.
**Step 3: Revert the view**
In `core/views.py::attendance_log`, the POST tail currently (≈688-785)
tracks `created_log_ids` and redirects to `site_report_edit`. Make
exactly these edits:
(a) **Delete the `created_log_ids` comment + init** (currently ~696-698):
```python
# Track the IDs of work logs we just created so we can redirect
# the supervisor to the site-report form for the most recent one.
created_log_ids = []
```
(Keep the `created_count = 0` and `skipped_count = 0` lines above it.)
(b) **Delete the append** (currently ~739): the line
`created_log_ids.append(work_log.id)` (keep `created_count += 1`).
(c) **Trim the Round-C comment** (currently ~750-758) — remove the
SiteReport wording. Replace the comment block with:
```python
# === Post-submit destination ===
# The attendance form has two submit buttons (both POST
# `next_action`):
# - default / 'log_only' → dashboard (home) + success toast
# - 'log_absences' → /absences/log/ pre-filled
next_action = request.POST.get('next_action', 'log_only')
```
(d) **Fix the log_absences guard** (currently ~760):
`if next_action == 'log_absences' and created_log_ids:`
`if next_action == 'log_absences' and created_count:`
(the rest of that branch — the `urlencode` prefill + `return
redirect(...)` — is UNCHANGED.)
(e) **Replace the two-step block** (currently ~775-785, from the
`# Two-step flow:` comment through `return redirect('home')`) with:
```python
# SiteReport feature removed (17 May 2026) — a successful
# attendance submit now simply returns to the dashboard;
# the success toast above already confirms what was logged.
return redirect('home')
```
**Step 4: Run the tests — verify they PASS**
```bash
USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests.AbsenceAttendanceShortcutTests -v 2
```
Expected: all 3 PASS. Then the full suite (still **209** — nothing
deleted yet, 2 rewritten):
```bash
USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests -v 2
```
Expected: **209 OK** (the SiteReport classes still exist + pass at this
point; the redirect tests pass; nothing else regressed). If any
SiteReport view/model test fails here, STOP — only the attendance
redirect should have changed; investigate before continuing.
**Step 5: Commit**
```bash
git add core/views.py core/tests.py
git commit -m "refactor: revert post-attendance flow to redirect-home (SiteReport removal step 1)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
```
---
### Task 2: Delete the SiteReport surface + its tests + autogenerate the drop migration
**Files (delete entire files):**
- `core/site_report_schema.py`
- `core/templates/core/site_report_edit.html`
- `core/templates/core/site_report_detail.html`
**Files (edit):**
- `core/models.py` — delete `class SiteReport` (lines **182-272**
inclusive: `class SiteReport(models.Model):` through
`return f"Site Report — {self.work_log}"`). Ensure exactly two blank
lines remain between the preceding class's end and `class Loan` (line
was 274).
- `core/forms.py`
- line 17 ` SiteReport, Absence,`` Absence,` (KEEP Absence).
- delete line 19 `from .site_report_schema import COUNT_METRICS, CHECK_METRICS`.
- delete the `SiteReportForm` class **and its preceding `# === … ===`
doc-header comment block**, ending immediately before line 615's
`# ====…` / `# === ABSENCE FORMS ===` banner (read `forms.py`
~470-617 to get the exact start of the SiteReport doc-header; the
ABSENCE FORMS banner at 615-617 and everything after it stays).
- `core/views.py`
- delete the model import `SiteReport,` (line ~31, inside the
`from .models import (…)` block — keep the other names),
- delete the form import `SiteReportForm,` (line ~38, inside
`from .forms import (…)`),
- delete line ~41 `from .site_report_schema import COUNT_METRICS, CHECK_METRICS, label_for`,
- delete the three functions `_can_access_site_report`,
`site_report_edit`, `site_report_detail` (contiguous, ~819-963 —
read 815-966 for exact bounds: from the blank/comment line above
`def _can_access_site_report` through the last line of
`site_report_detail`, stopping before `def work_history`),
- in `work_history` (~965+), remove the `'site_report'` from the
`WorkLog.objects.select_related('site_report')` calls (it is the
only select_related arg on those lines — drop the entire
`.select_related('site_report')` so it reads `WorkLog.objects` /
`WorkLog.objects.filter(…)`; keep any `prefetch_related`/other
chaining intact) and delete the now-stale explanatory comment
(~969-970) that mentions the indicator.
- `core/urls.py` — delete the two `path('site-report/…')` lines (32-33).
- `core/admin.py` — delete line 7 ` SiteReport,` (keep line 8
` Absence,`); delete the `@admin.register(SiteReport)` decorator +
the entire `SiteReportAdmin` class (starts line 154 — read
`admin.py` 153-190 to find its end: next `@admin.register` or EOF).
- `core/templates/core/work_history.html` — delete the site-report
indicator block: the `{# Site-report indicator… #}` comment (line
458) through the `{% endif %}` that closes the
`{% if log.site_report %}` (read 455-485 for the exact `{% endif %}`
line — it follows the `{% else %}` "Add site report" anchor). Remove
the whole comment+if/else/endif; leave the surrounding cell markup
valid.
- `core/tests.py` — delete the contiguous SiteReport test block: from
line **1622** (`# ===…` opening the `# === SITE REPORT TESTS ===`
banner) through line **1925** (last line of
`AttendanceLogRedirectsToSiteReportTests`,
`self.assertEqual(WorkLog.objects.count(), 1)`), INCLUDING the
`from core.models import SiteReport` / `from core.forms import
SiteReportForm` imports (1629-1630). Ensure exactly two blank lines
precede line 1927's `# === Worker Absence — Phase 1 ===` banner.
**Step 1: Delete files + apply all edits above.**
**Step 2: Generate the deletion migration (do NOT hand-write)**
```bash
USE_SQLITE=true python manage.py makemigrations core
```
Expected: creates `core/migrations/0018_delete_sitereport.py` containing
`migrations.DeleteModel(name='SiteReport')` (Django also emits the
correct `RemoveField` ordering for the `work_log`/`created_by`
relations) with `dependencies = [('core', '0017_alter_payrolladjustment_type')]`.
Open the file and sanity-check it is ONLY a SiteReport deletion (no
unrelated model changes). Then:
```bash
USE_SQLITE=true python manage.py makemigrations --check --dry-run
```
Expected: "No changes detected" (model state + migrations consistent).
**Step 3: System check + full suite**
```bash
USE_SQLITE=true python manage.py check
USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests -v 2
```
Expected: `check` clean; suite **193 OK** (209 16 deleted). If the
count isn't exactly 193, or any non-SiteReport test fails, STOP and
report (do not patch unrelated tests).
**Step 4: Grep-clean check (no live remnants)**
```bash
grep -rniE "sitereport|site_report|site-report" core/ --include="*.py" --include="*.html" | grep -v "/migrations/0013_add_site_report.py" | grep -v "/migrations/0014_add_absence.py" | grep -v "/migrations/0018_delete_sitereport.py"
```
Expected: **no output**. (The three migration files legitimately
contain the name — `0013` created it, `0014` depends on it for
ordering, `0018` deletes it; those are immutable history, not
remnants.) Any other match = a missed reference; fix before commit.
**Step 5: Commit**
```bash
git add -A core/
git commit -m "feat!: remove SiteReport / Log Today's Work feature (model, code, UI, tests)
Drops core_sitereport via 0018_delete_sitereport. Knowledge preserved
in docs/plans/2026-05-17-site-report-removed-capture.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
```
(`git add -A core/` so the deleted files are staged as deletions. Do
NOT `git add` outside `core/`.)
---
### Task 3: Docs (CLAUDE.md + parked-work.md)
**Files:**
- Modify: `CLAUDE.md`
- Modify: `docs/plans/parked-work.md`
**Step 1: Full-suite anchor**
```bash
USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests -v 2
```
Expected: **193 OK**. If not, STOP.
**Step 2: CLAUDE.md**
- Delete the entire **"## SiteReport metric schema (Apr 2026)"**
section (heading through the line before the next `##`).
- Delete the **Key Models** bullet that starts **`- **SiteReport**`**.
- Delete the two URL-routes table rows for
`/site-report/<work_log_id>/edit/` and `/site-report/<work_log_id>/`.
- Remove SiteReport/two-step-flow mentions elsewhere (e.g. the
"two-step flow" / "auto-redirected here after /attendance/log/ POST"
wording in the SiteReport-schema area and any Key-Business-Rules or
Absence-section cross-mention of the site-report redirect — grep
`grep -n "SiteReport\|site.report\|site_report\|Log Today" CLAUDE.md`
and clear every live reference).
- Add a one-line pointer where the SiteReport schema section was, e.g.:
> **SiteReport / "Log Today's Work" — REMOVED 17 May 2026.** Feature
> deleted (model/table/UI/routes) to be rebuilt from scratch later.
> See `docs/plans/2026-05-17-site-report-removed-capture.md`.
**Step 3: parked-work.md**
- Replace the **"### Post-Attendance Flow v2"** paused entry (under
"## ⏸ Paused — ready to execute") with a short entry, e.g.:
> ### Site-progress logging — rebuild from scratch (parked)
> The SiteReport / "Log Today's Work" feature was **removed**
> 17 May 2026 (Konrad's call — work mix shifting, scope uncertain).
> Post-attendance now just returns to the dashboard. Future rebuild
> starts fresh: see
> `docs/plans/2026-05-17-site-report-removed-capture.md` (preserves
> the schema-as-Python pattern, recovery pointers, and the
> now-superseded `2026-05-15-post-attendance-flow-v2-*` prior
> thinking — those files stay on disk).
- Leave the `2026-05-15-post-attendance-flow-v2-design.md` /
`-plan.md` files in place (do NOT delete — historical thinking).
- Scan the rest of parked-work.md for any other live "Site Report" /
two-step-flow reference and reconcile to "removed; see capture doc".
**Step 4: Verify docs**
```bash
grep -niE "sitereport|site.report|site_report|log today's work" CLAUDE.md docs/plans/parked-work.md
```
Expected: only the new "REMOVED — see capture doc" pointers (and the
parked-work rebuild entry) match — no stale "how SiteReport works" /
"Post-Attendance Flow v2 ready to execute" content remains.
**Step 5: Commit**
```bash
git add CLAUDE.md docs/plans/parked-work.md
git commit -m "docs: scrub SiteReport from CLAUDE.md + park rebuild (capture doc)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
```
---
## ⛔ HARD STOP — verification gate, then hand back to Konrad
After Task 3's commit, run the full verification and STOP:
1. `git status` clean; `git log --oneline -4` shows the 3 removal
commits on top of `777c7c6`.
2. `USE_SQLITE=true python manage.py makemigrations --check --dry-run`
→ "No changes detected".
3. `USE_SQLITE=true python manage.py check` → no issues.
4. `USE_SQLITE=true python manage.py migrate` on a fresh local SQLite
`0018_delete_sitereport` applies with no error; then
`USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests -v 2`
**193 OK**.
5. Grep-clean (Task 2 Step 4 command) → no output beyond the 3
migration files.
6. **Do NOT push. Do NOT deploy.** Report the commit list + 193 count
and point Konrad at the **"Local verification (Konrad — HARD STOP
gate)"** section of
`docs/plans/2026-05-17-site-report-removal-design.md` (7 manual
steps incl. `/attendance/log/` → dashboard, `/site-report/1/edit/`
→ 404, admin has no SiteReport).
7. On Konrad's explicit approval: he pushes; deploy = pull →
`migrate` (drops `core_sitereport`) → restart **last**. **No
collectstatic** (no `static/` change).
## Notes
- **DRY/YAGNI:** no replacement built (future separate rethink —
captured). No new tests beyond the 2 rewritten flow tests.
- **Migration discipline:** `0018` is autogenerated, never hand-edited;
`0013`/`0014` are immutable history and stay.
- **Don't touch:** the `next_action == 'log_absences'` branch, the
Manager/Salaried bundle, pay-type filter, Salary auto-scope, Pay
Salary quick action — all out of scope.
- The production-caught-up breadcrumb was already pushed separately
(`aaca0b3`); the design + capture docs are local (`777c7c6`). This
plan's commits + those docs all go in the SAME push when Konrad
approves — one bundle.