From fcb959900f60508a034920c5b53b6a724e9730ec Mon Sep 17 00:00:00 2001 From: NguyenND Date: Wed, 15 Jul 2026 13:47:59 -0400 Subject: [PATCH] Jul 15 - Add Auditor user role --- CLAUDE.md | 28 +++++++++--- app/api/comments.py | 2 +- app/api/inspections.py | 2 +- app/api/issues.py | 2 +- app/api/photos.py | 2 +- app/api/scheduled.py | 2 +- app/api/stats.py | 2 +- app/api/templates.py | 2 +- app/models/notification_matrix.py | 1 + app/models/user.py | 2 +- app/routes/dashboard.py | 5 ++- app/routes/inspections.py | 2 +- app/routes/issues.py | 35 +++++++++------ app/templates/auth/users.html | 2 +- app/templates/base.html | 4 +- app/templates/dashboard.html | 4 +- app/templates/facilities/list.html | 2 +- app/templates/facilities/view.html | 6 +-- app/templates/issues/list.html | 8 ++-- app/templates/issues/view.html | 10 ++--- app/templates/reports/_subnav.html | 4 +- app/templates/scheduled_inspections/list.html | 8 ++-- app/utils/decorators.py | 26 ++++++++++- app/utils/forms.py | 1 + app/utils/notifications.py | 1 + migrations/versions/phase40_auditor_role.py | 45 +++++++++++++++++++ 26 files changed, 152 insertions(+), 56 deletions(-) create mode 100644 migrations/versions/phase40_auditor_role.py diff --git a/CLAUDE.md b/CLAUDE.md index 555ab61..914f5d9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,7 @@ > **Audience:** AI assistants and developers working on this codebase. > **Purpose:** Authoritative reference for architecture, conventions, gotchas, and decisions. -> **Last reviewed:** July 2026 (Phase 19 complete + mobile API gap-fill Phases A–E + customer UI refinements + Phase 22 comment visibility + Phase 23 support chat/tickets + inspector performance Excel export + inspection list filters + customer issue logging + AI chatbot + dashboard grouped sections + issues/inspections PDF export + date/ID filters + Reports expansion Phases R1–R4 + Phase 24 issue_created notify defaults + Phase 25 inspection GPS + Phase 26 issue vendor fields + Phase 27 facility score alerts + Phase 28 inspection-notify fix + Phase 29 admin broadcasts + Phases 30–32 device registry consolidation + ProxyFix reverse-proxy fix + Phase 33 per-contract notification recipients + grouped Admin nav dropdown + forgot-password case-insensitive lookup & email normalization + transactional email sender/branding fix + Phase 34 facility QR public pages & report-a-problem + Phase 35 issue handler_type (our staff / facility / vendor) + Phase 36 scheduled inspections + Phase 37 support chat persistence + Phase 38 support knowledge base + Phase 39 per-area QR public pages) +> **Last reviewed:** July 2026 (Phase 19 complete + mobile API gap-fill Phases A–E + customer UI refinements + Phase 22 comment visibility + Phase 23 support chat/tickets + inspector performance Excel export + inspection list filters + customer issue logging + AI chatbot + dashboard grouped sections + issues/inspections PDF export + date/ID filters + Reports expansion Phases R1–R4 + Phase 24 issue_created notify defaults + Phase 25 inspection GPS + Phase 26 issue vendor fields + Phase 27 facility score alerts + Phase 28 inspection-notify fix + Phase 29 admin broadcasts + Phases 30–32 device registry consolidation + ProxyFix reverse-proxy fix + Phase 33 per-contract notification recipients + grouped Admin nav dropdown + forgot-password case-insensitive lookup & email normalization + transactional email sender/branding fix + Phase 34 facility QR public pages & report-a-problem + Phase 35 issue handler_type (our staff / facility / vendor) + Phase 36 scheduled inspections + Phase 37 support chat persistence + Phase 38 support knowledge base + Phase 39 per-area QR public pages + Phase 40 auditor role) --- @@ -191,7 +191,9 @@ users: id, username (unique, indexed), full_name, email (unique, indexed), password_set, set_password_token (indexed), set_password_token_expires ``` -**Role ENUM:** `admin`, `director`, `inspector`, `project_manager`, `customer` +**Role ENUM:** `admin`, `director`, `inspector`, `project_manager`, `customer`, `auditor` + +**`auditor` (Phase 40):** A staff role with the **same access as `project_manager`** (it is included in `@project_manager_required` and everywhere `project_manager` is checked) **plus full issue-management powers** — create, assign, quick-assign, handler/vendor triage, request-verification, and verify/bulk-verify/verification-queue (via the new `@issue_manager_required` decorator). **Auditor does NOT get issue deletion** (that stays admin/director via `@supervisor_required`), nor any other admin/director-only area PM lacks (users, audit trail, notification matrix, customers, templates). Auditors are **assignable** as an issue/inspection assignee; **admin was removed** from the assignable set at the same time (assignee dropdowns are now `director`/`inspector`/`auditor`, plus `project_manager` on the inspection flag-issue dropdown). The issue-update route defensively keeps any pre-existing out-of-set assignee (e.g. a legacy admin assignment) in the dropdown so saving never silently unassigns. Auditor **has mobile-API access** — it is included in the `_ALLOWED_ROLES` set of every `app/api/*` module (comments, inspections, issues, photos, scheduled, stats, templates), so the iPad app accepts auditor logins. In every API endpoint that scopes by role, auditor falls into the non-inspector/non-customer (privileged) branch — org-wide data, same as admin/director/PM. **Key property:** `display_name` → `full_name.strip()` or falls back to `username`. @@ -426,6 +428,8 @@ Management (`/scheduled-inspections/new|edit|delete`) is `@project_manager_requi ## 6. Role & Permission Matrix +**`auditor` reads as a `project_manager` column** below, with these overrides: **Issues (quick-assign)** ✅, **Issue verification** ✅, and it appears in the **Issues (create/assign)** and **Issue verification** rows as ✅. It never gains issue *delete* or any admin/director-only row PM lacks. See the `auditor` note in §5. + | Area | admin | director | project_manager | inspector | customer | |---|---|---|---|---|---| | Dashboard | ✅ full | ✅ full | ✅ full | ✅ limited | ✅ scoped | @@ -454,7 +458,8 @@ Management (`/scheduled-inspections/new|edit|delete`) is `@project_manager_requi ```python @admin_required # role == 'admin' only @supervisor_required # role in ('admin', 'director') — name kept to avoid touching 30+ routes -@project_manager_required # role in ('admin', 'director', 'project_manager') +@project_manager_required # role in ('admin', 'director', 'project_manager', 'auditor') +@issue_manager_required # role in ('admin', 'director', 'auditor') — issue verification (NOT delete) @customer_required # role == 'customer' only ``` @@ -472,7 +477,7 @@ Management (`/scheduled-inspections/new|edit|delete`) is `@project_manager_requi | `customers` | `/customers` | list, invite, set-password, manage, import CSV | | `inspections` | `/inspections` | list, start, execute, view, PDF export, flag-issue, save-draft (AJAX), flag-followup, reinspect, upload-photo (AJAX) | | `templates` | `/templates` | list, create, edit, delete, form editor, preview | -| `issues` | `/issues` | list, view, create, update, verify, comment, follow/unfollow, verification queue, bulk-verify, delete, quick-assign | +| `issues` | `/issues` | list, view, create, update, verify, comment, follow/unfollow, verification queue, bulk-verify, delete, quick-assign. **verify / bulk-verify / verification-queue are `@issue_manager_required` (admin/director/auditor); delete stays `@supervisor_required` (admin/director).** | | `notifications` | `/notifications` | list, mark-read, preferences, send-digest (cron), check-sla (cron), cleanup-tokens (cron) | | `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 | @@ -806,7 +811,8 @@ phase1_projects_roles → phase6_features → phase7_mobile_api → phase8_notif → phase36_scheduled_insp → phase37_support_chat → phase38_support_knowledge - → phase39_area_public_token ← HEAD + → phase39_area_public_token + → phase40_auditor_role ← HEAD ``` ### phase21_performance_indexes @@ -955,6 +961,16 @@ flask db upgrade # adds + backfills areas.public_token sudo systemctl restart gunicorn ``` +### phase40_auditor_role + +Revision id `phase40_auditor_role`. Adds the `auditor` value to the `users.role` ENUM (`ALTER TABLE users MODIFY COLUMN role ENUM(...,'auditor') NOT NULL`). This is a **pure ENUM expansion** (adds a value, removes/migrates nothing), so the 3-step ENUM protocol does not apply and the `MODIFY` is idempotent — safe to re-run. `downgrade()` reassigns any `auditor` rows to `project_manager` before contracting the ENUM. Backs the new Auditor role — see the `auditor` note in §5 and the `@issue_manager_required` decorator in §6. + +**Deploy order:** +```bash +flask db upgrade # expands users.role ENUM with 'auditor' +sudo systemctl restart gunicorn +``` + **Deploy order for phases 24–32:** ```bash flask db upgrade @@ -1300,6 +1316,8 @@ timeout = 30 | 76 | **Transactional email `From` must be an SMTP-authorized identity, per-domain branding via display name only** | Reset-password sends from `MAIL_DEFAULT_SENDER`; customer invite sends from `branded_sender()` = `(per-domain display name, authorized address)`. A per-host `noreply@` sender is accepted by the relay then dropped by SPF/DMARC. See rule 64 and §8 `mail_utils.py`. | | 77 | **`GET /api/v1/scheduled-inspections` is inspector-scoped by `inspector_id`, admin/director/PM see all** | New `app/api/scheduled.py` blueprint. Register in `app/api/__init__.py` AND `csrf.exempt(_api_scheduled_bp)` in `app/__init__.py` — the child-blueprint CSRF exemption never cascades from the parent. Read-only; do not add write/fulfil endpoints here (the schedule lifecycle stays in `routes/scheduled_inspections.py`). | | 78 | **`PATCH /api/v1/issues//handler` allows the inspector on purpose — do NOT align it to the web form's admin/director/PM restriction** | The iPad lets the assigned inspector set "Handled By" from the field, scoped via `get_inspector_scope()` (403 if the issue's facility isn't contracted). This is a deliberate divergence from the web form. `_issue_payload()` must keep returning all 8 handler fields (`handler_type`, `handler_label`, `facility_handler_*`, `vendor_*`) or the iPad's "Handled By" panel silently blanks — same failure mode as rule 40. | +| 79 | **`auditor` = `project_manager` access + issue management, minus delete — keep the two decorators distinct** | Auditor is added to `@project_manager_required` (PM baseline) and to every `project_manager` role check in routes/templates. Its *extra* issue powers (verify/bulk-verify/verification-queue) go through the separate `@issue_manager_required` (admin/director/auditor). Issue **delete** stays `@supervisor_required` — never add auditor there. When adding a new PM-level gate, include `auditor`; when adding a director-only or delete-level gate, do not. The three issue **delete** template gates (spaced `['admin', 'director']` in `issues/list.html` + `issues/view.html`) are deliberately left without auditor. Auditor is also in the `_ALLOWED_ROLES` set of every `app/api/*` module — a **new** API blueprint's `_ALLOWED_ROLES` must include `auditor` for PM parity. | +| 80 | **Assignee dropdowns are `director`/`inspector`/`auditor` (admin removed, auditor added)** | The issue/inspection assignee ` @@ -208,7 +208,7 @@ - {% if current_user.role in ['admin','director'] or issue.assigned_to == current_user.id %} + {% if current_user.role in ['admin','director','auditor'] or issue.assigned_to == current_user.id %} Edit {% else %} View @@ -290,7 +290,7 @@ }()); -{% if current_user.role in ['admin', 'director'] %} +{% if current_user.role in ['admin', 'director', 'auditor'] %}