38686-vm/docs/plans/2026-05-17-site-report-removal-plan.md
Konrad du Plessis a502bac8ec 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>
2026-05-17 01:27:40 +02:00

376 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.