# 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://: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` | | `Dev` | `:dev` und `:v-dev` | | `beta` | `:beta` und `:v-beta` | | Git-Tag `v1.4.0` | genau `:v1.4.0` | Zusätzlich wird bei jedem Build immer auch `:` gepusht, für die exakte Nachverfolgung einzelner Builds. `` 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 # 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://:8081/m` z.B. `http://:8081/m1` - Produktionsleiter: `http://:8081/start` **Alte/lange URL-Form:** `http://:8081/m_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`. **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 `` 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`, alte Form `/m_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/`.