Aug 19 - Update: now customer Director user can create scheduled task for their staff

This commit is contained in:
2026-08-19 10:18:18 -04:00
parent d8b5ce201d
commit 5df105a9d5
5 changed files with 210 additions and 25 deletions
+18 -2
View File
@@ -540,7 +540,22 @@ All are nullable and **legacy phase36 rows keep NULLs**, falling back to `_add_i
- **Instructions (July 2026).** `ScheduledInspectionForm.notes` is labelled **"Instructions"** and `scheduled_inspections/form.html` explains that the text reaches the inspector. The *field name*, `ScheduledInspection.notes`, the `scheduled_inspections.notes` column and the API key `notes` are all unchanged — the rename is a label only (rule 84). The text is surfaced to the inspector in two places: `inspections/execute.html` renders an indigo panel between the header and the form grid, guarded on `inspection.scheduled_inspection and .notes` (NULL for ad-hoc work and for schedules deleted after the start); the iPad shows it on the scheduled row, on the start screen and above the form.
- **"Scheduled" badge:** an inspection started from a schedule carries `scheduled_inspection_id`. `Inspection.scheduled_inspection` (relationship, foreign_keys on that column) resolves the source schedule (None if ad-hoc or the schedule was later deleted). The inspection **detail** view header shows a `bi-calendar-check` "Scheduled · <frequency>" badge, and the inspection **list** shows a compact "Scheduled" pill next to the template name — both gated on `scheduled_inspection_id` being set.
Management (`/scheduled-inspections/new|edit|delete`) is `@project_manager_required`; **Start** is the **assigned inspector ONLY** (`sched.inspector_id == current_user.id`) — managers do NOT get a Start button and `GET /<id>/start` 403s for anyone who isn't the assignee (the inspection is theirs to do; a manager who must run it assigns it to themselves). The Start button is hidden for non-assignees on both the scheduled-inspections list and the dashboard panel. Inspectors' list/dashboard views are scoped to their own `inspector_id`.
**Customer Directors can plan their own inspections (Aug 2026).** A client asking for an extra clean no longer has to go through us. `@schedule_manager_required` (in the blueprint) = the old `@project_manager_required` set **plus `role == 'customer'`**; `@customer_required` is not reused because this is a capability grant, not a portal gate.
Everything a Customer Director sees is narrowed to the facilities on their `CustomerAssignment` rows, and **the narrowed choice lists ARE the POST validation**`SelectField` rejects anything not offered, so this is the security boundary rather than a tidier dropdown:
| List | Narrowed to |
|---|---|
| facility_id | `get_customer_scope()` — deliberately breaking rule 61's "all facilities" convention, which exists so the UI-only contract selector cannot fail validation; for a customer the scope is the point |
| contract selector (`_active_contracts`) | the contracts behind those facilities |
| template_id | shared forms + those attached to their contracts (phase52), so another customer's bespoke form NAMES never appear (rule 96) |
| inspector_id | inspectors holding an `InspectorAssignment` on their contracts — theirs and ours, never another client's Customer Inspector (rule 93) |
Plus `_reject_facility_out_of_scope()` re-checks the chosen facility after validation (belt-and-braces: the choice-narrowing is a property of how a list was *built*), `_schedule_in_scope()` 403s edit/delete of a schedule outside their facilities, and `index()` filters the list — with `filter(False)` on an empty scope, never a skipped filter (rule 57's failure mode).
**Customer Inspectors are excluded**`_is_customer_director()` tests `role == 'customer'` by equality (rule 89). They *perform* scheduled inspections and are scoped by `InspectorAssignment`; handing them this screen would scope it by the wrong table and show no facilities at all. **Start** is unchanged and still assignee-only, so a Customer Director can plan work but never execute it.
Management of the underlying routes is otherwise unchanged; **Start** is the **assigned inspector ONLY** (`sched.inspector_id == current_user.id`) — managers do NOT get a Start button and `GET /<id>/start` 403s for anyone who isn't the assignee (the inspection is theirs to do; a manager who must run it assigns it to themselves). The Start button is hidden for non-assignees on both the scheduled-inspections list and the dashboard panel. Inspectors' list/dashboard views are scoped to their own `inspector_id`.
---
@@ -561,6 +576,7 @@ Management (`/scheduled-inspections/new|edit|delete`) is `@project_manager_requi
| Templates | ✅ | ✅ | ❌ | ❌ | ❌ |
| Inspections (execute) | ✅ | ✅ | ✅ | ✅ | read |
| Inspection follow-up (request) | ✅ | ✅ | ❌ | ❌ | ✅ own facilities |
| Scheduled inspections (plan) | ✅ | ✅ | ✅ | ❌ | ✅ own contracts |
| Inspection follow-up (clear) | ✅ | ✅ | ❌ | ❌ | ❌ |
| Issues (create/assign) | ✅ | ✅ | ✅ | ✅ | ✅ create own |
| Issues (quick-assign) | ✅ | ✅ | ❌ | ❌ | ❌ |
@@ -606,7 +622,7 @@ Management (`/scheduled-inspections/new|edit|delete`) is `@project_manager_requi
| `audit` | `/audit` | list (admin only), view, purge |
| `reports` | `/reports` | index, facility report, scorecard, CSV/PDF/Excel export, issues-aging, sla-compliance, followup-closure, facility summary PDF |
| `scheduled_reports` | `/scheduled-reports` | CRUD + manual trigger (accessible via Reports sub-nav) |
| `scheduled_inspections` | `/scheduled-inspections` | list (`?tab=pending\|completed` — Phase 47), new/edit/delete (PM+), `GET /<id>/start` (**assigned inspector only** → creates linked inspection; 403 for non-assignees incl. managers), `POST /<id>/acknowledge` (**assigned inspector only** → confirms receipt, sets `acknowledged_at`, notifies creator; idempotent — Phase 47), `GET /confirm/<token>` (**login-free** one-click email confirm; signed `itsdangerous` token binding schedule+inspector — Phase 47), `POST /run` (cron reminders, `token=DIGEST_SECRET`) |
| `scheduled_inspections` | `/scheduled-inspections` | list (`?tab=pending\|completed` — Phase 47), new/edit/delete (`@schedule_manager_required` — admin/director/PM/auditor **plus Customer Directors, scoped to their own contracts**), `GET /<id>/start` (**assigned inspector only** → creates linked inspection; 403 for non-assignees incl. managers), `POST /<id>/acknowledge` (**assigned inspector only** → confirms receipt, sets `acknowledged_at`, notifies creator; idempotent — Phase 47), `GET /confirm/<token>` (**login-free** one-click email confirm; signed `itsdangerous` token binding schedule+inspector — Phase 47), `POST /run` (cron reminders, `token=DIGEST_SECRET`) |
| `support` | `/support` | `GET /chat` (loads latest saved session; `?new=1` to start fresh), `POST /chat/message` (AJAX→Groq; **persists** user+assistant turns, returns `session_id`), `GET /my-conversations`, `GET /my-conversations/<id>` (customer chat history), `GET /admin/conversations`, `GET /admin/conversations/<id>` (staff, read-only), `GET /admin/knowledge` + `/new`, `/<id>/edit`, `/<id>/delete` (admin/director — chatbot knowledge base), `POST /tickets`, `GET /my-tickets`, `GET/POST /my-tickets/<id>`, `GET /admin/tickets`, `GET/POST /admin/tickets/<id>` |
| `enrollment` | `/enrollment` | **Self-contained onboarding intake — see §24.** `GET/POST /` (**login-free** public form), `GET /admin` (admin inbox), `GET/POST /admin/<id>` (detail + office-use fields), `GET /admin/<id>.json`, `GET /admin/export.csv`. Lives in `app/enrollment/` with its own templates; touches **no** DB table. |
| `broadcast` | `/admin/broadcast` | `GET /` (compose + history), `POST /send` (admin-only; fans out one Notification per targeted user) |