Go to file
2026-07-09 10:57:30 +00:00
api perf(db): batch the good-parts N+1 + cache the press/reason reference reads 2026-07-09 12:06:49 +02:00
dist fix(spa): reconnect spinner on connection loss, no demo data in prod 2026-07-09 09:20:34 +02:00
src fix(spa): reconnect spinner on connection loss, no demo data in prod 2026-07-09 09:20:34 +02:00
__init__.py feat: scaffold Ausschuss-Station reject-booking page-module (temper_rejects) 2026-07-01 20:44:22 +02:00
_db.py perf(db): batch the good-parts N+1 + cache the press/reason reference reads 2026-07-09 12:06:49 +02:00
.gitignore feat: wire live data + fail-safe reject booking 2026-07-02 13:46:56 +02:00
.npmrc.example feat: scaffold Ausschuss-Station reject-booking page-module (temper_rejects) 2026-07-01 20:44:22 +02:00
env.d.ts feat: scaffold Ausschuss-Station reject-booking page-module (temper_rejects) 2026-07-01 20:44:22 +02:00
index.html feat: scaffold Ausschuss-Station reject-booking page-module (temper_rejects) 2026-07-01 20:44:22 +02:00
package.json chore: use yarn (fleet standard) + declare vite devDependency 2026-07-02 09:06:22 +02:00
README.md docs: README — data is wired to live MariaDB + reject writes (was "currently SAMPLE") 2026-07-06 11:14:35 +02:00
tailwind.config.cjs feat: scaffold Ausschuss-Station reject-booking page-module (temper_rejects) 2026-07-01 20:44:22 +02:00
tsconfig.app.json feat: scaffold Ausschuss-Station reject-booking page-module (temper_rejects) 2026-07-01 20:44:22 +02:00
tsconfig.json feat: scaffold Ausschuss-Station reject-booking page-module (temper_rejects) 2026-07-01 20:44:22 +02:00
tsconfig.node.json feat: scaffold Ausschuss-Station reject-booking page-module (temper_rejects) 2026-07-01 20:44:22 +02:00
vite.config.ts feat: scaffold Ausschuss-Station reject-booking page-module (temper_rejects) 2026-07-01 20:44:22 +02:00
yarn.lock chore: use yarn (fleet standard) + declare vite devDependency 2026-07-02 09:06:22 +02:00

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 at modules/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)

Package manager is yarn 1.22.22 (pinned via packageManager; matches PressV). corepack provides it.

corepack enable               # one-time per machine (honours the pinned yarn)
cp .npmrc.example .npmrc      # paste a Gitea PAT with "package: Read" scope (yarn v1 reads .npmrc)
yarn install
yarn 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). yarn builddist/ (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.tsbase: '/module/temper_rejects/' and __init__.pyMODULE_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: yarn 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 (per ttId, 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.

Live data wiring

api/routes.py reads live MariaDB and POST /reject writes (details in the gateway memory .claude/memory/ausschuss-station.md; sample:true only in the standalone dev fallback). Via from .._db import run_select_query, transaction:

  • overview: reads production.temper_tracking across all presses (un-scoped), one row per ttId, newest-first by ttOutOfOvenAt (ttStatus='done'), joined to active_orders/finished_orders, windowed on ttOutOfOvenAt. The order-level 5-station distribution reuses modules/fastpress/press/temper.py::_temper_aggregates_all.
  • reasons: SELECT id, reason_de, reason_en FROM settings.reject_reasons WHERE active=1.
  • reject: one INSERT into production.rejects inside a transaction() (an un-scoped reuse of PressV send_reject), keyed by rejOrdernumber+rejPress from the body — the device-scoped POST /press/rejects/send can't be reused (it derives the press from the device JWT). ⚠ /reject returns HTTP 200 + {success:false} on failure (not 4xx/5xx — else the frontend store swallows it into a fake success).

Note: a nullable rejTtId column was later added (fastpress migration 009) so rejects attribute to the correct press cycle — see the gateway memory temper-cycle-reject-attribution.md.

Open question before go-live

Security of the login-free write: network/reverse-proxy restriction only, or a static STATION_TOKEN in .env? (No TLS/reverse-proxy exists yet.) Because this terminal writes, resolve this before exposing it beyond a trusted LAN. (Resolved: per-ttId booking is desired; belt shows the order-level distribution.)


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.tshydrate() (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. SpNumpadDialog would be an even more touch-friendly count entry than the PrimeVue InputNumber).

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.