Initial commit: PESCO NCR system
Complete Non-Conformance Report system replacing the PowerApps/SharePoint
prototype: FastAPI + SQLAlchemy 2 (async) + Alembic + MySQL 8 backend,
React 18 + Vite + TypeScript + MUI frontend, Entra ID auth (MSAL / JWKS,
group-gated), Microsoft Graph delegated Mail.Send notifications (OBO),
six-stage workflow state machine with server-side enforcement, atomic
NCR-YYYY-NNNN numbering, attachments with camera capture, immutable
field-level audit trail, admin reopen, reports + CSV export, WeasyPrint
PDF traveler, Power BI reporting views + read-only DB user, documented
VISUAL ERP job-lookup stub, pytest suite (26 tests), docker-compose
deployment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 11:41:22 -06:00
|
|
|
name: pesco-ncr
|
|
|
|
|
|
|
|
|
|
services:
|
|
|
|
|
mysql:
|
2026-07-13 13:49:50 -06:00
|
|
|
# mysql:8.4 (Oracle Linux 9) requires an x86-64-v2 CPU. On hosts/VMs
|
|
|
|
|
# without it ("Fatal glibc error: CPU does not support x86-64-v2"),
|
|
|
|
|
# set MYSQL_IMAGE=mysql:8.0-debian in .env (baseline x86-64 build).
|
|
|
|
|
image: ${MYSQL_IMAGE:-mysql:8.4}
|
Initial commit: PESCO NCR system
Complete Non-Conformance Report system replacing the PowerApps/SharePoint
prototype: FastAPI + SQLAlchemy 2 (async) + Alembic + MySQL 8 backend,
React 18 + Vite + TypeScript + MUI frontend, Entra ID auth (MSAL / JWKS,
group-gated), Microsoft Graph delegated Mail.Send notifications (OBO),
six-stage workflow state machine with server-side enforcement, atomic
NCR-YYYY-NNNN numbering, attachments with camera capture, immutable
field-level audit trail, admin reopen, reports + CSV export, WeasyPrint
PDF traveler, Power BI reporting views + read-only DB user, documented
VISUAL ERP job-lookup stub, pytest suite (26 tests), docker-compose
deployment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 11:41:22 -06:00
|
|
|
command:
|
|
|
|
|
- --character-set-server=utf8mb4
|
|
|
|
|
- --collation-server=utf8mb4_unicode_ci
|
|
|
|
|
environment:
|
|
|
|
|
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
|
|
|
|
|
MYSQL_DATABASE: ${MYSQL_DATABASE:-pesco_ncr}
|
|
|
|
|
MYSQL_USER: ${MYSQL_USER:-ncr_app}
|
|
|
|
|
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
|
|
|
|
POWERBI_RO_PASSWORD: ${POWERBI_RO_PASSWORD}
|
|
|
|
|
ports:
|
|
|
|
|
# Published so the on-prem Power BI gateway can reach the reporting views.
|
|
|
|
|
# Remove or firewall this mapping if external reporting access is not needed.
|
|
|
|
|
- "${MYSQL_PUBLISHED_PORT:-3306}:3306"
|
|
|
|
|
volumes:
|
|
|
|
|
- mysql_data:/var/lib/mysql
|
|
|
|
|
- ./db/init:/docker-entrypoint-initdb.d:ro
|
|
|
|
|
healthcheck:
|
|
|
|
|
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -u root -p$$MYSQL_ROOT_PASSWORD --silent"]
|
|
|
|
|
interval: 5s
|
|
|
|
|
timeout: 5s
|
|
|
|
|
retries: 30
|
|
|
|
|
start_period: 40s
|
|
|
|
|
restart: unless-stopped
|
|
|
|
|
|
|
|
|
|
api:
|
|
|
|
|
build: ./backend
|
|
|
|
|
environment:
|
|
|
|
|
APP_BASE_URL: ${APP_BASE_URL:-http://localhost:8080}
|
|
|
|
|
LOG_LEVEL: ${LOG_LEVEL:-INFO}
|
|
|
|
|
AUTH_MODE: ${AUTH_MODE:-entra}
|
|
|
|
|
ENTRA_TENANT_ID: ${ENTRA_TENANT_ID:-}
|
|
|
|
|
ENTRA_CLIENT_ID: ${ENTRA_CLIENT_ID:-}
|
|
|
|
|
ENTRA_CLIENT_SECRET: ${ENTRA_CLIENT_SECRET:-}
|
|
|
|
|
ENTRA_ALLOWED_GROUP_ID: ${ENTRA_ALLOWED_GROUP_ID:-}
|
|
|
|
|
ENTRA_API_AUDIENCE: ${ENTRA_API_AUDIENCE:-}
|
|
|
|
|
INITIAL_ADMIN_EMAILS: ${INITIAL_ADMIN_EMAILS:-}
|
|
|
|
|
MYSQL_HOST: mysql
|
|
|
|
|
MYSQL_PORT: 3306
|
|
|
|
|
MYSQL_DATABASE: ${MYSQL_DATABASE:-pesco_ncr}
|
|
|
|
|
MYSQL_USER: ${MYSQL_USER:-ncr_app}
|
|
|
|
|
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
|
|
|
|
ATTACHMENTS_DIR: ${ATTACHMENTS_DIR:-/data/attachments}
|
|
|
|
|
MAX_UPLOAD_MB: ${MAX_UPLOAD_MB:-25}
|
|
|
|
|
NOTIFICATIONS_ENABLED_DEFAULT: ${NOTIFICATIONS_ENABLED_DEFAULT:-true}
|
|
|
|
|
JOB_LOOKUP_PROVIDER: ${JOB_LOOKUP_PROVIDER:-null}
|
|
|
|
|
VISUAL_DB_HOST: ${VISUAL_DB_HOST:-}
|
|
|
|
|
VISUAL_DB_PORT: ${VISUAL_DB_PORT:-1433}
|
|
|
|
|
VISUAL_DB_NAME: ${VISUAL_DB_NAME:-}
|
|
|
|
|
VISUAL_DB_USER: ${VISUAL_DB_USER:-}
|
|
|
|
|
VISUAL_DB_PASSWORD: ${VISUAL_DB_PASSWORD:-}
|
|
|
|
|
VISUAL_SITE_ID: ${VISUAL_SITE_ID:-}
|
|
|
|
|
SEED_DEMO_DATA: ${SEED_DEMO_DATA:-false}
|
|
|
|
|
volumes:
|
|
|
|
|
- attachments_data:/data/attachments
|
|
|
|
|
depends_on:
|
|
|
|
|
mysql:
|
|
|
|
|
condition: service_healthy
|
|
|
|
|
healthcheck:
|
|
|
|
|
test: ["CMD", "curl", "-fsS", "http://localhost:8000/api/health"]
|
|
|
|
|
interval: 10s
|
|
|
|
|
timeout: 5s
|
|
|
|
|
retries: 12
|
|
|
|
|
start_period: 30s
|
|
|
|
|
restart: unless-stopped
|
|
|
|
|
|
|
|
|
|
frontend:
|
|
|
|
|
build: ./frontend
|
|
|
|
|
ports:
|
|
|
|
|
- "${HTTP_PORT:-8080}:80"
|
|
|
|
|
environment:
|
|
|
|
|
AUTH_MODE: ${AUTH_MODE:-entra}
|
|
|
|
|
ENTRA_TENANT_ID: ${ENTRA_TENANT_ID:-}
|
|
|
|
|
ENTRA_CLIENT_ID: ${ENTRA_CLIENT_ID:-}
|
|
|
|
|
ENTRA_API_SCOPE: ${ENTRA_API_SCOPE:-}
|
|
|
|
|
depends_on:
|
|
|
|
|
api:
|
|
|
|
|
condition: service_healthy
|
|
|
|
|
restart: unless-stopped
|
|
|
|
|
|
|
|
|
|
volumes:
|
|
|
|
|
mysql_data:
|
|
|
|
|
attachments_data:
|