04/27 Updated documents
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
> **Audience:** AI assistants and developers working on this codebase.
|
||||
> **Purpose:** Authoritative reference for architecture, conventions, gotchas, and decisions.
|
||||
> **Last reviewed:** April 2026 (Phase 12 complete + post-review hardening patch)
|
||||
> **Last reviewed:** April 2026 (Phase 12 complete + post-review hardening + UX improvements)
|
||||
|
||||
---
|
||||
|
||||
@@ -195,7 +195,9 @@ users: id, username (unique, indexed), full_name, email (unique, indexed),
|
||||
|
||||
**Expired invitations:** Customer accounts with `password_set=False` and `set_password_token_expires < now_eastern()` are flagged on the `/customers` index with an inline Resend button.
|
||||
|
||||
**Token verification:** `User.verify_set_password_token()` uses `hmac.compare_digest()` as a constant-time comparison guard after the DB lookup and expiry check, preventing timing oracle attacks on the stored token value.
|
||||
**Customer self-service setup:** When a customer clicks their invitation link, `set_password.html` prompts them to choose their own **username** (replacing the auto-generated placeholder) and set a **password**. The `set_password` route saves `form.username.data` to `user.username` before committing. The `SetPasswordForm` validates username uniqueness inline via `validate_username()`.
|
||||
|
||||
**Token verification:** `User.verify_set_password_token()` guards in order: token present → DB lookup → `password_set=False` check → expiry check → `hmac.compare_digest()` constant-time comparison. The `password_set=False` guard ensures accounts already activated cannot be re-used via a stale token. `compare_digest` is wrapped in `try/except` to safely handle type mismatches.
|
||||
|
||||
### Facility / Area
|
||||
|
||||
@@ -326,10 +328,10 @@ api_device_tokens: id, user_id, device_id, apns_token, device_name, app_version
|
||||
|---|---|---|
|
||||
| `auth` | `/auth` | `/login` (rate-limited 20/min), `/logout`, `/profile`, `/users/*`, `/notification-matrix` |
|
||||
| `dashboard` | `/` | `GET /`, `/facility-trend` (AJAX) |
|
||||
| `facilities` | `/facilities` | CRUD + area management |
|
||||
| `facilities` | `/facilities` | CRUD + area management; list grouped by Contract with collapsible sections; detail page has Back button |
|
||||
| `projects` | `/projects` | CRUD + customer assignment management |
|
||||
| `customers` | `/customers` | list (expired invitation banner), invite, set-password, manage, import CSV |
|
||||
| `inspections` | `/inspections` | list (stale badge, `now` passed from route), start, execute, view, PDF export, flag-issue, save-draft (AJAX), flag-followup, reinspect |
|
||||
| `customers` | `/customers` | list (expired invitation banner), invite (name + email only), set-password (customer chooses username + password), manage, import CSV |
|
||||
| `inspections` | `/inspections` | list (stale badge, `now` passed from route), start (template + facility only — area and notes removed), execute, view, PDF export, flag-issue, save-draft (AJAX), flag-followup, reinspect |
|
||||
| `templates` | `/templates` | list, create, edit, delete, form editor, preview |
|
||||
| `issues` | `/issues` | list (SLA column, facility filter, quick-assign dropdown), view, create, update, verify, comment, follow/unfollow, verification queue, delete |
|
||||
| `issues` | `/issues` | `POST /<id>/quick-assign` — JSON AJAX, admin/director only |
|
||||
@@ -625,6 +627,10 @@ Logs: `/home/jqc/logs/gunicorn-error.log`, `/home/jqc/logs/gunicorn-access.log`
|
||||
| 23 | **`hmac.compare_digest()` for token comparison** | Prevents timing oracle attacks on `verify_set_password_token` |
|
||||
| 24 | **`get_customer_scope()` uses bulk project query** | Single `Facility.project_id.in_(project_ids)` replaces per-assignment loop |
|
||||
| 25 | **CSV exports always call `log_action(ACTION_EXPORT, ...)`** | Data exports are compliance-relevant audit events |
|
||||
| 26 | **`StartInspectionForm` has no `area_id` or `notes` fields** | Both removed from the start page; `Inspection` constructor receives `area_id=None, notes=None` explicitly |
|
||||
| 27 | **Customer set-password page collects username + password** | `SetPasswordForm` includes `username` field; route saves it to `user.username`, replacing the auto-generated placeholder |
|
||||
| 28 | **`verify_set_password_token` requires `password_set=False`** | Prevents reuse of a stale token on an already-activated account |
|
||||
| 29 | **Facilities list grouped by Contract in route, not template** | `list_facilities()` builds a `grouped` OrderedDict before rendering; template iterates `grouped`, not the flat `facilities` list |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ A production-grade web application for managing janitorial service contracts, fa
|
||||
|
||||
## Features
|
||||
|
||||
- **Inspection Management** — Execute structured inspections against configurable templates with a drag-and-drop form builder supporting ratings, pass/fail, photos, signatures, and free-form fields
|
||||
- **Inspection Management** — Execute structured inspections against configurable templates with a drag-and-drop form builder supporting ratings, pass/fail, photos, signatures, and free-form fields. Starting an inspection requires only a Template and Facility selection.
|
||||
- **Issue Tracking** — Full lifecycle management (open → in-progress → pending verification → resolved) with SLA enforcement, follower subscriptions, resolution photo uploads, and inline quick-assign from the issues list
|
||||
- **Customer Portal** — Scoped facility visibility for client accounts with invitation-based onboarding (email link, 72-hour token); expired invitation warnings surface on the admin dashboard
|
||||
- **Customer Portal** — Scoped facility visibility for client accounts with invitation-based onboarding; admin enters name and email only, customer chooses their own username and password via a 72-hour emailed link; expired invitation warnings surface on the admin dashboard
|
||||
- **Notification System** — In-app + email notifications driven by an admin-controlled routing matrix; per-user preferences including digest mode and a one-click "Pause All Emails" toggle
|
||||
- **Reports** — On-demand PDF/CSV scorecards and scheduled recurring email reports (daily/weekly/monthly)
|
||||
- **Audit Trail** — Immutable log of every create, update, and delete action with actor and IP capture
|
||||
@@ -218,12 +218,13 @@ flask shell
|
||||
## Customer Onboarding
|
||||
|
||||
1. Admin navigates to **Customers → New Customer**
|
||||
2. Enter the customer's name and email — username is auto-generated
|
||||
3. The system sends an invitation email with a 72-hour setup link
|
||||
4. Customer sets their password via the link and gains access to their scoped portal
|
||||
5. Admin assigns the customer to Contracts/Facilities via **Customers → Manage**
|
||||
2. Enter the customer's **Full Name** and **Email** only — no username or password required from the admin
|
||||
3. The system auto-generates a temporary username and sends an invitation email with a secure **72-hour setup link**
|
||||
4. Customer clicks the link and arrives at the account setup page where they **choose their own username and set their password**
|
||||
5. The account activates immediately and the customer can log in
|
||||
6. Admin assigns the customer to Contracts/Facilities via **Customers → Manage**
|
||||
|
||||
> Expired invitations (token past 72 hours, password never set) are surfaced as a warning banner on the Customers page with inline **Resend** buttons.
|
||||
> Expired invitations (token past 72 hours, password never set) are surfaced as a warning banner on the Customers page with inline **Resend** buttons. Resending generates a fresh 72-hour token.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user