SPA_AfterTemperRejection/__init__.py
Erik c340aef2c3 feat: scaffold Ausschuss-Station reject-booking page-module (temper_rejects)
Vue 3 + Vite 8 + TS + PrimeVue 4 (Nora/Industrial) + Pinia + vue-i18n SPA, served
by the SPRO gateway as a static page-module under /module/temper_rejects. A central,
login-free reject-booking terminal for all presses' tempered carts.

- Implements the LOCKED design (memory: ausschuss-station): per-ttId list (newest
  first), left press checkbox filter (default all, greyed when no carts, hit-count
  badge), style-C 5-station temper belt per entry (badges + animated conveyor),
  12h/1T/7T/14T/30T window, touch density. Booking dialog = PrimeVue Dialog +
  InputNumber + reason Select. Colours match PressV + Ausschuss #C4162A.
- Python page-module wrapper: register(app) mounts committed dist/ + a no-auth API
  (GET /overview, GET /reasons, POST /reject). _db.py exposes run_select_query +
  transaction() for the future INSERT.
- Backend returns SAMPLE data and POST /reject echoes success WITHOUT writing;
  frontend falls back to bundled sample so `npm run dev` is fully demoable offline.
  TODOs point at the real temper_tracking reads + the rejects INSERT (needs a DBA
  rejTtId column) + the login-free-write security open question.
- Rich README bakes in the design, contract, wiring, open questions + Grafana embed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 20:44:22 +02:00

51 lines
2.2 KiB
Python

"""temper_rejects — the Ausschuss-Station page-module for the SPRO gateway.
A central, **login-free** reject-booking terminal for ALL presses' tempered
carts (Werk Cadolzburg). Mounted into the SPRO Universal Gateway as a git
submodule at ``<gateway>/modules/temper_rejects/`` and discovered by the kernel
module loader (``app.core.modules.load_modules``), which imports this package and
calls ``register(app)``.
It does two things:
* mounts the built Vue SPA (``dist/``) as static files under ``/module/temper_rejects``;
* exposes a small no-auth JSON API under ``/module/temper_rejects/api`` — reads
(tempered carts per ``ttId`` + reject reasons) AND one narrow write
(``POST /reject`` → one row in ``production.rejects``).
No login / device context by design — this is a central shop-floor terminal. The
existing device-scoped ``POST /press/rejects/send`` derives the press from the
device JWT, which is useless for a multi-press terminal; hence the un-scoped
variant here, which takes ``press`` + ``ttId`` from the request body. Non-sensitive
shop-floor data. Security posture (network/reverse-proxy vs. a static token) is an
open question — see this repo's README + ``.claude/memory/ausschuss-station.md``.
See ``agent_docs/vue-page-modules.md`` in the gateway repo for the module runbook.
"""
from __future__ import annotations
from pathlib import Path
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
MODULE_NAME = "temper_rejects"
# MUST match vite.config.ts `base` (with a trailing slash on the Vite side).
MODULE_PREFIX = "/module/temper_rejects"
def register(app: FastAPI) -> None:
"""Mount the data API and the SPA onto the gateway app.
API routes are registered FIRST — a ``StaticFiles`` mount captures everything
under its prefix, so the ``/api`` routes must exist before the mount or they
get shadowed. ``dist/`` MUST exist (it is committed) or this raises and the
loader logs-and-skips the module (kernel stays up, page 404s).
"""
from .api import routes
app.include_router(routes.router)
dist = Path(__file__).resolve().parent / "dist"
app.mount(MODULE_PREFIX, StaticFiles(directory=dist, html=True), name=MODULE_NAME)