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:
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.sqlanpassen/ersetzen, oder - den
ens-db-Service indocker-compose.ymlentfernen und stattdessen überDB_HOST/DB_PORT/DB_NAME/DB_USER/DB_PASSWORDbeiens-appauf deine bestehende Datenbank zeigen.
Achtung, bitte prüfen:
ens-dbbindet aktuell als Volume das Verzeichnis/var/lib/docker/volumes/postgresql_ens_dataein. 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ürens-dbverwenden.
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:
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:
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:
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, schreibtstart-Eintrag inMDE - 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
- Pflicht-Dropdown Abweichungsgrund, Server validiert verbindlich vor
dem Schreiben in
- 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 aufabgeschlossen/aktiv=falsegesetzt
- Prod. erfassen: Menge (numerisch, ≥0, ≥ bisherige Menge, ≤110% Soll)
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 nur offen/pausiert (umschaltbar auf "Alle"), optional nach
Maschine gefiltert, löschbar und (mit Einschränkungen) bearbeitbar:
- Status und Priorität sind hier nicht editierbar (nur informativ angezeigt) – Status ändert sich ausschließlich über die eigentlichen Maschinen-Aktionen (Start/Pause/Beenden), Priorität ausschließlich über die Drag & Drop-Seite "Auftragspriorität".
- Aktive Aufträge werden angezeigt, können aber nicht bearbeitet werden – ein Bearbeitungsversuch (auch ein direkter POST) wird mit einer Fehlermeldung 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 UmgebungsvariableMASCHINEN_PASSWORTindocker-compose.ymlkonfigurierbar (unbedingt vom Platzhalter auf einen echten Wert ändern!). Für die Login-Session wird zusätzlich einSECRET_KEYbenö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/.