Jul 8 - Update 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:** 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)
|
||||
> **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)
|
||||
|
||||
---
|
||||
|
||||
@@ -133,7 +133,7 @@ lt_janitorial_quality_control/
|
||||
│ └── utils/
|
||||
├── migrations/
|
||||
│ └── versions/
|
||||
│ └── phase32_device_token_columns.py ← HEAD
|
||||
│ └── phase36_scheduled_inspections.py ← HEAD
|
||||
└── ...
|
||||
|
||||
Note: `app/routes/broadcast.py` + `app/models/broadcast.py` (admin broadcasts) and
|
||||
@@ -390,6 +390,7 @@ inspections.scheduled_inspection_id FK→scheduled_inspections SET NULL ← P
|
||||
**A plan, not an inspection.** Names a facility + template + assigned inspector + `next_due_date`. Lifecycle:
|
||||
- The assigned inspector (or a manager) clicks **Start** → `scheduled_inspections.start` creates a normal `in_progress` Inspection with `scheduled_inspection_id` set, then redirects to the execute flow.
|
||||
- On **completion** (execute route, status → `completed`), `ScheduledInspection.fulfill()` runs in the same atomic commit: `once` → `active=False`; recurring → `next_due_date` rolls forward past today via `_add_interval()` and the three `*_notified` flags reset.
|
||||
- **Assignment notification** (immediate): on **create**, the assigned inspector gets an in-app + email "assigned to you" notification; on **edit**, only when the inspector actually changes (a "reassigned to you" notification to the new assignee). Via `_notify_assignee()` in the blueprint using `event_type=EVENT_SCHEDULED_INSPECTION`.
|
||||
- **Reminders** are dispatched by the cron endpoint (see §11): advance (1 day before) + due-date to the inspector, overdue to admin/director — each fires at most once per occurrence via the `*_notified` flags. Uses `notify()` with `event_type=EVENT_SCHEDULED_INSPECTION`.
|
||||
- Dashboard shows an **upcoming (next 7 days) / overdue** panel for non-customers (inspectors see only their own).
|
||||
|
||||
@@ -932,6 +933,9 @@ if obj is None: abort(404)
|
||||
### Active Nav Tab
|
||||
Detected via `request.endpoint.startswith('<blueprint>.')` in each nav `<a>` tag.
|
||||
|
||||
### Admin Nav Dropdown
|
||||
Admin-only tools (Users, Audit Trail, Notification Matrix, Broadcast, Devices) are consolidated into a single **Admin** dropdown in `base.html` (plain text items, no icons, no dividers — matching sibling top-level nav links). The dropdown toggle shows `active` when any of its endpoints is active (`admin_active` flag). The separate user-menu dropdown keeps its own dividers — don't `replace_all` on `dropdown-divider` across the file.
|
||||
|
||||
### Display Names
|
||||
Always use `user.display_name` in templates — never `.username` for display purposes.
|
||||
|
||||
@@ -960,7 +964,7 @@ Always use `user.display_name` in templates — never `.username` for display pu
|
||||
|
||||
The Contract selector is always a plain HTML `<select>` (never a WTForms field). On `change` it calls `GET /inspections/facilities_for_project/<project_id>` and replaces the Facility `<option>` list. When the Contract is cleared it restores the "All Facilities" placeholder. The filter bars auto-narrow the server-side facility dropdown on page load when `contract_id` is in the query string.
|
||||
|
||||
Pages using this pattern: `issues/form.html` (create), `issues/list.html` (filter bar), `inspections/list.html` (filter bar).
|
||||
Pages using this pattern: `issues/form.html` (create), `issues/list.html` (filter bar), `inspections/list.html` (filter bar), `scheduled_inspections/form.html` (create/edit — Contract selector narrows the facility list; `selected_project_id` restores it on edit and on POST error).
|
||||
|
||||
The issues list and inspections list both accept a `contract_id` query param that filters the DB query to facilities belonging to that contract (`facility.project_id == contract_id`) and narrows the facility dropdown in the rendered HTML.
|
||||
|
||||
@@ -1206,6 +1210,8 @@ timeout = 30
|
||||
| 72 | **Device registration has exactly ONE implementation — `register_device()` in `app/api/auth.py` → `api_device_tokens`** | A second `POST /api/v1/devices/register` (`app/api/devices.py` + `DeviceRegistration` model) was removed July 2026. It was shadowed by the `api_auth` route at routing time and queried the dropped `device_registrations` table. Do not reintroduce a competing device model or duplicate register route. |
|
||||
| 73 | **Per-contract recipients are dispatched ONLY inside `notify_by_matrix()` — never add a parallel path** | `_notify_contract_recipients()` runs after role + global-custom-email routing and shares the `notified` / `sent_emails` dedup sets. Any new event that should reach contract recipients must go through `notify_by_matrix()` (passing `facility_id`, or an `issue_id`/`inspection_id` that resolves to one). Bypassing it means contract recipients are silently skipped and dedup breaks. Commit stays the caller's responsibility. |
|
||||
| 74 | **The `public` blueprint (`/f/*`) is login-free — keep it occupant-safe** | Pages are addressed by unguessable `public_token` (never facility id), 404 on inactive/unknown facilities, and expose only a quality rating, last-inspected date, and open-issue COUNT — **never** issue descriptions, inspector names, per-item scores, or any other facility's data. The `report` POST must stay CSRF-protected (Flask-WTF form), rate-limited, and honeypot-guarded; public-reported issues are created with `reported_by=NULL`, `severity='medium'`, and routed through `notify_by_matrix('issue_created', facility_id=...)`. Do not add fields that leak internal detail, and do not reuse `render_template('base.html')` here — the public page is a standalone template with no authenticated nav. |
|
||||
| 75 | **Email is stored lowercased; look it up case-insensitively** | User/customer email is normalized to `.strip().lower()` at every write site (`auth.py` profile/create/edit, `customers.py` invite/edit). Forgot-password lookup uses `db.func.lower(User.email) == input` so a mixed-case legacy row still matches — a plain `filter_by(email=...)` silently missed them and sent no reset (the failure was invisible because of the generic "if an account exists…" message). Keep both halves: normalize on write, case-insensitive on lookup. |
|
||||
| 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@<subdomain>` sender is accepted by the relay then dropped by SPF/DMARC. See rule 64 and §8 `mail_utils.py`. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -7,9 +7,11 @@ 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. 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
|
||||
- **Scheduled Inspections** — Plan one-time or recurring (daily/weekly/monthly) inspection assignments per facility/template/inspector with a due date. The assigned inspector is notified on assignment, reminded the day before and on the due date, and managers are alerted on overdue; completing a linked inspection rolls recurring schedules forward automatically. Upcoming/overdue panel on the dashboard.
|
||||
- **Issue Tracking** — Full lifecycle management (open → in-progress → pending verification → resolved) with SLA enforcement, follower subscriptions, resolution photo uploads, and inline quick-assign. Each issue records **who handles it** — our staff, the facility's own staff, or an external vendor — while always keeping one of our staff as the internal follow-up owner
|
||||
- **Facility QR Codes** — Each facility has a printable QR code (single or bulk print sheet) linking to a login-free, occupant-friendly status page showing recent cleaning quality and a "Report a Problem" form that files an issue directly; token-addressed and rate-limited
|
||||
- **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
|
||||
- **Notification System** — In-app + email notifications driven by an admin-controlled routing matrix, plus optional per-contract additional recipients (staff users or external emails) for chosen events; per-user preferences including digest mode and a one-click "Pause All Emails" toggle
|
||||
- **Reports & Analytics** — Comprehensive reporting suite accessible from the second nav position:
|
||||
- *Overview & Trends* — facility scorecards, score trend charts, and PDF/CSV exports
|
||||
- *Issues Aging* — open issues grouped into age buckets (`<24h` → `>4 weeks`) with SLA status per issue; Excel export
|
||||
@@ -48,7 +50,7 @@ A production-grade web application for managing janitorial service contracts, fa
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.11+
|
||||
- Python 3.12+ (several route files use nested same-quote f-strings that require 3.12)
|
||||
- MySQL 5.7+ (or 8.0+)
|
||||
- A configured SMTP server (port 465 or 587)
|
||||
|
||||
@@ -158,6 +160,8 @@ server {
|
||||
}
|
||||
```
|
||||
|
||||
> **Reverse proxy:** the app wraps its WSGI stack in `ProxyFix` (trusting one proxy hop), so it reads the real client IP and scheme from the `X-Forwarded-For` / `X-Forwarded-Proto` headers Nginx sets above. This is required for correct per-client rate limiting and `https://` external links. Do not remove those `proxy_set_header` lines.
|
||||
|
||||
### Systemd service (example)
|
||||
|
||||
```ini
|
||||
@@ -180,13 +184,17 @@ WantedBy=multi-user.target
|
||||
|
||||
## Cron Jobs
|
||||
|
||||
Four background tasks require scheduled execution. All endpoints that require a token use `DIGEST_SECRET`.
|
||||
Background tasks require scheduled execution. All endpoints that require a token use `DIGEST_SECRET`.
|
||||
|
||||
```bash
|
||||
# SLA alerts — every 30 minutes
|
||||
*/30 * * * * curl -s -X POST "https://your-domain.com/notifications/check-sla" \
|
||||
-d "token=YOUR_DIGEST_SECRET"
|
||||
|
||||
# Scheduled inspection reminders (advance/due to inspector, overdue to managers) — every 30 minutes
|
||||
*/30 * * * * curl -s -X POST "https://your-domain.com/scheduled-inspections/run" \
|
||||
-d "token=YOUR_DIGEST_SECRET"
|
||||
|
||||
# Daily digest emails — 7:00 AM
|
||||
0 7 * * * curl -s -X POST "https://your-domain.com/notifications/send-digest" \
|
||||
-d "token=YOUR_DIGEST_SECRET&frequency=daily"
|
||||
@@ -198,6 +206,10 @@ Four background tasks require scheduled execution. All endpoints that require a
|
||||
# Scheduled report delivery — 8:00 AM
|
||||
0 8 * * * curl -s -X POST "https://your-domain.com/scheduled-reports/run" \
|
||||
-d "secret=YOUR_DIGEST_SECRET"
|
||||
|
||||
# Facility score-drop trend alerts — 8:00 AM
|
||||
0 8 * * * curl -s -X POST "https://your-domain.com/notifications/check-score-trends" \
|
||||
-d "token=YOUR_DIGEST_SECRET"
|
||||
```
|
||||
|
||||
---
|
||||
@@ -274,6 +286,7 @@ flask db downgrade
|
||||
|
||||
- **ENUM changes** require three steps: expand → migrate data → contract. Never skip steps.
|
||||
- **`CREATE INDEX IF NOT EXISTS`** is not supported on MySQL < 8.0.1. Use `information_schema.statistics` existence checks instead — see `phase12_performance_indexes.py` for the reusable `_index_exists()` helper pattern.
|
||||
- **Migration `revision` ids must be ≤ 32 characters** — Alembic's `alembic_version.version_num` column is `VARCHAR(32)`. A longer id passes the DDL step but fails when Alembic writes the version row (`Data too long for column 'version_num'`). Keep the `revision = '...'` value short even if the filename is long.
|
||||
|
||||
---
|
||||
|
||||
@@ -304,6 +317,7 @@ flask db downgrade
|
||||
| `REDIS_URL` | — | Redis connection URI for shared rate-limit storage; optional but recommended in production |
|
||||
| `GROQ_API_KEY` | — | Groq API key. When absent the AI chatbot is disabled; customers can still submit support tickets. |
|
||||
| `GROQ_MODEL` | `llama-3.3-70b-versatile` | Groq model ID override |
|
||||
| `GOOGLE_MAPS_API_KEY` | `""` | Optional. Enables the Google Maps embed of inspection GPS coordinates on the inspection detail page (admin/director). |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# Customer User Manual — New Sections (July 2026)
|
||||
|
||||
> Paste these sections into **JQC_Customer_User_Manual_v2.docx**. Suggested
|
||||
> placement noted under each heading. Replace the *[Screenshot]* placeholders
|
||||
> with captures from your environment.
|
||||
|
||||
---
|
||||
|
||||
## Facility QR Codes — Check Status & Report a Problem
|
||||
|
||||
*Suggested placement: after the "Viewing Your Facilities" section.*
|
||||
|
||||
Each facility has a **QR code** posted on-site (for example, at the entrance or
|
||||
inside restrooms). Anyone — staff, occupants, or visitors — can scan it with a
|
||||
phone camera to see the facility's current cleaning status and report a problem.
|
||||
**No login is required.**
|
||||
|
||||
*[Screenshot: posted QR code / the scan landing page on a phone]*
|
||||
|
||||
### What the page shows
|
||||
|
||||
When you scan the code (or open the link), you'll see:
|
||||
|
||||
- **Cleaning Quality** — an overall rating (Excellent, Good, Fair, or Needs
|
||||
attention) with the facility's average inspection score over the last 90 days.
|
||||
- **Last Inspected** — the date of the most recent inspection and its score.
|
||||
- **Open Issues** — how many issues are currently being tracked at the facility.
|
||||
|
||||
> For privacy, the public page shows only summary information. It never displays
|
||||
> individual issue details, inspector names, or any other facility's data.
|
||||
|
||||
### Reporting a problem
|
||||
|
||||
1. On the facility status page, scroll to **Report a Problem**.
|
||||
2. Optionally enter **where** in the building the problem is (e.g., "2nd floor
|
||||
men's restroom").
|
||||
3. Describe the problem.
|
||||
4. Optionally add your **name and contact** and attach a **photo**.
|
||||
5. Tap **Submit Report**.
|
||||
|
||||
Your report is filed as a new issue for that facility and the cleaning team is
|
||||
notified immediately. You'll see a confirmation message when it's received.
|
||||
|
||||
*[Screenshot: the "Report a Problem" form]*
|
||||
|
||||
---
|
||||
|
||||
## Issues — Who Is Handling It
|
||||
|
||||
*Suggested placement: within the existing "Issues" / "Logging an Issue" section.*
|
||||
|
||||
When you view an issue, it now shows **Handled By**, indicating who is
|
||||
responsible for resolving it:
|
||||
|
||||
- **Our Staff** — a member of the janitorial quality-control team.
|
||||
- **Facility Staff** — the facility's own on-site staff are handling it.
|
||||
- **External Vendor** — an outside contractor has been engaged.
|
||||
|
||||
In every case, one of our team members remains the **follow-up owner** who
|
||||
verifies the work and updates the issue. You do not set this field — it is
|
||||
managed by the JQC team — but it lets you see at a glance how your issue is
|
||||
being addressed.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Inspector (iPad) Manual — Update Note (July 2026)
|
||||
|
||||
> This note concerns **JQC_Inspector_Manual.docx** (the iPad / mobile app manual).
|
||||
|
||||
## No changes required this cycle
|
||||
|
||||
The features added in this round are **web-only** and are **not yet exposed in
|
||||
the mobile API**, so the iPad app's behavior — and therefore this manual — is
|
||||
unchanged:
|
||||
|
||||
- **Scheduled inspections** — created and executed through the web app. The iPad
|
||||
app has no scheduled-inspection list or "Start from schedule" action.
|
||||
- **Issue "Handled By" (Our Staff / Facility Staff / External Vendor)** — set and
|
||||
displayed on the web only. The mobile issue screens are unchanged.
|
||||
- **Facility QR codes / occupant report-a-problem** — a public web page; not part
|
||||
of the inspector app.
|
||||
|
||||
## If these come to the iPad later
|
||||
|
||||
When/if the mobile app is extended to cover them, the manual will need:
|
||||
|
||||
- A **Scheduled Inspections** screen (list of assigned schedules, due dates,
|
||||
"Start" to begin the inspection) — requires new `/api/v1` endpoints.
|
||||
- A **Handled By** field on the issue detail/edit screen — requires adding
|
||||
`handler_type` and the facility/vendor fields to the issue API payload and a
|
||||
PATCH endpoint.
|
||||
|
||||
No action needed now beyond noting that the web and iPad feature sets differ in
|
||||
these areas.
|
||||
@@ -0,0 +1,80 @@
|
||||
# Web Inspector Manual — New Sections (July 2026)
|
||||
|
||||
> Paste these sections into **JQC_Web_Inspector_Manual.docx**. Suggested
|
||||
> placement noted under each heading. Replace the *[Screenshot]* placeholders
|
||||
> with captures from your environment.
|
||||
|
||||
---
|
||||
|
||||
## Scheduled Inspections
|
||||
|
||||
*Suggested placement: as a new top-level section after "Starting an Inspection".*
|
||||
|
||||
Inspections can be **scheduled** in advance and assigned to you — either as a
|
||||
one-time visit or on a repeating schedule (daily, weekly, or monthly). You are
|
||||
notified when one is assigned to you and reminded as the due date approaches.
|
||||
|
||||
### Where to find your scheduled inspections
|
||||
|
||||
- **Dashboard** — the **Scheduled Inspections** panel lists what's coming up in
|
||||
the next 7 days and warns you about anything overdue.
|
||||
- **Inspections → Scheduled** — the full list of your assignments, with due
|
||||
dates. Overdue items are highlighted in red; items due soon in yellow.
|
||||
|
||||
*[Screenshot: dashboard Scheduled Inspections panel]*
|
||||
|
||||
### Notifications you'll receive
|
||||
|
||||
- **When assigned** — an in-app and email notification the moment a schedule is
|
||||
assigned (or reassigned) to you.
|
||||
- **The day before** the due date — a reminder.
|
||||
- **On the due date** — a reminder to complete it.
|
||||
|
||||
(Overdue items also alert management.)
|
||||
|
||||
### Completing a scheduled inspection
|
||||
|
||||
1. Open **Inspections → Scheduled** (or use the dashboard panel).
|
||||
2. Find the scheduled inspection and click **Start**.
|
||||
3. The inspection form opens, pre-set to the correct facility and template —
|
||||
fill it in and submit exactly like any other inspection.
|
||||
4. On submission, the schedule is automatically marked complete.
|
||||
- **One-time** schedules are then closed.
|
||||
- **Recurring** schedules automatically roll forward to the next due date —
|
||||
you don't need to recreate them.
|
||||
|
||||
*[Screenshot: Scheduled list with the Start button]*
|
||||
|
||||
> Note: Scheduled inspections are managed (created, edited, reassigned) by
|
||||
> administrators, directors, and project managers. As an inspector you see and
|
||||
> execute the ones assigned to you.
|
||||
|
||||
---
|
||||
|
||||
## Issues — Who Is Handling It ("Handled By")
|
||||
|
||||
*Suggested placement: within the existing "Managing Issues" / "Updating an
|
||||
Issue" section.*
|
||||
|
||||
Every issue now records **Handled By**, which separates who does the work from
|
||||
who follows up:
|
||||
|
||||
| Handled By | Meaning |
|
||||
|---|---|
|
||||
| **Our Staff** | A JQC team member resolves it. |
|
||||
| **Facility Staff** | The facility's own staff handle it (their contact is recorded on the issue). |
|
||||
| **External Vendor** | An outside contractor handles it (vendor details are recorded on the issue). |
|
||||
|
||||
Regardless of who does the work, **one of our staff is always the follow-up
|
||||
owner** — the person responsible for verifying the fix and updating the issue
|
||||
in the app. You will continue to follow up on and update the status of issues
|
||||
assigned to you even when facility staff or a vendor performs the actual work.
|
||||
|
||||
The **issues list** shows a **Facility** or **Vendor** badge on issues not
|
||||
handled by our own staff, and you can **filter** the list by "Handled By".
|
||||
|
||||
> Setting the Handled By category and the facility/vendor contact details is
|
||||
> done by administrators, directors, and project managers on the issue detail
|
||||
> page. Inspectors update status and add notes/photos as usual.
|
||||
|
||||
*[Screenshot: issue detail showing the Handled By badge and follow-up owner]*
|
||||
Reference in New Issue
Block a user