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>
152 lines
7.1 KiB
Markdown
152 lines
7.1 KiB
Markdown
# 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)
|
|
|
|
```bash
|
|
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`.
|
|
|
|
```bash
|
|
# 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`](src/types/station.ts); Python
|
|
mirrors them in [`api/routes.py`](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 }`.
|
|
|
|
```jsonc
|
|
// 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_tracking` across **all** presses (un-scoped),
|
|
one row per `ttId`, newest first by `ttOutOfOvenAt` (`ttStatus='done'`); join
|
|
`active_orders`/`finished_orders` for the article. Add each order's 5-station
|
|
distribution — reuse `modules/fastpress/press/temper.py::_temper_aggregates_all`
|
|
(notYetSent / sentToTemper / onWayToOven / inOven / finishedTempering). Filter by
|
|
`window` on `ttOutOfOvenAt`.
|
|
- **reasons:** `SELECT id, reason_de, reason_en FROM settings.reject_reasons WHERE active=1`.
|
|
- **reject:** one `INSERT` into `production.rejects` inside a `transaction()` — same
|
|
shape as `modules/fastpress/press/rejects.py::send_reject`. Booking references the
|
|
cart, which needs an **additive nullable `rejTtId` column** (a DBA applies it; the
|
|
runtime account has no `ALTER`). The device-scoped `POST /press/rejects/send`
|
|
can't be reused — it derives the press from the device JWT; here `press`+`ttId`
|
|
come from the body.
|
|
|
|
### Open questions before go-live (from the design memory)
|
|
|
|
1. **Security of the login-free write:** network/reverse-proxy restriction only, or a
|
|
static `STATION_TOKEN` in `.env`? (No TLS/reverse-proxy exists yet.)
|
|
2. Confirm the `rejTtId` column name + that per-`ttId` booking is desired (yes, per spec).
|
|
3. 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. `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
|
|
|
|
```html
|
|
<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.
|