Phase 10 — Support Ticket System:
- tickets.py router: SLA status (on_track/at_risk/breached/responded), email
notifications on create+reply via background threads, school_name in list,
priority filter, bulk-close endpoint
- tasks/tickets.py: escalate_stale Celery task (48h→high, 72h no reply→urgent)
- worker.py: escalate_stale scheduled every hour
- templates/email/ticket_notification.html: HTML ticket notification email
- TicketsPage.vue: status tabs, SLA badge, priority badge, school name column,
checkbox bulk-close, pagination
- TicketDetailPage.vue: inline priority/status/assignee selectors, SLA timer,
internal note lock icon, closed-ticket guard
Phase 11 — Monthly Report Generation:
- models/report.py: MonthlyReport + SchoolMonthlyStats ORM models
- tasks/reports.py: send_monthly_reports enhanced with SMS stats, attendance
data, invoice summary, stores MonthlyReport record per school per month
Phase 12 — On-Prem Monthly Report Pull:
- tasks/reports.py: pull_monthly_stats task — httpx GET to each school's
hub_base_url, upserts SchoolMonthlyStats; runs 1st at 5am
- worker.py: pull_monthly_stats scheduled 1st at 5am
Phase 13 — Feature Flags + Suspension:
- models/school.py: hub_base_url, feature_overrides (JSON), onboarding_completed_at
- routers/schools.py: PUT /{id}/feature-overrides endpoint
- routers/sync.py: _tier_features() merges school.feature_overrides into poll config
Phase 14 — Onboarding Wizard + Welcome Email:
- tasks/onboarding.py: send_welcome_email Celery task with license key
- routers/schools.py: auto-trigger welcome email on POST /schools,
POST /{id}/activate (status→active + onboarding_completed_at),
POST /{id}/resend-welcome
Phase 15 — UX Polish + Ops Tools:
- routers/search.py: GET /api/search?q= (schools + invoices + tickets, 5 each)
- routers/audit.py: GET /api/audit-logs (paginated, filterable)
- AppLayout.vue: global search bar with debounced dropdown, result navigation
- AuditLogsPage.vue: new page with filter + pagination
- AppSidebar.vue: Audit Logs nav item added
- router/index.ts: /audit-logs route
- api.ts: globalSearch, getAuditLogs, activateSchool, resendWelcomeEmail,
updateFeatureOverrides, bulkCloseTickets
Deployment:
- docker-compose.yml: x-backend-env anchor (DRY), PDF_DIR env var,
seed service (one-shot python seed.py on first boot)
- migrations/003_phases11_15.py: monthly_reports, school_monthly_stats tables
+ schools hub_base_url/feature_overrides/onboarding_completed_at columns
113 lines
2.3 KiB
YAML
113 lines
2.3 KiB
YAML
version: "3.9"
|
|
|
|
x-backend-env: &backend-env
|
|
DATABASE_URL: postgresql+asyncpg://postgres:postgres@db:5432/taptrack_hub
|
|
REDIS_URL: redis://redis:6379/0
|
|
SECRET_KEY: changeme-in-production-use-openssl-rand-hex-32
|
|
ENVIRONMENT: development
|
|
SEMAPHORE_API_KEY: ""
|
|
SMTP_HOST: ""
|
|
SMTP_PORT: "587"
|
|
SMTP_USER: ""
|
|
SMTP_PASSWORD: ""
|
|
SMTP_FROM: "noreply@taptrack.io"
|
|
HUB_BASE_URL: "http://localhost:8090"
|
|
PDF_DIR: "/app/data/invoices"
|
|
|
|
services:
|
|
db:
|
|
image: postgres:15-alpine
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_DB: taptrack_hub
|
|
POSTGRES_USER: postgres
|
|
POSTGRES_PASSWORD: postgres
|
|
volumes:
|
|
- db_data:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 10
|
|
|
|
redis:
|
|
image: redis:7-alpine
|
|
restart: unless-stopped
|
|
volumes:
|
|
- redis_data:/data
|
|
|
|
backend:
|
|
build: ./backend
|
|
restart: unless-stopped
|
|
environment:
|
|
<<: *backend-env
|
|
volumes:
|
|
- ./backend:/app
|
|
- backend_data:/app/data
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_started
|
|
|
|
# One-shot seed service — runs seed.py then exits
|
|
seed:
|
|
build: ./backend
|
|
command: python seed.py
|
|
environment:
|
|
<<: *backend-env
|
|
volumes:
|
|
- ./backend:/app
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
restart: "no"
|
|
|
|
celery:
|
|
build: ./backend
|
|
command: celery -A app.worker worker --loglevel=info --concurrency=2
|
|
restart: unless-stopped
|
|
environment:
|
|
<<: *backend-env
|
|
volumes:
|
|
- ./backend:/app
|
|
- backend_data:/app/data
|
|
depends_on:
|
|
- db
|
|
- redis
|
|
|
|
celery-beat:
|
|
build: ./backend
|
|
command: celery -A app.worker beat --loglevel=info
|
|
restart: unless-stopped
|
|
environment:
|
|
<<: *backend-env
|
|
volumes:
|
|
- ./backend:/app
|
|
- backend_data:/app/data
|
|
depends_on:
|
|
- db
|
|
- redis
|
|
|
|
frontend:
|
|
build: ./frontend
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- backend
|
|
|
|
nginx:
|
|
image: nginx:alpine
|
|
restart: unless-stopped
|
|
ports:
|
|
- "8090:80"
|
|
volumes:
|
|
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
|
depends_on:
|
|
- backend
|
|
- frontend
|
|
|
|
volumes:
|
|
db_data:
|
|
redis_data:
|
|
backend_data:
|