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>
This commit is contained in:
ang3l12
2026-07-13 11:41:22 -06:00
commit dea316b113
111 changed files with 13817 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
"""Printable NCR PDF (WeasyPrint) — a clean single-document rendering of the
complete NCR for hard-copy travelers and audits.
WeasyPrint is imported lazily so environments without the Pango/Cairo system
libraries (e.g. unit tests) can still import the app.
"""
from functools import partial
from pathlib import Path
import anyio
from jinja2 import Environment, FileSystemLoader, select_autoescape
from app.domain import STAGE_LABELS, Stage
from app.models import Ncr
from app.models.base import utcnow
from app.services.storage import attachment_abs_path
_TEMPLATES_DIR = Path(__file__).resolve().parent.parent / "templates"
_env = Environment(
loader=FileSystemLoader(_TEMPLATES_DIR),
autoescape=select_autoescape(["html"]),
)
def _render_html(ncr: Ncr) -> str:
images = []
other_files = []
for att in ncr.attachments:
entry = {
"filename": att.original_filename,
"uploaded_by": att.uploaded_by.display_name,
"uploaded_at": att.uploaded_at,
"size_kb": max(1, att.size_bytes // 1024),
}
path = attachment_abs_path(att.stored_path)
if att.is_image and path.is_file():
entry["src"] = path.as_uri()
images.append(entry)
else:
other_files.append(entry)
template = _env.get_template("ncr_pdf.html")
return template.render(
ncr=ncr,
stage_label=STAGE_LABELS[Stage(ncr.stage)],
stage_labels=STAGE_LABELS,
Stage=Stage,
images=images,
other_files=other_files,
generated_at=utcnow(),
)
def _html_to_pdf(html: str) -> bytes:
from weasyprint import HTML # lazy: needs Pango/Cairo system libs
return HTML(string=html).write_pdf()
async def render_ncr_pdf(ncr: Ncr) -> bytes:
html = _render_html(ncr)
# WeasyPrint rendering is CPU-bound; keep it off the event loop.
return await anyio.to_thread.run_sync(partial(_html_to_pdf, html))