277 lines
14 KiB
Markdown
277 lines
14 KiB
Markdown
# WDM Performance – Produktionserfassung für ~40 Maschinen
|
||
|
||
Flask-Backend mit **serverseitigem Rendering** (Jinja2-Templates, normale
|
||
HTML-Formulare, Post-Redirect-Get) – **kein Client-JavaScript**, genau wie
|
||
beim Essensportal-Projekt. Postgres-Datenbank.
|
||
|
||
## Start
|
||
|
||
Es gibt bewusst **keine `.env`-Datei** – alle Einstellungen (DB-Zugangsdaten,
|
||
Port, `SECRET_KEY`, `MASCHINEN_PASSWORT`) stehen direkt in `docker-compose.yml`.
|
||
Vor dem ersten Start dort alle `BITTE-AENDERN-...`-Platzhalter durch echte
|
||
Werte ersetzen (Datenbank-Passwort bei `ens-db` und `ens-app` muss identisch
|
||
sein), dann:
|
||
|
||
```bash
|
||
docker compose up -d --build
|
||
```
|
||
|
||
Die Anwendung ist danach unter `http://<server>:8081/` erreichbar (Port
|
||
direkt im `ports:`-Abschnitt von `docker-compose.yml` änderbar).
|
||
|
||
**Wichtig zur DB-Initialisierung:** `build/db/init.sql` (= dein hochgeladenes Schema,
|
||
ergänzt um ein paar Beispiel-Abweichungsgründe) wird **nur beim allerersten
|
||
Start** ausgeführt, wenn das Datenvolume noch leer ist. Hast du bereits eine
|
||
bestehende Postgres-Datenbank mit Daten, dann:
|
||
|
||
- entweder das Datenverzeichnis vor dem ersten Start leer lassen und
|
||
`init.sql` anpassen/ersetzen, **oder**
|
||
- den `ens-db`-Service in `docker-compose.yml` entfernen und stattdessen über
|
||
`DB_HOST` / `DB_PORT` / `DB_NAME` / `DB_USER` / `DB_PASSWORD` bei `ens-app`
|
||
auf deine bestehende Datenbank zeigen.
|
||
|
||
> **Achtung, bitte prüfen:** `ens-db` bindet aktuell als Volume das Verzeichnis
|
||
> `/var/lib/docker/volumes/postgresql_ens_data` ein. Laut eurer eigenen
|
||
> System-Doku ist genau das der Datenbankpfad des *bestehenden* Budibase-
|
||
> Systems. Läuft Budibase parallel mit seiner eigenen Postgres-Instanz auf
|
||
> demselben Host, würden zwei unabhängige Postgres-Prozesse gleichzeitig auf
|
||
> dasselbe Datenverzeichnis schreiben – das kann das Datenverzeichnis
|
||
> beschädigen. Bitte kurz gegenprüfen, ob das so beabsichtigt ist (z.B. weil
|
||
> Budibase dort inzwischen abgelöst ist) oder ob hier versehentlich der
|
||
> falsche Pfad übernommen wurde; im Zweifel einen eigenen, neuen Pfad/Volume
|
||
> für `ens-db` verwenden.
|
||
|
||
## Bauen und Verteilen als Container
|
||
|
||
Bei jedem Push auf `main`, `Dev`, `beta` oder einen `v*`-Tag baut die
|
||
Gitea-Actions-Pipeline (`.gitea/workflows/build.yml`) automatisch ein Image
|
||
und pusht es in die Gitea-Registry. Welche Tags dabei entstehen, hängt vom
|
||
Branch ab:
|
||
|
||
| Push auf … | Image-Tags |
|
||
|--------------------|--------------------------------------|
|
||
| `main` | `:latest` und `:v<VERSION>` |
|
||
| `Dev` | `:dev` und `:v<VERSION>-dev` |
|
||
| `beta` | `:beta` und `:v<VERSION>-beta` |
|
||
| Git-Tag `v1.4.0` | genau `:v1.4.0` |
|
||
|
||
Zusätzlich wird bei jedem Build immer auch `:<commit-sha>` gepusht, für die
|
||
exakte Nachverfolgung einzelner Builds.
|
||
|
||
`<VERSION>` kommt aus der Datei `VERSION` im Repo-Root (aktuell `1.0.0`).
|
||
Vor einem Release dort einfach die Zahl erhöhen und committen – die Pipeline
|
||
liest den Wert automatisch bei jedem Build neu ein, es ist keine weitere
|
||
Konfiguration nötig.
|
||
|
||
Für einen **anderen Server**, der nur das fertige Image ziehen und selbst
|
||
nichts bauen soll, gibt es `docker-compose.deploy.yml`: sie referenziert das
|
||
Image direkt aus der Registry (`image: git.thiede-brauer.de/bryan.hoffmann/wdm:latest`,
|
||
kein `build:`). Auf diesem Server reicht allein diese eine Datei – der Rest
|
||
des Repos wird dort nicht benötigt. Statt `:latest` kann dort auch gezielt
|
||
ein Versions-Tag (z.B. `:v1.4.0`) oder `:dev`/`:beta` eingetragen werden, um
|
||
eine bestimmte Version bzw. einen bestimmten Entwicklungsstand zu deployen:
|
||
|
||
```bash
|
||
docker login git.thiede-brauer.de -u <benutzername> # falls die Registry nicht öffentlich lesbar ist
|
||
docker compose -f docker-compose.deploy.yml up -d
|
||
```
|
||
|
||
Update auf eine neue Version:
|
||
|
||
```bash
|
||
docker compose -f docker-compose.deploy.yml pull
|
||
docker compose -f docker-compose.deploy.yml up -d
|
||
```
|
||
|
||
Auch hier gilt: keine `.env`, alle Werte direkt in `docker-compose.deploy.yml`
|
||
eintragen (dieselben Werte wie beim Bauen, insbesondere das DB-Passwort).
|
||
|
||
## Aufruf der einzelnen Seiten
|
||
|
||
- Maschine N (1–40): `http://<server>:8081/m<N>`
|
||
z.B. `http://<server>:8081/m1`
|
||
- Produktionsleiter: `http://<server>:8081/start`
|
||
|
||
**Alte/lange URL-Form:** `http://<server>:8081/m<N>_m_view_main` (z.B.
|
||
`.../m1_m_view_main`) funktioniert unverändert weiter und zeigt auf
|
||
dieselbe Seite. Sie bleibt bewusst zusätzlich zur kurzen Form erhalten,
|
||
damit bereits auf Tablets als Kiosk-Startseite/Lesezeichen hinterlegte
|
||
URLs nicht angepasst werden müssen. Für neu eingerichtete Geräte reicht
|
||
die kurze Form `/m<N>`.
|
||
|
||
**Hinweis zum ursprünglich gewünschten `#/...`-Format:** Ein Hash-Fragment
|
||
(`#/...`) wird nie an den Server geschickt und funktioniert nur mit
|
||
Client-JavaScript, das per Definition entfällt (kein JS). Deshalb sind die
|
||
Seiten stattdessen unter echten Server-Pfaden mit denselben Bezeichnern
|
||
erreichbar (ohne `#`).
|
||
|
||
Die Maschinennummer `<N>` entspricht der `id` in der Tabelle `Maschinen`.
|
||
Für Tablets an den Maschinen einfach jeweils die passende URL als
|
||
Startseite/Kiosk-Link einrichten.
|
||
|
||
**Reverse Proxy:** Falls du die App hinter einem Unterpfad (z.B.
|
||
`/app/wdm-performance/`) betreiben willst, muss der Proxy diesen Präfix vor
|
||
Weiterleitung an den Container **entfernen** (strip prefix), da das
|
||
Backend mit wurzelrelativen Pfaden arbeitet. Beispiel nginx:
|
||
|
||
```nginx
|
||
location /app/wdm-performance/ {
|
||
proxy_pass http://wdm-app:5000/;
|
||
}
|
||
```
|
||
|
||
## Funktionsumfang
|
||
|
||
### Maschinenseite (`/m<N>`, alte Form `/m<N>_m_view_main` weiterhin gültig)
|
||
- Überschrift = Maschinenname aus der DB
|
||
- Kein aktiver Auftrag → **Start**-Button → Auftragsauswahl (offene/pausierte
|
||
Aufträge dieser Maschine, sortiert nach Priorität/`sid`) → Auswahl markiert
|
||
Auftrag als aktiv, schreibt `start`-Eintrag in `MDE`
|
||
- Aktiver Auftrag → Start-Button ausgeblendet, stattdessen:
|
||
- **Prod. erfassen**: Menge (numerisch, ≥0, ≥ bisherige Menge, ≤110% Soll)
|
||
+ Pflicht-Dropdown Abweichungsgrund, Server validiert verbindlich vor
|
||
dem Schreiben in `MDE`
|
||
- **Auftrag pausieren**: gleiche Eingabemaske, setzt Auftrag auf
|
||
`status=pause`, `aktiv=false`
|
||
- **Auftrag beenden**: bei Menge < Soll erscheint eine große
|
||
Bestätigungsabfrage; nach Bestätigung wird die Sollmenge als produziert
|
||
in `MDE` (`status=beendet`) vermerkt, Auftrag auf `abgeschlossen` /
|
||
`aktiv=false` gesetzt
|
||
|
||
### Produktionsleiter (`#/start`)
|
||
- **Auftrag anlegen**: Maschine (Dropdown), Auftragsnummer, optional Artikel,
|
||
Produktionsmenge, Stück/Stunde sowie optional eine **Priorität** (Position
|
||
in der Warteschlange dieser Maschine, 1 = als nächstes dran) – alles
|
||
serverseitig validiert. Wird eine Priorität angegeben, wird der Auftrag an
|
||
genau dieser Position eingereiht und alle bestehenden offenen/pausierten
|
||
Aufträge dieser Maschine rücken automatisch nach (dichte Nummerierung
|
||
1..N, kein bloßes Setzen eines rohen Zahlenwerts). Ohne Angabe wird der
|
||
Auftrag ans Ende der Warteschlange gehängt.
|
||
- **Aufträge importieren** (`/start_import`): Aufträge aus einer Excel-
|
||
(.xlsx) oder CSV-Datei per Drag & Drop oder Dateiauswahl bulk-anlegen,
|
||
siehe eigener Abschnitt unten
|
||
- **Auftragsübersicht**: alle Aufträge (inkl. Artikel, Maschinenname),
|
||
standardmäßig offen/pausiert **und aktiv** (umschaltbar auf "Alle", zeigt
|
||
dann zusätzlich auch abgeschlossene Aufträge), optional nach Maschine
|
||
gefiltert, löschbar und (mit Einschränkungen) bearbeitbar:
|
||
- **Status ist hier manuell änderbar** (z.B. um einen versehentlich
|
||
abgeschlossenen Auftrag wieder auf "Offen" zu setzen) – **"Aktiv" ist als
|
||
Zielwert bewusst ausgeschlossen**, das passiert ausschließlich über die
|
||
eigentlichen Maschinen-Aktionen (Start/Pause/Beenden). **Priorität bleibt
|
||
weiterhin nicht editierbar**, dafür gibt es die Drag & Drop-Seite
|
||
"Auftragspriorität".
|
||
- **Aktive Aufträge werden angezeigt, können aber weder bearbeitet noch
|
||
gelöscht werden** – Bearbeiten- und Löschen-Button werden für sie in der
|
||
Liste durch einen Hinweistext ersetzt; ein direkter POST-Versuch (am UI
|
||
vorbei) wird serverseitig ebenfalls abgelehnt, solange der Auftrag gerade
|
||
an der Maschine läuft.
|
||
- Editierbar bleiben Maschine (inkl. automatischem Umhängen der kompletten
|
||
MDE-Buchungshistorie auf die neue Maschine), Auftragsnummer, Artikel,
|
||
Produktionsmenge und Stück/Stunde.
|
||
- **Mengen-Schutz**: Wurde an einem Auftrag bereits eine Menge gemeldet
|
||
(MDE-Historie vorhanden), darf die Produktionsmenge nicht mehr so weit
|
||
reduziert werden, dass die bereits gemeldete Menge außerhalb der
|
||
110%-Toleranz läge (dieselbe Toleranzregel, die auch an der Maschine
|
||
beim Erfassen gilt) – die App zeigt dabei den konkret noch zulässigen
|
||
Mindestwert an.
|
||
- **Buchungskorrektur**: direkter Eingriff in die `MDE`-Tabelle (editieren/
|
||
löschen), optional nach Maschine gefiltert
|
||
- **Auftragspriorität**: Reihenfolge der offenen/pausierten Aufträge je
|
||
Maschine per **Drag & Drop** ändern (Auftrag anfassen und an neue Position
|
||
ziehen) – wird automatisch gespeichert. Funktioniert am PC/Laptop mit
|
||
Maus; auf reinen Touch-Geräten ohne Maus ggf. eingeschränkt (Hinweis dazu
|
||
steht auf der Seite selbst).
|
||
- **Zu Maschine springen**: Direktlinks zu jeder Maschinenseite (zeigt an,
|
||
ob die Maschine gerade läuft)
|
||
- **Maschinenverwaltung** (`/start_maschinen_verwaltung`, passwortgeschützt):
|
||
Maschinen anlegen, umbenennen/Standort ändern, löschen (nur möglich wenn
|
||
keine Aufträge mehr an der Maschine hängen). Passwort über die
|
||
Umgebungsvariable `MASCHINEN_PASSWORT` in `docker-compose.yml` konfigurierbar
|
||
(unbedingt vom Platzhalter auf einen echten Wert ändern!). Für die
|
||
Login-Session wird zusätzlich ein `SECRET_KEY` benötigt (ebenfalls dort
|
||
gesetzt).
|
||
|
||
### Aufträge importieren (`/start_import`)
|
||
Excel- (.xlsx) oder CSV-Datei mit (mindestens) diesen Spalten hochladen
|
||
(Reihenfolge egal, Erkennung anhand von Stichworten in der Kopfzeile). Die
|
||
Datei kann per Drag & Drop auf das gestrichelte Feld gezogen oder klassisch
|
||
über die Dateiauswahl ausgewählt werden. Bei CSV werden Zeichenkodierung
|
||
(UTF-8/BOM, sonst cp1252), Trennzeichen (Komma, Semikolon – wie bei
|
||
deutschem Excel-Export üblich – oder Tab) sowie deutsches/internationales
|
||
Zahlformat (`1.234,56` oder `1234.56`) automatisch erkannt.
|
||
|
||
| Spalte | Verwendung |
|
||
|-------------------|---------------------------------------------------------|
|
||
| Maschine | muss exakt (Groß-/Kleinschreibung egal) einer bestehenden Maschine entsprechen, sonst wird die Zeile verworfen |
|
||
| man. Prio | wird direkt als Priorität (`sid`) übernommen (das Feld erlaubt auch Kommazahlen, z.B. `15.1`) |
|
||
| FA.-Nr. | → Auftragsnummer. Existiert diese FA.-Nr. bereits irgendwo im System, wird die Zeile unangetastet übersprungen |
|
||
| Rest-Menge | zusammen mit Ist-Menge → Produktionsmenge (Soll) = Rest + Ist |
|
||
| Ist-Menge | wird als bereits produzierte Menge übernommen (Start-Buchung in `MDE`), damit Fortschritt und Mengen-Validierung an der Maschine korrekt weiterlaufen |
|
||
| Artikel | wird 1:1 übernommen und angezeigt |
|
||
| Soll-pro-Stunde | → Stück/Stunde (Soll) |
|
||
| Bezeichnung, Auftrag, Lieferdatum, Bemerkung | werden ignoriert |
|
||
|
||
Ist die Rest-Menge einer Zeile `0`, wird der Auftrag direkt mit Status
|
||
"Abgeschlossen" angelegt (inkl. passender Buchung in der MDE-Historie).
|
||
Nach dem Import zeigt die Seite eine Liste, was importiert, was wegen
|
||
unbekannter Maschine verworfen und was wegen bereits vorhandener FA.-Nr.
|
||
übersprungen wurde.
|
||
|
||
### Artikel
|
||
Neues, optionales Textfeld je Auftrag (Spalte `artikel`). Kann beim
|
||
manuellen Anlegen eingetragen werden oder kommt aus dem Import; wird auf der
|
||
Maschinenseite, in der Auftragsauswahl, der Auftragsübersicht, der
|
||
Prioritätsliste und (sofern gerade ein Auftrag aktiv ist) auch in der
|
||
Maschinenübersicht ("Zu Maschine springen") mit angezeigt.
|
||
|
||
### Logo / Icon
|
||
`build/static/logo.svg` wird als Favicon und über dem Seitentitel angezeigt.
|
||
Die Darstellung ist proportional (feste Höhe 130px, Breite skaliert automatisch
|
||
im echten Seitenverhältnis mit) – funktioniert also auch mit einem breiten,
|
||
nicht-quadratischen Logo. Einfach durch eine eigene Datei mit demselben Namen
|
||
(`logo.svg`) ersetzen, um das Firmenlogo einzubinden – kein Code muss dafür
|
||
angepasst werden.
|
||
|
||
### JavaScript-Einsatz (Ausnahmen vom "kein Client-JS"-Grundsatz)
|
||
Zwei bewusste, punktuelle Ausnahmen von der sonst reinen Server-Side-
|
||
Rendering-Philosophie:
|
||
- Die Filter-Dropdowns (Maschine/Status) in Auftragsübersicht, Buchungs-
|
||
korrektur und Auftragspriorität lösen die Filterung direkt beim Auswählen
|
||
aus (`onchange`-Submit) – reiner Komfort, der "Filtern"-Button bleibt als
|
||
Fallback sichtbar und funktioniert auch ohne JavaScript.
|
||
- Die Drag-&-Drop-Sortierung auf der Auftragspriorität-Seite benötigt
|
||
zwingend JavaScript (ohne JS erscheint dort ein Hinweis, dass Sortieren
|
||
nicht möglich ist – alle anderen Seiten funktionieren weiterhin ohne JS).
|
||
|
||
## Stabilität im Dauerbetrieb
|
||
|
||
- Backend läuft unter `gunicorn` (mehrere Worker/Threads statt Flask-Devserver)
|
||
- Datenbank-Connection-Pool mit automatischem Retry beim Start (verträgt
|
||
verzögerten DB-Start ohne Absturz)
|
||
- Docker-Healthchecks für App und DB, `restart: unless-stopped`
|
||
- Reines Server-Side-Rendering ohne Build-Step und ohne Client-JavaScript –
|
||
jede Aktion ist ein klassischer Formular-Submit (Post-Redirect-Get), das
|
||
ist auf Tablets mit älteren/eingeschränkten Browsern besonders robust
|
||
|
||
## Struktur
|
||
|
||
```
|
||
wdm/
|
||
├── Dockerfile Build-Anweisungen (Kontext = Projektwurzel)
|
||
├── docker-compose.yml lokal bauen/testen (build: .)
|
||
├── docker-compose.deploy.yml nur Image ziehen (für andere Server)
|
||
├── .gitea/workflows/build.yml CI: baut Image (branch-/versionsabhängig getaggt) und pusht in die Gitea-Registry
|
||
├── VERSION aktuelle Versionsnummer (z.B. "1.0.0"), von der CI-Pipeline gelesen
|
||
├── README.md
|
||
└── build/
|
||
├── app.py Flask-Anwendung (alle Routen, Server-Side-Rendering)
|
||
├── db.py DB-Pool mit Retry-Logik
|
||
├── requirements.txt
|
||
├── db/init.sql Schema (dein Dump + Beispiel-Abweichungsgründe)
|
||
├── templates/ Jinja2-HTML-Templates (kein JavaScript)
|
||
└── static/
|
||
└── style.css Großflächiges Touch-Design
|
||
```
|
||
|
||
Wichtig: `docker compose` muss aus dem Projektwurzel-Verzeichnis heraus
|
||
aufgerufen werden (dort, wo `docker-compose.yml` liegt), nicht aus `build/`. |