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> |
||
|---|---|---|
| api | ||
| dist | ||
| src | ||
| __init__.py | ||
| _db.py | ||
| .gitignore | ||
| .npmrc.example | ||
| env.d.ts | ||
| index.html | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tailwind.config.cjs | ||
| tsconfig.app.json | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| vite.config.ts | ||
SPA_AfterTemperRejection — Ausschuss-Station
A central, login-free reject-booking terminal for all presses' tempered
carts (Werk Cadolzburg). A Vue 3 SPA that the SPRO Universal Gateway serves as
a static page-module under /module/temper_rejects/. It lists every tempered cart
(one row per ttId, newest first), shows each order's temper flow, and lets a
shop-floor operator book an Ausschuss (count + reason) against a specific cart.
Standalone orientation. Developed on its own (sibling repo, like
FastPress/PressV), mounted into the gateway as a submodule atmodules/temper_rejects/. This README bakes in everything a fresh session needs.
Design is LOCKED (see .claude/memory/ausschuss-station.md in the gateway repo):
per-ttId list · press checkbox filter on the left (default all, greyed when a
press has no carts in the window, hit-count badge) · style-C 5-station temper
belt under each entry (number badges + animated conveyor) · time window
12h/1T/7T/14T/30T. Touch-density controls (56px). Run npm run dev to see it.
Quick start (standalone dev)
cp .npmrc.example .npmrc # paste a Gitea PAT with "package: Read" scope (one-time)
npm install
npm run dev # http://localhost:5173/module/temper_rejects/
With no gateway backend, the store falls back to bundled sample data
(src/data/sample.ts) and the booking flow simulates success — so the whole
terminal (filter, belt, dialog) is demoable offline (a DEMO-DATEN tag shows).
npm run build → dist/ (committed).
How it's wired into the gateway (page-module contract)
Per agent_docs/vue-page-modules.md. The repo root is a Python package
(__init__.py) so the loader can import temper_rejects and call register(app):
| File | Role |
|---|---|
__init__.py |
register(app) — mounts dist/ at MODULE_PREFIX + includes the API router |
_db.py |
kernel DB seam — run_select_query (reads) and transaction() (the reject write) |
api/routes.py |
no-auth API: GET /overview, GET /reasons, POST /reject |
dist/ |
committed build output — the gateway serves this |
URL prefix in two places that MUST match: vite.config.ts →
base: '/module/temper_rejects/' and __init__.py →
MODULE_PREFIX = "/module/temper_rejects". The frontend derives its API base from
import.meta.env.BASE_URL.
# in the gateway repo (fastAPI)
git submodule add https://git.sprodat.eu/Erik/SPA_AfterTemperRejection.git modules/temper_rejects
# MODULES_ENABLED must include temper_rejects (or be empty = load all)
Restart → log shows loaded module: temper_rejects. Smoke:
http://localhost:8800/module/temper_rejects/ and …/api/overview. Deploy:
npm run build, commit dist/, bump the submodule pin. Never build on the host.
Data API contract
Types are authoritative in src/types/station.ts; Python
mirrors them in api/routes.py.
GET /module/temper_rejects/api/overview?window=12h|1T|7T|14T|30T→ tempered carts (perttId, newest first) + the press list for the filter.GET /module/temper_rejects/api/reasons→ active reject reasons (id,reason_de,reason_en).POST /module/temper_rejects/api/reject→ body{ press, orderNo, ttId, count, reason }.
// overview
{ "window":"7T","generatedAt":"…","sample":true,
"presses":["P1","P2","P3","P4","P5","P6","P7","P8","P9"],
"entries":[{ "ttId":10231,"cartId":"00A17226","press":"P5","orderNo":"O-4455",
"article":"Gehäuse 12 mm","partsGood":60,"scrap":2,"doneAt":"…",
"flow":{ "notYetSent":120,"wagen":60,"unterwegs":40,"ofen":72,"fertig":260 } }] }
The flow object is the order-level 5-station distribution shown on the belt.
Belt colour mapping (matches PressV process colours): notYetSent→violet,
wagen→cyan, unterwegs→amber, ofen→orange, fertig→emerald; the reject accent
is Grafana dark-red #C4162A.
Wiring the REAL data (currently SAMPLE)
api/routes.py returns sample data and POST /reject echoes success without
writing. To make it live (see the TODO there + .claude/memory/ausschuss-station.md):
from .._db import run_select_query, transaction.- overview: read
production.temper_trackingacross all presses (un-scoped), one row perttId, newest first byttOutOfOvenAt(ttStatus='done'); joinactive_orders/finished_ordersfor the article. Add each order's 5-station distribution — reusemodules/fastpress/press/temper.py::_temper_aggregates_all(notYetSent / sentToTemper / onWayToOven / inOven / finishedTempering). Filter bywindowonttOutOfOvenAt. - reasons:
SELECT id, reason_de, reason_en FROM settings.reject_reasons WHERE active=1. - reject: one
INSERTintoproduction.rejectsinside atransaction()— same shape asmodules/fastpress/press/rejects.py::send_reject. Booking references the cart, which needs an additive nullablerejTtIdcolumn (a DBA applies it; the runtime account has noALTER). The device-scopedPOST /press/rejects/sendcan't be reused — it derives the press from the device JWT; herepress+ttIdcome from the body.
Open questions before go-live (from the design memory)
- Security of the login-free write: network/reverse-proxy restriction only, or a
static
STATION_TOKENin.env? (No TLS/reverse-proxy exists yet.) - Confirm the
rejTtIdcolumn name + that per-ttIdbooking is desired (yes, per spec). - Belt semantics: order-level distribution (chosen) vs. the cart's own journey position.
Tech stack (matches PressV / the fleet)
Vue 3.5 · Vite 8 · TS 5.9 · Pinia 3 · vue-router 4 (hash) · vue-i18n 10 (de default,
?lang=en) · PrimeVue 4.5 (Nora + Industrial preset — the booking dialog uses
Dialog/Select/InputNumber/Button) · Tailwind v4 · @sp-ui-kit/tokens ·
Inter Variable. Dark-first, data-density="touch" (56px controls), flat 2px
corners, reduced-motion disables the conveyor.
Store: src/stores/temperRejects.ts — hydrate() (overview + reasons) →
refresh() → startPolling(20000); press filter state (checkedPresses, default
all) + submitReject() (posts, then refreshes; simulates success with no backend).
Falls back to sample data when a response lacks an entries[] array.
Not used yet but available in the fleet:
@sp-ui-kit/ui-core(e.g.SpNumpadDialogwould be an even more touch-friendly count entry than the PrimeVueInputNumber).
Design tokens
Flow/reject accents (src/style.css): nicht-gesendet #a78bfa, Wagen #22d3ee,
Unterwegs #fbbf24, Ofen #f97316 (hot #ef4444), Fertig #34d399, Ausschuss
#C4162A — :root dark + html:not(.dark) light variants.
Grafana embedding
<iframe src="http://<gateway-host>:8800/module/temper_rejects/#/"
width="100%" height="900" style="border:0"></iframe>
(?lang=en before the # for English.) Gateway sets no frame headers, so it embeds
directly. Because this terminal writes, resolve open question #1 (security)
before exposing it beyond a trusted LAN.