Files
LT_Janitorial_Quality_Control/README.md
T

293 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# JQC — Janitorial Quality Control System
A production-grade web application for managing janitorial service contracts, facility inspections, issue tracking, and client reporting.
---
## 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
- **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
---
## Tech Stack
| Layer | Technology |
|---|---|
| Backend | Python / Flask |
| Database | MySQL |
| ORM / Migrations | SQLAlchemy + Alembic |
| Auth (web) | Flask-Login + Flask-WTF (CSRF) |
| Auth (API) | JWT + opaque refresh tokens |
| Email | Flask-Mail (SMTP) |
| PDF | ReportLab |
| Frontend | Bootstrap 5, Chart.js, Jinja2 |
| Server | Gunicorn + Nginx |
| Mobile | React Native + Expo |
---
## Prerequisites
- Python 3.11+
- MySQL 8.x
- A configured SMTP server (port 465 or 587)
---
## Installation
### 1. Clone and create a virtual environment
```bash
git clone <repo-url> jqc
cd jqc
python -m venv venv
source venv/bin/activate # Linux / macOS
# venv\Scripts\activate # Windows
```
### 2. Install dependencies
```bash
pip install -r requirements.txt
```
### 3. Create the database
```sql
CREATE DATABASE jqc CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'jqc'@'localhost' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON jqc.* TO 'jqc'@'localhost';
FLUSH PRIVILEGES;
```
### 4. Configure environment variables
Create a `.env` file in the project root (never commit this file):
```dotenv
SECRET_KEY=<long-random-string>
DATABASE_URL=mysql+pymysql://jqc:your_password@localhost/jqc
# Email
MAIL_SERVER=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=noreply@example.com
MAIL_PASSWORD=smtp_password
MAIL_DEFAULT_SENDER=noreply@example.com
# Application
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.
### 5. Run database migrations
```bash
flask db upgrade
```
### 6. Start the development server
```bash
python run.py
```
The application will be available at `http://localhost:5000`.
---
## Production Deployment
### Gunicorn
```bash
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/`.
### Nginx (recommended configuration)
```nginx
server {
listen 443 ssl;
server_name your-domain.com;
client_max_body_size 50M; # must match MAX_CONTENT_LENGTH in config.py
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /static/ {
alias /path/to/jqc/app/static/;
expires 30d;
}
}
```
### Systemd service (example)
```ini
[Unit]
Description=JQC Gunicorn
After=network.target mysql.service
[Service]
User=jqc
WorkingDirectory=/home/jqc/lt_janitorial_quality_control
EnvironmentFile=/home/jqc/.env
ExecStart=/home/jqc/venv/bin/gunicorn -c gunicorn_config.py wsgi:app
Restart=on-failure
[Install]
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:
```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()
"
```
### Digest Emails and Scheduled Reports
```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"
# Scheduled reports
0 8 * * * curl -s -X POST "https://your-domain.com/scheduled-reports/run?secret=YOUR_DIGEST_SECRET"
```
---
## User Roles
| Role | Description |
|---|---|
| **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 |
| **inspector** | Executes inspections and manages assigned issues |
| **customer** | Read-only portal scoped to assigned facilities; receives notifications on their facilities |
### Creating the First Admin Account
```bash
flask shell
>>> from app import db
>>> from app.models.user import User
>>> u = User(username='admin', email='admin@example.com', role='admin')
>>> u.set_password('your-secure-password')
>>> db.session.add(u)
>>> db.session.commit()
```
---
## 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**
---
## Mobile API
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 |
Access tokens expire after 60 minutes. Refresh tokens are valid for 30 days and rotate on every use.
---
## Database Migrations
```bash
# Apply all pending migrations
flask db upgrade
# Create a new migration after model changes
flask db migrate -m "description of change"
# Roll back one migration
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.
---
## Application Logs
| Log File | Contents |
|---|---|
| `logs/jqc.log` | Application log (rotating, 5 × 5 MB) |
| `/home/jqc/logs/gunicorn-error.log` | Gunicorn worker errors |
| `/home/jqc/logs/gunicorn-access.log` | HTTP access log |
---
## Configuration Reference
| Variable | Default | Description |
|---|---|---|
| `SECRET_KEY` | — *required* | Flask session signing key |
| `DATABASE_URL` | — *required* | SQLAlchemy connection URI |
| `MAIL_SERVER` | — | SMTP hostname |
| `MAIL_PORT` | `587` | SMTP port (465 = SSL, 587 = STARTTLS) |
| `MAIL_USERNAME` | — | SMTP username |
| `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 |
| `MAX_CONTENT_LENGTH` | `50MB` | Maximum upload size per request |
---
## License
See `LICENSE` for terms.