Files
bryan.hoffmann 455f513f6e
Build und Push Docker Image / build-and-push (push) Successful in 11s
Auftragsübersicht anpassen
2026-08-27 11:52:24 +02:00

277 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 (140): `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/`.