Replaces the forced post-attendance SiteReport redirect with 3 explicit buttons (Log Work → dashboard / + Site Journal / + Absences) + a parallel "Save Site Journal + Add Absences" on the journal page. Renames the user-facing "Site Report" → "Site Journal" (display-only, Path-A; frees "Journal" for the parked voice feature). Reuses the Round C next_action POST mechanism — no model/migration/URL changes. NOT to be deployed until Konrad verifies locally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
204 lines
10 KiB
Markdown
204 lines
10 KiB
Markdown
# Post-Attendance Flow v2 — Design
|
|
|
|
**Date:** 15 May 2026
|
|
**Status:** Approved by Konrad on 15 May 2026; ready for implementation plan.
|
|
**Branch:** `ai-dev`. **NOT to be pushed/deployed until Konrad confirms it works locally.**
|
|
|
|
## Goal (one sentence)
|
|
|
|
Replace the forced auto-redirect into the Site Report form after every
|
|
attendance submit with three explicit, clearly-prioritised choices —
|
|
"just log work", "log work + site journal", "log work + absences" — and
|
|
add a parallel "save + add absences" path on the site-journal page,
|
|
while renaming the user-facing vocabulary to "Site Journal" to free up
|
|
"Journal" for the (parked) voice-transcript feature.
|
|
|
|
## Why
|
|
|
|
Phase A.1 shipped a two-step flow: attendance submit → forced redirect
|
|
to the SiteReport form (with a Skip link). In real use Konrad found the
|
|
forced redirect intrusive — most days he just wants to log attendance
|
|
and be done. The fix is to make the site-journal step (and the absences
|
|
step) explicit opt-in buttons rather than a mandatory interstitial.
|
|
|
|
## Decisions locked in (from the brainstorm)
|
|
|
|
| # | Question | Decision |
|
|
|---|----------|----------|
|
|
| 1 | "Journal" naming collision (parked voice JournalEntry vs the structured SiteReport form) | Rename the SiteReport-the-page to **"Site Journal"** in all UI text. Model/view/URL stay `SiteReport`/`site_report_*` in code (Path-A display-only rename, zero migration). The parked voice feature will be renamed (e.g. "Voice Notes") so it doesn't collide. |
|
|
| 2 | Where does plain "Log Work" land now? | **Dashboard** (home) + the existing green "work log(s) created" toast. No more forced SiteReport redirect. |
|
|
| 3 | Button structure | **Approach C** — exactly the 3 explicit buttons Konrad asked for, but with deliberate visual hierarchy (primary "Log Work" + two secondary "+ …" buttons) so 3 buttons don't feel like a wall. |
|
|
| 4 | End of the "+ Add Absences" chain | The absences form's own buttons (Log Absences → list, Cancel) are the natural end. No extra chaining. |
|
|
|
|
## § 1 — Vocabulary rename (display-only, zero migration)
|
|
|
|
User-facing text changes from "Site Report" / "Log Today's Work" →
|
|
**"Site Journal"**:
|
|
|
|
- `core/templates/core/site_report_edit.html` — `<h1>` "Log Today's
|
|
Work" → "Site Journal"; save button "Save Site Report" → "Save Site
|
|
Journal"; `{% block title %}` text.
|
|
- `core/templates/core/site_report_detail.html` — heading + `<title>`.
|
|
- `core/templates/core/work_history.html` — the clipboard-icon tooltip
|
|
/ link title text that references "site report".
|
|
- Success-toast string in `core/views.py::site_report_edit` — "Site
|
|
report saved …" → "Site journal saved …".
|
|
|
|
**Unchanged in code (Path-A pattern, per CLAUDE.md "UI-vs-DB naming
|
|
drift"):** `SiteReport` model, `site_report_edit` /
|
|
`site_report_detail` views, `/site-report/<id>/edit/` + `/site-report/
|
|
<id>/` URLs, the `work_log.site_report` related-name, `SiteReportForm`,
|
|
`core/site_report_schema.py`, all existing tests. Only user-visible
|
|
strings move.
|
|
|
|
**Parked-work doc note:** add a line under the Backburner section that
|
|
the future voice-transcript feature must NOT be called "Journal" (the
|
|
name is now taken by the site-progress form) — suggest "Voice Notes".
|
|
|
|
## § 2 — Attendance form: 3 buttons, clear hierarchy
|
|
|
|
`/attendance/log/` submit block (top → bottom, Konrad's specified order):
|
|
|
|
| Button label | Bootstrap style | `next_action` value | Redirect on success |
|
|
|---|---|---|---|
|
|
| **Log Work** | `btn btn-lg btn-accent` (primary, orange) | `log_only` | `redirect('home')` + toast |
|
|
| **Log Work + Site Journal** | `btn btn-lg btn-outline-secondary` | `log_journal` *(NEW)* | `redirect('site_report_edit', work_log_id=<last created>)` |
|
|
| **Log Work + Add Absences** | `btn btn-lg btn-outline-secondary` | `log_absences` | `/absences/log/?date=&team=&project=` (unchanged) |
|
|
|
|
- The primary orange "Log Work" anchors the eye — the common case.
|
|
- The two `+ …` buttons are visually secondary (outline) — opt-in.
|
|
- `log_only` changes its redirect target from SiteReport → home.
|
|
This is the behavioural reversal of Phase A.1's forced redirect.
|
|
- `log_journal` is new; it does exactly what `log_only` used to do
|
|
(go to the site-journal form for the last-created WorkLog).
|
|
- `log_absences` is untouched (Round C).
|
|
- The conflict-resolution re-render already passes `next_action`
|
|
through via the `form.data.items` loop (Round C, commit `8c749f3`).
|
|
`log_journal` rides those same rails for free — no extra work, but
|
|
needs a regression test.
|
|
|
|
## § 3 — Site Journal page: 3 actions, same hierarchy
|
|
|
|
`/site-report/<id>/edit/` action row:
|
|
|
|
| Action label | Style | Behaviour |
|
|
|---|---|---|
|
|
| **Skip** | quiet `btn btn-outline-secondary` (or text link) | → `home`, nothing saved (unchanged) |
|
|
| **Save Site Journal** | `btn btn-accent` (primary) | Save → `home` + toast (unchanged destination) |
|
|
| **Save Site Journal + Add Absences** | `btn btn-outline-secondary` | Save → `/absences/log/?date=&team=&project=` prefilled from the WorkLog |
|
|
|
|
- Mirror of the attendance form's hierarchy.
|
|
- The absence prefill query string is built from `work_log.date`,
|
|
`work_log.team_id`, `work_log.project_id` — the view already
|
|
`select_related('project', 'team', 'supervisor')`s the work_log, so
|
|
no new query. Identical query-string construction to the attendance
|
|
form's `log_absences` branch (DRY: consider a tiny shared helper
|
|
`_absence_prefill_qs(work_log)` but YAGNI — two call sites, ~3 lines
|
|
each; decide during implementation).
|
|
- New submit button carries `name="next_action" value="save_absences"`.
|
|
The view reads it after a successful `form.save()`.
|
|
|
|
## § 4 — View changes
|
|
|
|
### `core/views.py::attendance_log` (POST success branch)
|
|
|
|
Current Round C logic: `next_action == 'log_absences'` → absences
|
|
prefill; else → `redirect('site_report_edit', …)`.
|
|
|
|
New logic:
|
|
- `next_action == 'log_absences'` → absences prefill (unchanged)
|
|
- `next_action == 'log_journal'` → `redirect('site_report_edit',
|
|
work_log_id=created_log_ids[-1])` (what the old default did)
|
|
- else (`log_only`, missing, or anything unrecognised) →
|
|
`redirect('home')` + success toast
|
|
|
|
### `core/views.py::site_report_edit` (POST success branch)
|
|
|
|
Current: always `redirect('home')` after `instance.save()`.
|
|
|
|
New:
|
|
```python
|
|
instance.save()
|
|
messages.success(request, f"Site journal saved for {work_log.project.name} on {work_log.date:%d %b %Y}.")
|
|
if request.POST.get('next_action') == 'save_absences':
|
|
from urllib.parse import urlencode
|
|
params = {'date': work_log.date.isoformat()}
|
|
if work_log.team_id:
|
|
params['team'] = work_log.team_id
|
|
if work_log.project_id:
|
|
params['project'] = work_log.project_id
|
|
return redirect(f"{reverse('absence_log')}?{urlencode(params)}")
|
|
return redirect('home')
|
|
```
|
|
|
|
(Exact variable names verified against the live view during planning.)
|
|
|
|
## Tests (~5, in `core/tests.py`)
|
|
|
|
1. `attendance_log` POST `next_action=log_only` → 302 to `/` (home),
|
|
NOT to `/site-report/…`. (Regression — this reverses Phase A.1.)
|
|
2. `attendance_log` POST `next_action=log_journal` → 302 to
|
|
`/site-report/<id>/edit/`.
|
|
3. `attendance_log` POST `next_action=log_absences` → 302 to
|
|
`/absences/log/?...` (existing test, confirm still green).
|
|
4. `site_report_edit` POST default (`next_action` absent) → saves +
|
|
302 to `/` (home).
|
|
5. `site_report_edit` POST `next_action=save_absences` → saves +
|
|
302 to `/absences/log/?date=…&team=…&project=…`.
|
|
6. Conflict-resolution path carries `next_action=log_journal` through
|
|
to the final redirect (extend the existing Round C conflict test).
|
|
|
|
Update any existing test that asserted attendance submit redirects to
|
|
the site report by default — that contract is intentionally changing.
|
|
|
|
## Files touched
|
|
|
|
| File | Change |
|
|
|---|---|
|
|
| `core/templates/core/attendance_log.html` | +1 button (`log_journal`), restyle 3 buttons into primary/secondary hierarchy |
|
|
| `core/templates/core/site_report_edit.html` | text → "Site Journal"; +1 submit button (`save_absences`) |
|
|
| `core/templates/core/site_report_detail.html` | heading/title text → "Site Journal" |
|
|
| `core/templates/core/work_history.html` | clipboard tooltip text → "site journal" |
|
|
| `core/views.py::attendance_log` | 3-way `next_action` branch (log_only→home, log_journal→site report, log_absences→absences) |
|
|
| `core/views.py::site_report_edit` | `next_action=save_absences` branch + toast text |
|
|
| `core/tests.py` | ~6 new/updated tests |
|
|
| `docs/plans/parked-work.md` | Backburner note: future voice feature ≠ "Journal" |
|
|
| `CLAUDE.md` | Update the SiteReport line + URL-routes note: UI label is "Site Journal", model stays `SiteReport` (another Path-A entry) |
|
|
|
|
**No model / migration / URL / dependency changes.** ~120 LOC incl.
|
|
tests. Pure flow + display change.
|
|
|
|
## Out of scope (deliberately)
|
|
|
|
- No change to the SiteReport model, schema, or `site_report_schema.py`.
|
|
- No change to the absences feature itself (just the prefill entry point).
|
|
- No actual building of the voice-transcript "Voice Notes" feature —
|
|
still backburnered; this only reserves the name.
|
|
- No "what next?" interstitial panel (rejected in Q2 — Konrad chose a
|
|
plain dashboard landing for `log_only`).
|
|
|
|
## Verification (manual, local — Konrad)
|
|
|
|
1. `/attendance/log/` → submit with **Log Work** → lands on dashboard
|
|
with toast, NOT the site-journal form.
|
|
2. Submit with **Log Work + Site Journal** → lands on the Site Journal
|
|
form (titled "Site Journal").
|
|
3. Submit with **Log Work + Add Absences** → lands on `/absences/log/`
|
|
prefilled (unchanged).
|
|
4. On the Site Journal form, **Save Site Journal** → dashboard + toast.
|
|
5. **Save Site Journal + Add Absences** → `/absences/log/` prefilled
|
|
with the same date/team/project.
|
|
6. Trigger an attendance conflict while having clicked "Log Work +
|
|
Site Journal" → resolve it → still lands on the Site Journal form.
|
|
7. Full suite green (`USE_SQLITE=true … manage.py test core.tests`).
|
|
|
|
Then — and only then — Konrad decides whether to push to `ai-dev` +
|
|
deploy.
|
|
|
|
## Branch / deploy
|
|
|
|
Build on `ai-dev`. **Do NOT push to origin or deploy** until Konrad
|
|
has run the local verification above and explicitly approves. This is
|
|
a UX-flow change to a daily-use path — it warrants a hands-on local
|
|
check before it reaches production.
|