Updated documents

This commit is contained in:
2026-04-25 13:14:00 -04:00
parent e3e30f01e9
commit ff0650e1d9
2 changed files with 320 additions and 474 deletions
+44 -43
View File
@@ -7,13 +7,13 @@ 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
- **Issue Tracking** — Full lifecycle management (open → in-progress → pending verification → resolved) with SLA enforcement, follower subscriptions, and resolution photo uploads
- **Customer Portal** — Scoped facility visibility for client accounts with invitation-based onboarding (email link, 72-hour token)
- **Notification System** — In-app + email notifications driven by an admin-controlled routing matrix; per-user preferences and digest mode
- **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
- **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
- **Mobile API** — JWT-authenticated REST API for the companion React Native / Expo mobile application
- **Project Hierarchy** — Facilities grouped into Projects with optional Project Manager assignment and per-project customer access control
- **Mobile API** — JWT-authenticated REST API (Phase 7) for the companion React Native / Expo mobile application; rate-limited login and refresh endpoints
- **Contract Hierarchy** — Facilities grouped into Contracts (internally "Projects") with optional Contract Manager assignment and per-contract customer access control
---
@@ -26,6 +26,7 @@ A production-grade web application for managing janitorial service contracts, fa
| ORM / Migrations | SQLAlchemy + Alembic |
| Auth (web) | Flask-Login + Flask-WTF (CSRF) |
| Auth (API) | JWT + opaque refresh tokens |
| Rate limiting | Flask-Limiter |
| Email | Flask-Mail (SMTP) |
| PDF | ReportLab |
| Frontend | Bootstrap 5, Chart.js, Jinja2 |
@@ -37,7 +38,7 @@ A production-grade web application for managing janitorial service contracts, fa
## Prerequisites
- Python 3.11+
- MySQL 8.x
- MySQL 5.7+ (or 8.0+)
- A configured SMTP server (port 465 or 587)
---
@@ -89,7 +90,7 @@ APP_BASE_URL=https://your-domain.com
DIGEST_SECRET=<random-secret-for-cron-auth>
```
> **Email SSL:** Port 465 uses implicit SSL (`MAIL_USE_SSL=True`). Port 587 uses STARTTLS (`MAIL_USE_TLS=True`). The application auto-detects based on `MAIL_PORT` — never set both flags to True.
> **Email SSL:** Port 465 uses implicit SSL; port 587 uses STARTTLS. The application auto-detects based on `MAIL_PORT` — never set both flags to True.
### 5. Run database migrations
@@ -117,6 +118,8 @@ gunicorn -c gunicorn_config.py wsgi:app
The included `gunicorn_config.py` binds to `127.0.0.1:8000` with sync workers. Log files are written to `/home/jqc/logs/`.
> **Rate limiting note:** Flask-Limiter uses in-process memory storage by default. With multiple Gunicorn workers each process maintains its own counter. For accurate shared-state enforcement across workers, set `storage_uri = 'redis://localhost:6379'` in `app/__init__.py` and install Redis.
### Nginx (recommended configuration)
```nginx
@@ -163,31 +166,24 @@ WantedBy=multi-user.target
## Cron Jobs
Two background tasks require scheduled execution:
### SLA Alerts
Checks all open issues against their SLA window and dispatches notifications:
Four background tasks require scheduled execution. All endpoints that require a token use `DIGEST_SECRET`.
```bash
# Run every 30 minutes
*/30 * * * * /home/jqc/venv/bin/python -c "
from app import create_app
from app.utils.sla import send_sla_alerts
app = create_app('production')
with app.app_context():
send_sla_alerts()
"
```
# SLA alerts — every 30 minutes
*/30 * * * * curl -s -X POST "https://your-domain.com/notifications/check-sla" \
-d "token=YOUR_DIGEST_SECRET"
### Digest Emails and Scheduled Reports
# 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"
```bash
# Daily digest — run at 7:00 AM
0 7 * * * curl -s -X POST "https://your-domain.com/notifications/send-digest?frequency=daily&secret=YOUR_DIGEST_SECRET"
# Expired/revoked API token cleanup — 3:00 AM
0 3 * * * curl -s -X POST "https://your-domain.com/notifications/cleanup-tokens" \
-d "token=YOUR_DIGEST_SECRET"
# Scheduled reports
0 8 * * * curl -s -X POST "https://your-domain.com/scheduled-reports/run?secret=YOUR_DIGEST_SECRET"
# Scheduled report delivery — 8:00 AM
0 8 * * * curl -s -X POST "https://your-domain.com/scheduled-reports/run" \
-d "secret=YOUR_DIGEST_SECRET"
```
---
@@ -198,7 +194,7 @@ with app.app_context():
|---|---|
| **admin** | Full access to all features including Audit Trail and Notification Matrix |
| **director** | Broad access equivalent to admin, excluding Audit Trail and Notification Matrix |
| **project_manager** | Manages projects, facilities, and reports; cannot manage users or system settings |
| **project_manager** | Manages contracts, facilities, and reports; cannot manage users or system settings |
| **inspector** | Executes inspections and manages assigned issues |
| **customer** | Read-only portal scoped to assigned facilities; receives notifications on their facilities |
@@ -218,11 +214,13 @@ flask shell
## Customer Onboarding
1. Admin navigates to **Customers → Invite Customer**
2. Enter the customer's name and email address
3. The system auto-generates a username and sends an invitation email with a 72-hour setup link
4. Customer clicks the link, sets their password, and gains access to their scoped portal
5. Admin assigns the customer to one or more Projects/Facilities via **Customers → Manage Assignments**
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**
> Expired invitations (token past 72 hours, password never set) are surfaced as a warning banner on the Customers page with inline **Resend** buttons.
---
@@ -232,15 +230,15 @@ The REST API is available at `/api/v1/` and uses JWT Bearer token authentication
### Authentication Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | `/api/v1/auth/login` | Login; returns access + refresh tokens |
| POST | `/api/v1/auth/refresh` | Rotate refresh token; returns new access token |
| POST | `/api/v1/auth/logout` | Revoke refresh token |
| GET | `/api/v1/auth/me` | Return current user profile |
| POST | `/api/v1/devices/register` | Register APNs device token for push notifications |
| Method | Endpoint | Rate Limit | Description |
|---|---|---|---|
| POST | `/api/v1/auth/login` | 10/min | Login; returns access + refresh tokens |
| POST | `/api/v1/auth/refresh` | 30/min | Rotate refresh token; returns new access token |
| POST | `/api/v1/auth/logout` | — | Revoke refresh token |
| GET | `/api/v1/auth/me` | — | Return current user profile |
| POST | `/api/v1/devices/register` | — | Register APNs device token |
Access tokens expire after 60 minutes. Refresh tokens are valid for 30 days and rotate on every use.
Access tokens expire after 60 minutes. Refresh tokens are valid for 30 days and rotate on every use. Expired and revoked tokens are cleaned up automatically on each login and via a nightly cron job.
---
@@ -257,7 +255,10 @@ flask db migrate -m "description of change"
flask db downgrade
```
> **MySQL ENUM changes require three steps:** expand the ENUM to include both values, migrate existing data, then contract the ENUM. See `Claude.md` for the full protocol.
### MySQL Compatibility Notes
- **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.
---
@@ -283,7 +284,7 @@ flask db downgrade
| `MAIL_PASSWORD` | — | SMTP password |
| `MAIL_DEFAULT_SENDER` | `noreply@janitorialqc.local` | From address |
| `APP_BASE_URL` | `""` | Base URL for links in emails |
| `DIGEST_SECRET` | — | Token for authenticating cron requests |
| `DIGEST_SECRET` | — | Authenticates all cron endpoints |
| `MAX_CONTENT_LENGTH` | `50MB` | Maximum upload size per request |
---