- JavaScript 62.7%
- HTML 25.8%
- CSS 10.7%
- Python 0.5%
- Shell 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
Some checks are pending
Tests / test (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
| .agents | ||
| .ai_brain | ||
| .forgejo/workflows | ||
| assets | ||
| deploy | ||
| docs | ||
| scripts | ||
| tests | ||
| .gitattributes | ||
| .gitignore | ||
| APP_REVIEW_2026-07-17.html | ||
| auth-client.js | ||
| auth.css | ||
| bar.html | ||
| bestellungen.pdf | ||
| CHANGELOG.md | ||
| checkout-operation.js | ||
| CHEF_UEBERSICHT.html | ||
| coaster-rounds.js | ||
| coaster-transfer.js | ||
| diagnostic-shift-log.js | ||
| favicon.ico | ||
| favicon.svg | ||
| finish-diagnostics.js | ||
| index.html | ||
| nachbon.js | ||
| offline-outbox.js | ||
| offline-start.js | ||
| offline.html | ||
| order-insights.js | ||
| order-operation.js | ||
| oven-store.js | ||
| package-lock.json | ||
| package.json | ||
| pickup-store.js | ||
| pickups.js | ||
| printer-queue.js | ||
| printer-ticket-layout.js | ||
| product-catalog.js | ||
| products.js | ||
| pub-tables.js | ||
| quick-sales.js | ||
| README.md | ||
| receipt-archive.js | ||
| receipt-request.js | ||
| receipt-tax.js | ||
| reservations-store.js | ||
| reservations.js | ||
| ROADMAP.md | ||
| schedule.css | ||
| schedule.js | ||
| server.js | ||
| sqlite-isolated-transaction.js | ||
| sqlite-transaction-queue.js | ||
| start-screen.css | ||
| station-admin.js | ||
| station-audio.js | ||
| station-diagnostics.js | ||
| station-routing.js | ||
| station-workspace.js | ||
| style.css | ||
| supplier-order-store.js | ||
| supplier-orders.js | ||
| sw.js | ||
| theke.html | ||
| theme-shell.js | ||
| ticket-validation.js | ||
| timesheet.css | ||
| timesheet.js | ||
| touch-controls.js | ||
Willy's Pub – Kassen- & Bar-System
Vier nachgewiesene Entwurfsfehler behoben (2026-09-28)
Neue Deckeländerungen werden vor Versand gesichert; abgelaufene Entwürfe bleiben ohne
automatische Übertragung zur Prüfung erhalten. Fehlgeschlagene Konfliktsicherung meldet
keinen falschen Erfolg und bewahrt vorhandene Kopien. Erfolgreich übertragene Entwürfe
erscheinen sofort auf dem Gerät. Ausschließlich Service-Entwurfspfad in index.html
geändert; keine Server-, Datenbank-, Bestell-, Zahlungs- oder Ofenregeländerung.
736 Tests, sechs Entwurfs- und sechs bestehende Offline-Browserfälle sowie der bestehende
Bestell-/UI-Browserlauf bestanden. Nachweis, Grenzen und Übernahme.
Noch nicht auf dem Pi installiert. Geräteabnahme in ROADMAP.
Verbleibende Entwurfsverluste isoliert reproduziert (2026-09-28)
Auf Nutzerwunsch ausschließlich Diagnose, keine Appänderung: drei Verlustfälle bei noch ungesendeten Deckeländerungen reproduziert, drei Vergleichsfälle erhalten die Daten. Zusätzlich falsche leere Anzeige nach erfolgreich gespeichertem Offline-Entwurf; erneutes Laden zeigt die Position. Zwei vollständige Chromium-Läufe auf synthetischer DB. Befunde und genaue Grenzen. Der damalige Diagnoseauftrag änderte die App nicht. Die anschließend beauftragte Reparatur ist oben dokumentiert; auf dem Pi noch nicht installiert.
Freigegebene Altbons abgeschlossen (2026-09-28)
Für genau die drei geprüften Bons aus DdaUgl ist die Statuskorrektur vom Betreiber
freigegeben. Separates Werkzeug scripts/close-reviewed-tickets.js: standardmäßig
nur lesende Vorprüfung, mit --apply geprüfter vollständiger SQLite-Snapshot und
atomare Änderung ausschließlich von tickets.status. Keine App-Laufzeitänderung.
15 gezielte Tests einschließlich echter Server-/Socketprüfung bestanden.
Auf dem Pi ausgeführt: genau drei Bons korrigiert, geprüfte Sicherung erstellt, PM2
neu gestartet. Nachbericht kwC33Y: null offene Bons, Integrität/FK/Zuordnungen OK,
sämtliche Tabellenzeilenzahlen unverändert. Keine erneute Korrektur nötig.
Nachweis, Geräteprüfung weiterhin in ROADMAP.
Produktionsbestand ausgewertet (2026-09-28)
Die Pi-Berichte PeeKpM und DdaUgl bestätigen die geprüfte SQLite-Integrität und
Zuordnungen ohne Befund. Keine ungesendeten Serverpositionen oder Ofenbelegungen.
Die damals drei offenen Altbons stammen vom 11., 13. und 19.09.2026: jeweils Zahlungsnachweis
und Barabschluss vorhanden, Gesamtstatus trotzdem open. Nach heutigem Routing
benötigen die exportierten Artikel ausschließlich die Bar. Dieser Statuswiderspruch
wird von den Strukturprüfungen nicht erfasst; seine historische Ursache ist nicht belegt.
Die freigegebene Statuskorrektur ist inzwischen ausgeführt und nachgeprüft (siehe oben).
Details im Pi-Bericht.
Für die gezielte Sichtung: node scripts/check-db-readonly.js ./pub.db --open-tickets.
Nur lesend; zusätzlich höchstens 20 offene Bons mit Tisch-/Gastbezeichnung, Artikeln,
Mengen und Stationsstatus im Bericht. Keine Notizen oder Mitarbeiterdaten.
Keine produktive App-Änderung und kein Neustart nötig.
Produktive Datenbank lesend prüfen (2026-09-28)
In einer Betriebspause bei weiterlaufendem PM2: node scripts/check-db-readonly.js ./pub.db.
Expliziter Pfad, OPEN_READONLY, query_only und gemeinsame Lesetransaktion; keine
Appinitialisierung, Migration, Bereinigung oder neue Abhängigkeit. Bericht unter
/tmp/pub-db-pruefung-*/bericht.json mit Integrität/Fremdschlüsseln, JSON-Formaten,
ausgewählten Zuordnungen und Bestandszahlen. Geräteentwürfe sind separat zu prüfen.
Pi-Befehle als Text, Details in Backup/Betrieb.
Offline-Versand im Browser geprüft (2026-09-28)
Sechs echte Chromium-Ausfallfälle mit isolierter SQLite-Datenbank bestanden: Verbindung vor Versand, verzögerte Serverantwort, verlorene Bestätigung mit Serverneustart, 15-Minuten-Grenze, Artikelerfassung bei gesperrtem Browsernetz und neue Offline-Tischrunde. Zusätzlich 51 gezielte Regressionen bestanden. Nur Testwerkzeug und Dokumentation ergänzt; keine App- oder Datenbankänderung, kein produktiver Neustart für diesen Prüfschritt nötig. Nachweis und Grenzen, Geräteablauf als Text; offene Abnahme ausschließlich ROADMAP.
Kombinierter Pi-Lastlauf bestanden (2026-09-28)
Der vierte kombinierte Lauf (819cc5e, Archiv ODvXdx) ist auf dem Pub-Pi vollständig
bestanden und ausgewertet: 140 Runden in 910 s, 2.380 Getränke, 2.240 Ofenportionen,
700 Zahlungen, parallele Fertigmeldungs-/Archivleser und Neustart. Keine unerwarteten
Server- oder Readfehler; Mengen und Umsatz stimmen. Bestellung p95 840 ms/max. 1.992 ms.
Der erste Altbestandsabgleich braucht weiterhin 6,04 s, beantwortet das erste Gerät
aber nach 3,01 s mit der vorgesehenen Warteantwort; danach vollständige Daten.
Messwerte und Grenzen im Pi-Messbericht.
Der Lasttest selbst hat die produktive App nicht neu gestartet. Der produktive Neustart nach Pull ist inzwischen im Rahmen der Altbonkorrektur bestätigt (PID 75669). Service/Bar/Theke vollständig neu laden und echte Grundabläufe prüfen; diese Geräteabnahme steht weiterhin ausschließlich in ROADMAP. Kein weiterer Neustart für diese Dokumentation.
Lange Erstabfragen rechtzeitig beantworten (2026-09-28)
Der dritte kombinierte Pi-Lauf benötigt beim ersten Fertigmeldungsabgleich noch
6,07 s. Die bisherigen Optimierungen verkürzen die Arbeit, garantieren aber keine
Antwortreserve für jeden Altbestand. Deshalb erhält jetzt auch das zuerst anfragende
Gerät nach drei Sekunden die bereits unterstützte Rückmeldung PICKUPS_BUSY.
Der eine laufende Abgleich behält seine Sperre bis zum Ende; Wiederholungen starten
keinen zweiten. Nach Erfolg fordert pickups_changed zum frischen Laden auf.
Keine Teilantworten oder doppelten ACKs; späte Fehler werden gezählt und protokolliert.
Das begrenzt die Wartezeit auf eine Rückmeldung, nicht die Dauer des vollständigen Erstabgleichs. Die Anzeige kann dabei weiterhin einige Sekunden zum Abgleich brauchen. Bestell-/Abholregeln und die 6-Sekunden-Clientfrist bleiben gleich. Der erneute Pi-Lauf ist inzwischen bestanden (siehe oben). Produktiver Server inzwischen neu gestartet (siehe oben).
Fertigmeldungsabgleich mit Altbestand beschleunigt (2026-09-28)
Die ersten beiden kombinierten Pi-Läufe überschritten beim Laden der Fertigmeldungen die 6-Sekunden-Frist. Der zweite Lauf misst 8,69 s trotz Cursorindex und gebündelter Ablaufwrites. Deshalb werden jetzt auch die Lesepakete der Fertigmeldungsansicht auf höchstens 1.024 Kandidaten erweitert: 12 statt 94 Reads bei 12.000 Altmeldungen. Gezielte Abhol-/Übergabeaktionen behalten ihre kleinen Lesezugriffe. Anzeigezeit, Übergaben, Abholung und Schutz vor veralteten Änderungen bleiben gleich. Keine Meldungen werden gelöscht oder als abgeholt markiert.
Der bestandene Lauf ODvXdx prüft die neue Version in seiner separaten Datenbank;
PM2 lief dabei weiter. Der spätere produktive Neustart ist inzwischen bestätigt.
Der Index wird beim Start automatisch additiv angelegt; keine manuelle
Indexanlage und kein npm ci für diese Änderung erforderlich.
Messbefund und Grenzen: Pi-Messbericht.
Geführte Prüfung auf dem Pub-Pi (2026-09-28)
Erste Hardware-/Speichermessungen und genaue Testgrenzen stehen im
Pi-Messbericht. In einer Betriebspause startet
node scripts/check-pi-soak.js einen isolierten 15-Minuten-Konsistenztest mit
automatischem Diagnosearchiv. Produktive App und Datenbank bleiben unverändert;
es entsteht zusätzliche CPU-/Speicherlast auf dem Pi. Kein npm ci oder
Serverneustart zum Einspielen dieses reinen Testwerkzeugs erforderlich.
Der Zusatz mixed startet den Mischtest mit Ofenbelegung, Warteschlange, Zahlungen
und konkurrierenden Ofenaktionen. Neue Testdatenbanken liegen in einem eigenen
Ordner unter ~/pub-testdaten-*, um auf dem Pi die SD-Karte statt /tmp-RAM zu prüfen.
Beide 15-Minuten-Läufe sind inzwischen auf dem Pi bestanden und anhand der Archive
ausgewertet. Der Abdeckungsstand der gesamten App trennt
Entwicklungsprüfungen, Pi-Lastnachweise und Gerätebeobachtungen; nächste Abnahmen in ROADMAP.
node scripts/check-pi-soak.js combined erweitert den Mischlauf um gleichzeitig
laufende Fertigmeldungs- und Archivabfragen mit Altbestand, Mango-Übergaben, Undo,
Deckelkonflikte, Rückruf und Sofortverkauf/Nachbon. Umfang, Mengen, Antwortfristen und
erwartete Fehler stehen im kombinierten Lastprofil.
Auch hierfür bleiben PM2 und die produktive Datenbank unverändert.
Gesicherte Entwürfe sichtbar prüfen und erledigen (2026-09-27)
„Deckel prüfen“ öffnet eine eigene Ansicht mit lokal gesichertem Entwurf, Sicherungszeit und aktuellem Deckel. Auch geschlossene Tische und leere Entwürfe werden ausdrücklich angezeigt. Das Fenster folgt der Gestaltung von Abrechnung und Umbuchen, mit Tischname über dem Titel, einheitlichen Positionszeilen und großen Aktionsschaltflächen. Nach dem Abgleich kann „Entwurf erledigt“ → „Entwurf entfernen“ genau diese lokale Sicherung entfernen. Andere Entwürfe, offene Übertragungen und Serverbestellungen bleiben erhalten; bei ausstehenden Übertragungen für denselben Deckel ist das Entfernen gesperrt. „Später prüfen“ und „Schließen“ behalten den Entwurf. Mehrere Sicherungen werden nacheinander angeboten; auf unterschiedlichen Geräten müssen deren lokale Sicherungen einzeln geprüft werden.
Nur Clientänderung: git pull, anschließend Service, Bar und Theke vollständig neu laden.
Keine Datenbankbereinigung, Migration oder neue Abhängigkeit für diese Korrektur.
Technischer Audit und Betriebsabsicherung (2026-09-27)
Der Auditbericht beschreibt Befunde, Ursachen, Reparaturen, Tests und die Grenzen der Prüfung für Service, Bar und Theke gemeinsam. Deckelbereinigung, Rückruf, Ofenzuordnung bei Umbuchungen und Stationsabschluss bleiben bei Schreibfehlern konsistent. Rückrufe werden dauerhaft nachgewiesen. Späte Ofenaktionen können keine neu eingelegte Speise abschließen. Schema-/Restorefehler verhindern einen unsicheren Start; beschädigte Ofendaten werden zur Wiederherstellung erhalten.
Bewirtungsbelege trennen Speisen (7 %) und Getränke (19 %) positionsbezogen; gemischte Belege weisen beide Steuergruppen aus. Checkout-/Archivbeträge werden in Cent berechnet, Steuersätze mit den bezahlten Positionen gespeichert. Alte Archive bleiben unverändert. „Diverses Getränk“ ist weiter nur für Getränke gedacht. Quellen und Beispiele im Auditbericht.
Einspielen in einer Betriebspause: geprüfte DB-Sicherung, npm ci, Serverneustart und
vollständiger Reload aller drei Module (auth-client.js/station-diagnostics.js
20260927-2). Das alte numerische Ofenabschlussprotokoll wird abgelehnt. Additive
Schemaänderungen: ticket_recalls und vier Indizes; keine neue Laufzeitabhängigkeit.
In Produktion sind abgeschaltete Authentifizierung und fehlendes TLS jetzt Startfehler.
Druckqueues sind begrenzt; deren Annahme ist weiterhin keine Papierbestätigung. Offene Betriebsabnahmen und verbleibende Risiken stehen ausschließlich in ROADMAP.md.
Weitere Absicherung ohne geänderte Fachabläufe (2026-09-27)
Ofen-Fertigmeldung und dauerhafte Platzfreigabe werden gemeinsam gespeichert. Bei einem Schreibfehler bleibt die Belegung erhalten und die Aktion meldet einen Fehler; sie kann erneut ausgeführt werden. Ein alter, nur halb gespeicherter Abschluss kann seinen Platz freigeben, ohne erneut eine Fertigmeldung oder einen Druckauftrag zu erzeugen. Einlegen, Abschließen und Tischumbuchungen überschreiben keine gleichzeitig laufende Ofenaktion.
Eine Übergabe prüft nur die betroffenen Positionen samt Zulieferungen. Die Archivübersicht verarbeitet höchstens 256 Belege je Leseblock und behält nur die sichtbaren Belege plus Auswertungszähler. Summen, Filter, Tagesgrenzen, Stationsanteile und Prognoseregeln bleiben gleich. Während der Auswertung neu gespeicherte Abschlüsse erscheinen beim nächsten Abgleich.
Der Server lässt eine Archivauswertung gleichzeitig zu. Geräte warten bei Auslastung mit
wachsenden Abständen; schnelle Filterwechsel werden zum neuesten gewünschten Stand
zusammengefasst. Eine Antwort darf bis zu 30 Sekunden benötigen. Schließen oder Sperren verwirft
alte Antworten und Wiederholungen. Der Diagnose-Heartbeat enthält dazu archives mit denselben
Zählern wie pickups. Keine neue Migration oder Laufzeitabhängigkeit; Server neu starten und
alle Geräte vollständig neu laden. Vor-Ort-Abnahme ausschließlich in ROADMAP.md.
Stabilität bei Fertigmeldungen und verzögertem Versand (2026-09-27)
Alte Fertigmeldungen werden auch bei unterschiedlichen Fertigzeiten auf demselben Bon zuverlässig ausgeblendet. Das bestätigt weiterhin keine Abholung; offene Übergaben und wartende Zulieferungen bleiben erhalten. Der Server verarbeitet die Liste portionsweise und lässt nur eine Listenabfrage gleichzeitig zu. Weitere Geräte versuchen den Abgleich mit wachsenden, leicht versetzten Abständen erneut. Es entsteht keine Warteschlange vollständiger Listen im Arbeitsspeicher.
Bei einer bereits bekannten Tischrunde darf eine fehlende Bestätigung des Entwurfsspeicherns den Bestellversand nicht still abbrechen. Die Bestellung wird über die vorhandene Outbox mit unveränderter ID übertragen und bis zur Bestätigung wiederholt, höchstens 15 Minuten. Echte Ablehnungen und Rundenwechsel bleiben geschützt; der Entwurf bleibt zur Prüfung erhalten. Ist die erste Tischrunde noch nicht bestätigt, weist die Oberfläche ausdrücklich auf den noch ausstehenden Versand hin.
Einspielen in einer Betriebspause: normale DB-Sicherung prüfen, Server aktualisieren und
neu starten, anschließend Service, Bar und Theke vollständig neu laden
(pickups.js?v=20260927-1). Keine neue Datenbankmigration oder Laufzeitabhängigkeit.
Die vorhandenen Altmeldungen werden beim nächsten Listenabgleich verarbeitet; keine Tabelle
manuell leeren. Vor-Ort-Abnahme und Pi-Messung stehen ausschließlich in ROADMAP.md.
Die bestehenden 30-Sekunden-Diagnosezeilen enthalten jetzt pickups: active (0/1),
completed, busy, failed sowie last_ms und max_ms. Zähler und Maximum gelten seit
Prozessstart. Diagnosepakete und core.<PID> sind vom statischen HTTP-Zugriff ausgeschlossen.
Reproduzierbarer Speichertest ausschließlich mit künstlichen Daten im RAM:
node --expose-gc --max-old-space-size=64 scripts/check-pickup-load.js.
Baileys bereinigt (2026-09-23)
Baileys gibt es einmal mit 2 cl zu 2,50 € und 4 cl zu 4,50 € sowie separat als „Baileys on Ice“ (4 cl zu 4,50 €). Der doppelte 2-cl-Artikel wurde entfernt. Bereits gesendete Positionen des entfernten Artikels bleiben gültig. Nach Einspielen Server in einer Betriebspause neu starten und Geräte vollständig neu laden. Ungesendete Positionen des entfernten Artikels entfernen und neu hinzufügen.
Bierdeckel mit Ja oder Nein statt Menge (2026-09-22)
Die Zeile „Bierdeckel“ der Getränkebestellung fragt jetzt Ja oder Nein ab statt einer Menge. Ein Tipp auf ihr Feld öffnet an derselben Stelle wie der Zahlenblock zwei große Antworten; ein erneuter Tipp auf die aktive Antwort hebt die Wahl wieder auf. Ohne Antwort steht dort wie bei allen Zeilen ein Strich – in der Liste und im PDF. Die anderen 61 Zeilen sind unverändert Mengenzeilen; physische Zifferntasten wirken dort weiter, bei Bierdeckel nicht.
Im Händler-PDF steht „Ja“ beziehungsweise „Nein“ mittig im vorhandenen Mengenfeld der Bierdeckel-Zeile, in derselben Schrift wie die Mengen. Vorlage, Spalten und Reihenfolge sind unverändert, die erzeugte Datei bleibt genauso lang wie bisher. Eine Antwort ist keine Einheit: Kontrolle, Übergabe und Historie nennen Einheiten nur, wenn wirklich Verpackungen bestellt sind, zählen die Zeile aber als Position.
Der Pub-Server prüft jede Zeile nach ihrem eigenen Vertrag – Mengenzeilen nur 1–9999, die Bierdeckel-Zeile nur „Ja“ oder „Nein“. Der Artikelkatalog steht damit auf Version 4; ein noch nicht neu geladenes Gerät erhält beim Bestätigen den Hinweis, die App neu zu laden. Ein vor dem Update gespeicherter Entwurf behält seine übrigen Mengen und weist einmal darauf hin, dass Bierdeckel jetzt eine Antwort braucht. Gespeicherte Bestellungen der Historie bleiben unverändert.
Nach Einspielen Server in einer Betriebspause neu starten, damit er denselben Katalog liest, und
alle Servicegeräte vollständig neu laden (CSS 20260922-5, supplier-orders.js 20260922-1,
Katalog und Vorlage ?v=4). Keine Migration.
Ruhige Wechsel in Abschlüssen und Reservierungen (2026-09-22)
Ein Wechsel von Heute/Woche/Monat, ein Tipp auf die Blätterpfeile, Gesamt/Theke/Bar, der Produktfilter, die Suche und die automatische Aktualisierung nach einer Kassierung lassen die Auswertung jetzt stehen, wo sie ist. Vorher wurde der ganze Block für die Dauer der Abfrage durch eine 44 px hohe Ladezeile ersetzt und danach wieder aufgebaut – das war das Auf- und Zuklappen. Jetzt bleiben die alten Zahlen sichtbar und werden dezent abgedunkelt, bis die neuen da sind; ein Ladeplatzhalter erscheint nur noch beim ersten Laden nach der PIN-Freigabe, nach einem Fehler und beim Rückweg aus einem Abschlussdetail.
Die drei Bereichsköpfe „Artikel & Größen“, „Tage & Uhrzeiten“ und „Hinweise & Ausblick“ sind über Abgleiche hinweg dieselben Elemente. Ein offener Bereich bleibt offen, behält den Tastaturfokus und wird nur neu aufgebaut, wenn sich sein Inhalt wirklich geändert hat. Die seitliche Bewegung der Abschlussliste gehört wieder allein dem Wechsel zwischen Liste und Detailansicht.
Im Reservierungsbuch klappen die Bereichslisten mit derselben ruhigen Bewegung wie in den Abschlüssen: Deckkraft und Verschiebung statt Höhe. Eine Änderung von einem anderen Gerät aktualisiert einen offenen Bereich still, ohne ihn nachzucken zu lassen. Auch „Weitere Angaben“ im Formular folgt dieser Bewegung.
Nach Einspielen alle Geräte vollständig neu laden (CSS 20260922-4, Reservierungen
20260922-1). Kein Serverneustart nötig, es betrifft nur die Darstellung.
Abschlüsse: drei Bereiche statt einer langen Bahn (2026-09-22)
„Details & Trends“ ist in drei benannte Bereiche aufgeteilt, die einzeln aufklappen; es ist immer nur einer offen, und alle starten geschlossen:
- Artikel & Größen – Top und Flop 10 nach Menge und Umsatz, die vollständige Artikeltabelle je Größe, darunter „Ohne Verkauf im Zeitraum“.
- Tage & Uhrzeiten – Tagestabelle des Zeitraums und der Stundenverlauf mit Tabelle.
- Hinweise & Ausblick – Betriebstagsgrenze, Bereichshinweise, genauer Vergleichszeitraum und die Wochenprognose.
Die Erklärtexte stehen damit nicht mehr zwischen den Zahlen. Die Hauptansicht ist unverändert: drei Kennzahlen, Vergleichszeile, Blätterleiste und Tagesbalken.
Die Artikeltabelle zeigt zunächst 40 Zeilen; „Alle … Größen zeigen“ baut den Rest auf ausdrücklichen Tipp auf. Weil die meistverkauften Größen oben stehen, sind die relevanten Entscheidungen ohne diesen Schritt sichtbar.
Damit ist auch das Ruckeln beim Aufklappen erledigt. Ein Bereich entsteht erst beim Öffnen und
gibt seinen Inhalt beim Schließen wieder frei; ein Monat mit 320 verkauften Größen bringt
geschlossen 108 statt 2.514 Elemente ins Dokument. Ein Neuladen während des Dienstes – nach
jeder Kassierung – baut nur noch den offenen Bereich neu auf. Die Bewegung verwendet weiterhin
220 ms, animiert aber Deckkraft und Verschiebung statt der Höhe: Höhe erzwingt in jedem Frame
ein neues Layout des gesamten Inhalts. node scripts/profile-archive-insights.js hält diese
Kosten reproduzierbar fest (ohne Server und ohne Betriebsdaten); Frame-Zeiten auf den Tablets
sind damit nicht gemessen.
Nach Einspielen alle Geräte vollständig neu laden (CSS 20260922-3). Für diese Änderung ist
kein Serverneustart nötig, sie betrifft nur die Darstellung.
Abschlüsse: Größen getrennt, Top/Flop 10 und Artikel ohne Verkauf (2026-09-22)
Unter „Artikel & Größen“ trennen Ranglisten und die neue Artikeltabelle jetzt die Größen: „Licher Pilsner 0,5 l“ und „Licher Pilsner 0,3 l“ sind eigene Zeilen, weil sie unterschiedliche Entscheidungen sind. Die Größe steht als ruhiger Goldmarker hinter dem Namen. Der Produktfilter oben bleibt bewusst auf Artikelebene und fasst alle Größen eines Artikels zusammen.
Top und Flop zeigen je zehn statt fünf Einträge, nach Menge und nach Umsatz.
Neu ist „Alle verkauften Artikel“: eine Zeile je Artikel und Größe mit Menge, Umsatz und Anteil am Umsatz des gewählten Bereichs, meistverkaufte zuerst. Gezeigt werden bis zu 400 Größen; mehr wird ausdrücklich als gekürzt gekennzeichnet.
Darunter steht „Ohne Verkauf im Zeitraum“: die Katalogartikel, die im gewählten Zeitraum kein einziges Mal verkauft wurden, gruppiert nach Warengruppe. Diese Liste kommt aus dem Katalog, nicht aus dem Archiv – nicht verkaufte Artikel sind keine Archivpositionen. Im Service verborgene Artikel (die Tonic-Aufpreise) stehen nicht darin, Saison- und Eventartikel dagegen schon. Bei „Theke“ oder „Bar“ enthält die Liste nur Artikel der jeweiligen Station. Mit Produktfilter oder Suche entfällt sie, weil sie dann nichts Sinnvolles aussagt.
Einkaufspreise, Margen und Litermengen sind bewusst nicht enthalten.
Nach Einspielen Server in einer Betriebspause neu starten und alle Geräte vollständig neu
laden (CSS 20260922-2). Keine Migration, keine Änderung an gespeicherten Abschlüssen.
Abschlüsse: Zeiträume blättern und eindeutige Vergleiche (2026-09-22)
Unter „Heute / Woche / Monat“ steht eine Blätterleiste: linker Pfeil einen Betriebstag, eine Woche oder einen Monat zurück, rechter Pfeil wieder vorwärts. Die Mitte nennt den angezeigten Zeitraum („Dienstag, 22.09.2026“, „21.09.–27.09.2026“, „September 2026“) und führt mit einem Tipp zurück zum aktuellen Zeitraum. Der Rückwärtspfeil ist gesperrt, sobald davor keine archivierten Abschlüsse liegen; der Vorwärtspfeil ist im aktuellen Zeitraum gesperrt. Bereich (Gesamt/Theke/Bar), Produktfilter und Suche bleiben beim Blättern erhalten. Ein Wechsel der Zeitraumart und jede neue PIN-Freigabe starten wieder im aktuellen Zeitraum.
Alle Grenzen rechnet der Server aus Betriebstagen ab 06:00 Uhr Berlin; Geräte senden nur Zeitraumart und Schrittzahl, keine Zeitstempel. Zurück reichen 520 Schritte (Tage, Wochen oder Monate).
Die Vergleiche sind jetzt ausdrücklich benannt:
- Ein abgeschlossener Zeitraum wird vollständig gegen den vollständigen Vorzeitraum gestellt („Vollständiger Zeitraum gegen vollständigen Zeitraum“).
- Ein laufender Zeitraum vergleicht bis zum gleichen Stand („Tag 22 von 30“), gemessen in Betriebstagen plus Uhrzeit. Die Zeitumstellung verschiebt das Fenster damit nicht mehr.
- Ist der Vorzeitraum kürzer als der bisherige Fortschritt – etwa Februar gegen den 31. März –, wird er vollständig gewertet und genau so beschriftet („28 statt 31 Tage“).
- Monat vergleicht gegen den Vormonat, Woche gegen die Vorwoche. Ein Betriebstag vergleicht gegen denselben Wochentag der Vorwoche, weil der feste Ruhetag den Vortag als Referenz unbrauchbar macht; der Vortag steht zusätzlich als ruhige Zahl darunter.
Die Monatsansicht hat jetzt dieselbe Tagesstruktur wie die Woche: ein Balken je Betriebstag mit Umsatzspitze, Tag mit den meisten Abschlüssen und einer vollständigen Tagestabelle unter „Tage & Uhrzeiten“. Zukünftige Tage bleiben ohne Wert. Der Ausblick bleibt der laufenden Woche vorbehalten.
Nach Einspielen Server in einer Betriebspause neu starten und Bar/Theke sowie Servicegeräte
vollständig neu laden (CSS 20260922-1). Keine Migration, keine Änderung an gespeicherten
Abschlüssen; die Auswertung liest weiterhin nur receipt_archives.
Getränkebestell-PDF und Archivzeiträume (2026-09-21)
Leere Mengenfelder im neu exportierten Getränkebestell-PDF zeigen einen zentrierten
Strich. Nach git pull --ff-only Service beziehungsweise Stationsseite vollständig neu
laden und das PDF neu erzeugen; vorhandene PDF-Dateien ändern sich nicht.
Das Abschlussarchiv nennt den ausgewerteten Zeitraum oberhalb der Kennzahlen und
kennzeichnet den Vormonat ausdrücklich als Vergleich. „1 verkauft“ in der Flop-Liste
ist eine Verkaufsmenge; mehrere Artikel können dieselbe Menge haben. Nicht verkaufte
Artikel sind nicht Teil der Ranglisten; sie stehen seit dem 22.09.2026 in einer eigenen
Liste (siehe oben). Für diese Anzeigeänderung ist kein zusätzlicher
Serverneustart nötig.
Atomare Bestellungen und Tischrunden (2026-09-21)
Bestellungen speichern Deckelpositionen, Produktionsbon und Wiederholungsnachweis gemeinsam.
new_ticket benötigt jetzt round_id, base_revision und eine stabile order_line_id je
Position. Der Server bestätigt und verteilt erst nach dem Commit. Wiederholung derselben
Bestell-ID liefert das gespeicherte Ergebnis; andere Nutzdaten unter derselben ID werden
abgelehnt. Unabhängige Ergänzungen derselben Runde dürfen parallel eintreffen, bereits
bestellte oder zwischenzeitlich veränderte Positionen werden nicht nochmals produziert.
Nachbons werden im gleichen Vorgang abrechenbar, bleiben aber ohne Produktionsauftrag.
coaster_rounds hält unveränderliche Runden-IDs und abgeschlossene Revisionsstände fest.
Eine neue Bewirtung unter demselben Deckelnamen erhält eine andere ID und einen höheren
Revisionsstand. Auch ältere Änderungsanforderungen ohne Rundenfeld können so keinen alten
Revisionsstand wiederverwenden. Leergekassierte Sofortverkaufsplätze beginnen eine neue Runde.
Bestehende offene Deckel werden beim Start additiv zugeordnet; Belege und Produktionsdaten
werden nicht umgeschrieben. order_operations und order_lines sichern Bestellwiederholungen.
Verwaltungstransaktionen und die Sofortverkaufs-Migration laufen auf einer exklusiven,
seriell genutzten Verbindung (sqlite-isolated-transaction.js). Normale Deckelwrites auf
der Hauptverbindung können damit nicht in den Rollback einer Verwaltungstransaktion geraten.
Bestellung, Verwaltung und Checkout teilen dieselbe Transaktionswarteschlange auf
checkoutDb; Umbuchungen behalten ihre eigene Verbindung/Warteschlange. Es wird keine
zusätzliche dauerhafte SQLite-Verbindung geöffnet.
Archivlesezugriffe verwenden die dauerhaft serialisierte Hauptverbindung, nicht die
Checkout-Transaktionsverbindung. Damit belegen wartende Reads nicht den Worker-Pool,
den der schreibende Vorgang für seinen Commit benötigt.
Update: In einer Betriebspause mit leerem Ofen und geprüfter DB-Sicherung einspielen, Server neu starten und alle Service-/Stationsseiten vollständig neu laden. Alte Clients können keine neuen Produktionsbons mehr senden; ihre Entwürfe müssen vor dem Update geklärt sein. Die neue Oberfläche wartet vor Bestellversand/Offline-Replay auf die Rundenfreigabe vom Server. Kein automatisches Live-Deployment durch die Prüfskripte.
Bei Konflikten bleiben Entwürfe lokal erhalten. „Deckel prüfen“ zeigt den gesicherten Snapshot zum Abgleich; er überschreibt den Serverstand nicht automatisch. Abgelehnte Bestellungen landen zur Prüfung bei den gespeicherten Offline-Entwürfen. Ein Entwurf einer geschlossenen Runde wird nicht auf eine neue Bewirtung desselben Tisches übertragen. Unbestätigte Bestellungen bleiben bis zum ACK beziehungsweise zur bisherigen 15-Minuten-Grenze in der Outbox. Eine erstmals offline angelegte Runde benötigt vor dem ersten Senden eine Serververbindung; ihr lokaler Entwurf bleibt erhalten.
Reproduzierbare Prüfungen ohne produktive Daten:
node --test tests/consistency-socket.test.js
node scripts/check-consistency-soak.js # 15 Minuten, eigene DB und eigener Server
PLAYWRIGHT_MODULE=/pfad/zu/playwright CHROMIUM_BIN=/pfad/zu/chromium node scripts/check-order-browser.js
Automatische Betriebsdiagnose auf dem Pi (2026-09-21)
Die kleine Diagnose für langsame Vorgänge aller Geräte läuft automatisch. Einmalig in einer Betriebspause aktualisieren:
git pull --ff-only
pm2 restart willyspub
Danach Bar/Theke vollständig neu laden. Die Diagnose läuft während jedes Dienstes 10:00 bis 03:00 Uhr in Europe/Berlin, inklusive Sommer-/Winterzeit. Außerhalb werden keine Diagnosevorgänge aufgezeichnet. Automatisch entsteht im Projekt:
runtime-diagnostics/dienst-2026-09-21.jsonl
Diese Datei umfasst den 21.09. ab 10 Uhr bis zum 22.09. vor 03 Uhr. Einträge nach
Mitternacht gehören weiterhin zum Startdatum. Neustarts hängen an die bestehende
Datei an; nichts wird überschrieben oder automatisch gelöscht. Die Datei kann direkt
zur Auswertung weitergegeben werden, ein Export ist nicht nötig. Auf dem Pi liegt sie
normalerweise unter ~/willyspub/runtime-diagnostics/.
Optional eine Zusammenfassung anzeigen (Datum = Dienstbeginn):
python3 scripts/export-finish-diagnostics.py --date 2026-09-21
Der Befehl liest die vorhandene Dienstdatei und zeigt Anzahl, p50/p95/max, Fehler,
fehlende Berichte sowie die langsamsten Vorgänge. --output <Datei> erstellt auf Wunsch
eine Kopie ohne Überschreiben vorhandener Dateien. --logs <Muster> unterstützt ältere
PM2-Logs einschließlich gzip; auch diese werden nach dem Berliner Dienstfenster gefiltert.
Erfasst werden Servereingang und ACK von finish_ticket_station_batch/finish_oven_slot,
kumulative Teilzeiten einschließlich DB-Wartezeit, Client-ACK und beobachtete Entfernung
des Abschlussbuttons/Ofenartikels, Timerverzögerung und Touch/Hintergrundstatus.
DOM-Abfrage alle 100 ms nur während eines Vorgangs; zwei Animationsframes schätzen eine
Darstellungsmöglichkeit, nicht den tatsächlichen Bildschirm-Paint. Messstart ist der
Aktionsversand, nicht die physische Fingerberührung. Server und Client messen jeweils
monoton; Geräteuhren werden nicht voneinander subtrahiert.
Zusätzlich werden Bestellen (new_ticket), Deckeländerung/-löschung, Sofortverkauf,
Kassieren, Umbuchen, Bonrückruf, Einlegen in den Ofen und einzelne Stationsaktionen
serverseitig für alle Geräte gemessen. Pro schnellem Vorgang eine Ergebniszeile mit
Startzeit, Dauer, Aktion, Socketkennung/Rolle und Ergebnis; ab einer Sekunde zusätzlich
activity_pending, nach 30 Sekunden ohne Antwort activity_unconfirmed. Das ist nur
ein Diagnoseende, kein Abbruch der Geschäftsaktion; eine spätere Antwort wird unverändert
zugestellt. Maximal 256 solche Messungen gleichzeitig. Keine Payloads und keine zusätzlichen
Geräteanfragen. Auswertung gruppiert diese Laufzeiten nach Aktion. Client-/DOM-Messung
bleibt auf Stations-/Ofenabschlüsse beschränkt; Gerätekennung gilt pro Socketverbindung.
Gemeinsame dbGet/dbRun/dbAll melden Fehler und Aufrufe ab 250 ms ohne SQL/Parameter;
direkte DB-Aufrufe in weiteren Modulen sind dadurch nicht vollständig instrumentiert.
Alle 30 Sekunden: Node-CPU, RSS, maximale Event-Loop-Verzögerung und Socketanzahl
(eingebetteter Service zählt als eigene Verbindung). Verbindungen/Abbrüche tragen
Zeitstempel. Keine Gäste-, Tisch-, Artikel-, Passwort- oder Tokeninhalte in Diagnosezeilen.
Bestehende andere Logs werden nicht verändert.
Dateigröße: maximal 4 MiB (4.194.304 Bytes) pro Dienst, einschließlich Neustarts.
Ein normaler Dienst benötigt voraussichtlich weniger; die tatsächliche Größe hängt von
der Aktivität ab. 30 Dateien belegen höchstens 120 MiB, 365 höchstens etwa 1,43 GiB.
Keine automatische Löschung alter Dateien. Zusätzlich maximal 12.000 Diagnosezeilen
pro Dienst und Prozess. Beim Dateilimit bleibt eine file_limit-Zeile erhalten; danach
entfallen weitere Einträge für diesen Dienst. Fehlende Berichte allein beweisen keinen
Verbindungsabbruch. Einträge werden asynchron etwa einmal pro Sekunde gesammelt
angehängt, ohne Datenbankwrites. Maximal 256 KiB warten im Puffer plus ein laufender
Schreibblock gleicher Größe; Überlauf/Dateifehler unterbrechen keine Geschäftsaktion.
Bei abruptem Stromverlust können noch ungeflushte Einträge fehlen. Geordneter Neustart
flusht den Puffer innerhalb des bestehenden Shutdown-Zeitlimits.
Die Dateien sind von Git und HTTP-Auslieferung ausgeschlossen. Bestehende PM2-Logs
bleiben unverändert erhalten; neue Diagnosedaten werden nicht zusätzlich darin dupliziert.
STATION_DIAGNOSTICS=0 bleibt nur als technischer Not-Aus, nicht als Bedienungsschritt.
Thekentisch direkt bedienen (2026-09-21)
Jeder Klick auf „Kombi“ oder „Bedienen“ wählt an der Bar „Bar - Theke“, an der
Theke „Pub - Theke“. Auch beim Laden einer gespeicherten Ansicht ist die eigene
Theke gewählt. Andere Tische bleiben manuell wählbar. Laufende oder ungeklärte
Umbuchungen werden nicht unterbrochen. Stationsseiten vollständig neu laden;
kein Serverneustart erforderlich (station-workspace.js 20260921-1).
Bar-Login nach Ofenentfernung (2026-09-21)
Die fehlende Socket.IO-Einbindung der Bar ist wiederhergestellt. Sie hatte nach
korrekter Passworteingabe den Start verhindert und später den WLAN-Hinweis ausgelöst.
Nach git pull die Bar-Seite vollständig neu laden; für diese HTML-Korrektur ist
kein zusätzlicher Serverneustart erforderlich. Der Ofen bleibt aus der Bar entfernt.
Der neue Browsercheck scripts/check-station-login.js prüft den echten Login und
Socket-Verbindungsaufbau mit isolierter Serverkopie und Testdatenbank.
Bar ohne Ofen (2026-09-21)
Die Bar zeigt in Fokus, Kombi und Bedienen keinen Ofen mehr. Ofendock, Dialoge,
Sekundentimer und Ofenton entfallen dort vollständig. Der Server sendet Ofenstände
auch nicht an den eingebetteten Service einer Bar-Station. Die Theke führt weiterhin
Warteliste, Belegung, Timer, Alarm und Abschluss. Speisen lassen sich im Service der
Bar weiterhin bestellen und werden regulär an die Theke geroutet.
Nach Einspielen Server neu starten und alle Stationsansichten neu laden
(station-audio.js 20260921-1).
Mehr Luft über der mobilen Tischwahl (2026-09-20)
Die Kopfzeile der Tisch-/Deckelansicht hat bis 700 px Breite oben 24 statt 12 px
Innenabstand. Im eigenständigen Service kommt die Geräte-Safe-Area hinzu;
im eingebetteten Service bleibt es bei 24 px. Nach Einspielen Clients neu laden
(CSS 20260920-13); für diese CSS-Korrektur ist kein Serverneustart nötig.
Gin mit eindeutiger Tonic-Auswahl (2026-09-20)
Gin-Karten trennen „2 cl pur“ von „Mit Tonic“ (4 cl). Der Tonic-Button öffnet den bestehenden Auswahldialog mit Schweppes (inklusive), Fever-Tree und Thomas Henry (je +2,50 €), jeweils mit dem vollständigen Getränkepreis. Auch „Gin Tonic Standard“ nutzt diese Auswahl und bleibt ohne Pur-Variante. Gin und Tonic bilden genau eine Position auf Deckel, Barbon, Gästebeleg und im Archiv. Bestehende Preise bleiben gleich. Die bisherigen separaten Aufpreisartikel sind im Service einschließlich Suche, Favoriten und Eventkarte verborgen; alte/offline gespeicherte Buchungen bleiben gültig.
Technisch erzeugt products.js aus dem bisherigen Grundpreis drei reguläre Varianten;
tonicChoices steuert nur die Auswahl. Die vorhandene serverseitige Katalogprüfung
validiert Sorte und Gesamtpreis. Die gespeicherte Variante bleibt unverändert;
getProductionItemDisplay trennt auf Stations-/Arbeitsbons die Größe „4 cl“ von
der Tonic-Sorte. Diese steht darunter in den vorhandenen „+“-Zusatzboxen beziehungsweise
im Sonderwunschbereich des Papierbons. Weitere Notizen bleiben erhalten.
Die bisherige Variante „4 cl + Tonic“ bleibt für Altbestellungen erhalten. Neue
Buchungen nennen das gewählte Tonic ausdrücklich im Variantenlabel.
Nach Einspielen Server neu starten und alle Clients neu laden (Produkte 20260920-2,
CSS 20260920-12), damit Server und Geräte denselben Katalog verwenden.
Kompakter Stationskopf im Fokusmodus (2026-09-20)
Bar und Theke nutzen im Fokusmodus 12 statt 24 px Innenabstand oben und unten.
Der einzeilige Tablet-Kopf ist damit 73 statt 97 px hoch; die Bons gewinnen 24 px.
Schriften, Symbole, Touchflächen und Anordnung bleiben gleich. Schmale Ansichten
behalten ihre bestehenden Umbrüche. Die Höhe hängt nicht von wartendem Essen ab.
Kombi und Bedienen bleiben unverändert. Nach Einspielen Clients neu laden
(CSS 20260920-11); diese CSS-Anpassung benötigt keinen Serverneustart.
Automatische Ofenwarteliste (2026-09-20)
Ofengerichte erscheinen beim Bestelleingang direkt in der gemeinsamen Ofenwarteliste. Reine Essensaufträge erzeugen keine Karte im Bonbereich. Gemischte Bons behalten ihren gewohnten Kopf mit Raumzeichen, Tisch, Nummer und Zeitsiegel. Rechts neben „Fertig“ steht ein Button mit Goldakzent, Ofensymbol und „2 Essen“, der den passenden Auftrag in der Warteliste öffnet. Der grüne Fertigbutton bleibt die größere Hauptaktion; reine Getränkebons nutzen weiter die volle Buttonbreite. Keine zusätzliche Zeile. Nach Getränkeabschluss verschwindet der Bon; wartendes Essen bleibt in der gemeinsamen Leiste enthalten. Getränke lassen sich bei vollem Ofen mit „Fertig“ abschließen. Ihr Arbeitsbon und ihre Fertigmeldung enthalten kein wartendes Essen. Bestellung, Deckel und Zahlung bleiben zusammen.
Der Essensbutton verwendet dieselbe Buttonkomponente wie „Fertig“: gleiche Schrift,
Symbolgröße, Rundung, Konturstärke und Höhe. Goldtext/-kontur markieren Essen, Grün die
Fertigaktion. Der abgestimmte Hintergrund bleibt im Light Mode neutral (--bg-header),
im Dark Mode dunkel (--gold-tint-solid). Unterkante und Zusatzschatten entfallen am Button.
Die obere Leiste behält Goldunterkante und Schatten. Bei schmalen Bons stehen die Symbole
bei beiden Buttons über ihrem Text, innerhalb derselben Buttonzeile und Höhe.
Die sichtbare 44-px-Leiste sitzt in Fokus/Kombi direkt unter dem Stationskopf und außerhalb der scrollenden Bonliste. In Bedienen verschiebt sich derselbe Button an den oberen Rand der Servicefläche; die Ansichtssteuerung berücksichtigt die 56-px-Touchhöhe. Die Ofenetagen mit ihren Timern bleiben unten. Ob eine oder acht Portionen warten, der Platzbedarf der gemeinsamen Leiste bleibt gleich. Sie zeigt das Ofensymbol, „8 Portionen warten“ und rechts die älteste Wartezeit. Sehr schmal steht „8 warten“. Die Vorschau zeigt die nächsten vier Artikelpositionen in Wartelistenreihenfolge, älteste zuerst, jeweils mit Tisch, vollständiger Menge und Gericht. Auch bei neuen Eingängen bleibt diese Reihenfolge sichtbar. Gleiche Gerichte verschiedener Aufträge bleiben getrennt. Bei Platzmangel wechselt die Vorschau alle sechs Sekunden zwischen vollständigen Wortabschnitten, ohne Laufanimation. Berührung und offener Dialog pausieren. Unter 700 px stehen Tisch/Gericht kompakt untereinander; die Altersangabe ist dort nur im zugänglichen Namen enthalten, damit die Vorschau Platz hat. Die Leiste bleibt 44 px hoch.
Neue Essensaufträge setzen „Neu“ auf Goldfläche rechts vor den Chevron, bis die Warteliste geöffnet wird. Der Marker ersetzt rechts die sichtbare Altersangabe, niemals die Auftragsvorschau. Mehrere Eingänge behalten denselben Marker und die älteste Vorschau. Jeder echte neue Eingang startet den 500-ms-Goldimpuls erneut. Reload und Duplikate erzeugen keinen neuen Hinweis; Reduced Motion behält den Text ohne Bewegung. Entfernte/eingelegte Aufträge verschwinden aus der Vorschau und werden aus der ungesehenen Gruppe entfernt. Während der Etagenwahl eintreffendes Essen bleibt bis zur Rückkehr in die Liste markiert. Ab 15 Minuten gilt Warning auf Kontur und Zeit. 6 px echtes Buttonpadding schützen die 44-px-Fläche; gehaltene Stationsaktionen frieren den Einstieg ein. Ohne wartendes Essen entfällt die Zeile. Antippen öffnet den bestehenden Ofendialog mit Tisch, Menge, Gericht, Sonderwunsch und Wartezeit; der Essensbutton neben „Fertig“ springt zum passenden Auftrag. Älteste Bestellungen stehen zuerst. „In den Ofen“ wechselt im selben Dialog zur bekannten Etagenwahl; „Zur Warteliste“ führt mit dem gemeinsamen umrandeten Sekundärbutton samt Linkspfeil zurück (48 px Touchhöhe). Je Auswahl wird eine Portion belegt. Weitere Portionen bleiben stehen. Timer beginnen erst nach bestätigtem Einlegen. Nach erfolgreichem Einlegen bleibt die Liste für weitere Gerichte offen; ist sie leer, schließt der Dialog. Ofenabschluss und Druckschalter gelten wie bisher. Die Warteliste verwendet die dezente warme Gold-Tönung und Kontur der Etagenwahl. Sie folgt den vorhandenen Auswahlkarten: Tisch als Überschrift, darunter Menge und Gericht, Wartezeit als ruhiger Text, Sonderwünsche in Goldtext. Die älteste Bestellung steht oben; Rangnummern, Zeit-Badges und Sonderflächen entfallen. Im Wartelistenmodus bleiben Titel und Schließen stehen; nur die Liste scrollt mit eigener, in Hell/Dunkel gestalteter Scrollspur und mindestens 12 px Inhaltsabstand. Ein Tipp auf den Bonhinweis öffnet die Liste beim passenden Auftrag. Teilmengen aktualisieren den Hinweis; nach dem letzten bestätigten Einlegen entfällt er. Bezahlte Runden bleiben separat, Bonzähler und Bonnummern zählen weiterhin nur gewöhnliche Stationsaufträge. Die sichtbaren Etagenkarten bleiben unverändert. Die Etagenbuttons haben einen unsichtbaren 4-px-Trefferrand einschließlich der runden Ecken; in der 12-px-Lücke bleiben 4 px neutral. So verwirft der zusätzliche Touch-Treffertest keine Randtaps, die der Browser bereits dem Button zugeordnet hat. Halten löst erst beim Loslassen aus; Abrutschen, Scrollen und Durchklicken bleiben gesperrt.
Die Warteliste verwendet die vorhandenen persistenten tickets; kein zusätzlicher
Bon und keine neue Datenbanktabelle. completed_stations beschreibt den Getränke-
beziehungsweise normalen Stationsauftrag. Solange Ofengerichte warten, bleibt das
Ticket offen, auch nach Kassieren oder Neustart. Das letzte Einlegen schließt einen
bereits an allen Stationen erledigten Restbon atomar ab. Fertigmeldungen für Speisen
entstehen ausschließlich beim Ofenabschluss. Dessen Bon erhält auch Sonderwünsche.
Der zentrale Katalogresolver getOvenLane trennt Ofengerichte von normalen Artikeln;
Namen-Fragmente im Thekenrendering sind dafür nicht mehr maßgeblich.
Update der Anzeige: nach Pull alle Stationsseiten vollständig neu laden
(CSS 20260921-3, Workspace 20260921-2). Dafür ist kein Serverneustart nötig.
Die ursprüngliche Ofenwarteliste benötigt beim erstmaligen Einspielen einen Serverneustart. Warteliste und laufende Belegung sind Bestandteil der normalen DB-Sicherung.
Geräteabnahme ausschließlich in ROADMAP.md; Tests verwenden isolierte Datenbanken.
Teesortenauswahl und Wodka Sour (2026-09-20)
Tee (2,50 €) öffnet beim Buchen die vorhandene Auswahl: Pfefferminz, Kamille, Schwarz, Grün, Früchte, Rooibos, Ingwer-Zitrone und Kräuter. Die Sorte wird als Artikelnotiz auf Deckel und Bon mitgegeben; Abbrechen bucht nichts. Wodka Sour steht unter „Cocktails · Sours“: regulär 8,50 €, Happy Hour 7,50 €. Beide Getränke laufen an die Bar. Nach Einspielen Server in einer Betriebspause neu starten (Serverkatalog) und alle Service-/Stationsansichten neu laden.
Datenbankzugriffe bei gleichzeitiger Bedienung (2026-09-19)
Die gemeinsam genutzte SQLite-Verbindung bleibt mit db.serialize() dauerhaft
seriell. Zuvor galt die Serialisierung nur innerhalb des Schema-Initialisierungsblocks.
Parallele Deckeländerungen konnten während einer Kassiertransaktion mehrere
libuv-Worker mit SQLite-Sperrwartezeiten belegen und dadurch auch COMMIT und das
Laden statischer Dateien verzögern. WAL und der bestehende Busy-Timeout bleiben erhalten.
Ein isolierter Socket-Test mit einmal Kassieren und acht gleichzeitigen Änderungen
anderer Deckel reproduzierte vor der Korrektur acht SQLITE_BUSY-Fehler und eine
HTTP-Antwortzeit von rund 40 Sekunden. Mit Serialisierung wurden alle Vorgänge im
gleichen lokalen Vergleich innerhalb von 44 ms bestätigt. Dies ist keine zugesicherte
Pi-Laufzeit und kein rückwirkender Beweis für die gemeldeten Handy-Ausfälle.
tests/sqlite-contention.test.js prüft Bestätigungen, HTTP-Erreichbarkeit und den
anschließend gespeicherten Zustand ausschließlich mit einer temporären Datenbank.
Die Änderung benötigt einen Serverneustart in einer Betriebspause; keine Migration
oder Änderung von Zugangsdaten ist erforderlich. Geräteabnahme siehe ROADMAP.md.
Zusätzlich werden vollständige Kassier- und Umbuchungstransaktionen pro verwendeter
Verbindung über sqlite-transaction-queue.js eingereiht. Der bestehende Schutz pro
Deckel genügte für verschiedene Tische nicht: zwei gleichzeitige Kassierungen oder
zwei unabhängige Umbuchungen konnten auf derselben Verbindung ein zweites BEGIN
ausführen und mit cannot start a transaction within a transaction scheitern.
Die Warteschlange hält bis COMMIT beziehungsweise ROLLBACK; ein Fehler wird an den
Aufrufer zurückgegeben und blockiert nachfolgende Vorgänge nicht. Tests prüfen
parallele Vorgänge, Wiederholschutz, gespeicherte Mengen/Belege und Weiterarbeit nach
Rollback. Auch diese Korrektur wird erst nach einem Serverneustart wirksam.
Unbestätigte Bons automatisch erneut senden (2026-09-16)
Neue Bons bleiben vor dem ersten Versand lokal in der Outbox gespeichert, bis der Server sie bestätigt. Fehlt nach vier Sekunden die Bestätigung, folgt bei bestehender Verbindung nach einer Sekunde ein weiterer Versuch; ein Reconnect ist dafür nicht mehr erforderlich. Pro Ticket-ID läuft höchstens ein Versuch gleichzeitig. Wiederholungen behalten die ursprüngliche 15-Minuten-Frist, auch nach einem Browserneustart. Abgelaufene Bons bleiben in der bestehenden Quarantäne. Der Server prüft den dauerhaften Zustellnachweis atomar beim Einfügen, sodass auch zurückgeholte oder gelöschte Bons nicht erneut entstehen. Diese Änderung behebt eine konkrete Wiederholungslücke; die Ursache der beobachteten Verzögerungen bis zehn Sekunden ist damit nicht rückwirkend nachgewiesen.
iPhone-Service: Übergang zur Statusleiste (2026-09-16)
Die eigenständige iOS-Serviceansicht verwendet für den nativen HTML-/Body-Hintergrund
und den oberen Rand dieselbe deckende Kopffarbe (--bg-header), ohne Root-Farbtransition.
Eine echte, nicht interaktive service-status-surface deckt die obere Safe Area ab
(mindestens 12 px auch bei gemeldetem Inset 0). service-canvas bewahrt die bisherige
Katalogfarbe unter Glow und Vignette. Start-/Anmeldefläche folgt der Kopffarbe.
Die Regeln gelten nur für html.service-app[data-platform="ios"]:not(.embedded).
CSS 20260916-1; Service-PWA nach dem Update vollständig schließen und neu öffnen.
Die native Geräteabnahme ist in ROADMAP.md erfasst.
Ofenbelegung übersteht einen Serverneustart (2026-09-16)
Offene Ofenbelegungen werden in oven_slots gespeichert und beim Start vollständig
wiederhergestellt, auch aus früheren Betriebstagen. Nur unlesbare oder ungültige Einträge
werden entfernt. Die Originalzeit bleibt erhalten; abgelaufene Timer erscheinen als FERTIG
und erfordern den ausdrücklichen Abschluss an der Theke. Der Start druckt nichts automatisch.
Das Einlegen überträgt die Position atomar vom Bon in den Ofen. Schlägt das Speichern fehl, bleibt der ursprüngliche Bon vollständig erhalten. Gleichzeitiges Einlegen wird serialisiert; Clients bekommen die Änderung erst nach erfolgreichem Speichern. Umbuchungen schreiben weiterhin den neuen Deckelnamen mit. Der Erstzustand wartet auf die Wiederherstellung.
Die Tabelle wird automatisch angelegt; pub.db bleibt am bisherigen Ort. Update auf dem
Pub-Server einspielen und Server neu starten. Bereits vor Installation dieser Persistenz
verlorene Einträge können dadurch nicht rekonstruiert werden. Tests in
tests/oven-persistence.test.js prüfen Neustarts am selben und über einen Betriebstag hinweg
sowie Rollback bei Schreibfehlern und belegten Plätzen.
Automatische Stationsbons (2026-09-14)
Bar und Theke haben im Stationskopf neben „Ansicht“ den Schiebeschalter „Automatisch / Bondruck“ (rechts = an mit satter Pubgrün-Fläche und hellem Knopf, links = aus mit transparenter Spur). In Kombi sitzt derselbe Schalter im Admin-Menü. Die Wahl gilt
stationsweit, synchronisiert alle verbundenen Tablets und bleibt nach Neuladen sowie
Serverneustart gespeichert. Standard ist aus: Ohne bewusst gespeicherte Wahl druckt keine
Station automatisch. Theke umfasst auch Ofenbons.
Fertigstellen, Fertigmeldungen und Abrechnung funktionieren unabhängig vom Ausdruck;
ausdrücklich angeforderte Gästebelege und andere manuelle Ausdrucke bleiben möglich.
Beim Einschalten werden ausgelassene Bons nicht nachgedruckt. Bereits in der Druckqueue
angenommene Aufträge werden durch Ausschalten nicht zurückgenommen.
Ohne Verbindung oder während des Speicherns ist der Schalter gesperrt.
Eine früher an einer Station ausdrücklich eingeschaltete Wahl bleibt gespeichert und muss dort
einmal ausgeschaltet werden; der neue Standard gilt für Stationen ohne gespeicherte Wahl.
Für dieses Update Server neu starten und Bar/Theke neu laden (CSS 20260914-21,
Stationsmodul station-admin.js 20260914-2, Ansichtsmodul station-workspace.js 20260914-1). Die Tabelle station_print_settings
wird automatisch angelegt; die vorhandene Datenbank bleibt am bisherigen Ort.
Getränkebestellung am Handy (2026-09-15)
Nur auf dem eigenständigen Service-Gerät öffnet „Getränke bestellen“ im ⋯-Menü
eine mobile Vollbildliste. In der eingebetteten Serviceansicht von Bar/Theke
(Kombi und Bedienen) ist die Getränkebestellung nicht verfügbar.
Die App zeigt die 62 Artikel als Lagerrundgang mit Warengruppen: Lager oben,
Vorderer Keller, Kühlhaus und zuletzt Artikel ohne Lagerortangabe. Innerhalb eines Lagerorts
bleiben die Warengruppen des Händlerformulars als Unterabschnitte erhalten – Fässer, Flaschenbier,
Apfelwein und so weiter –, damit Fass und Flasche nicht durchmischen. Erst innerhalb einer
Warengruppe stehen die Artikel alphabetisch (deutsche Sortierung, Umlaute unter ihrem
Grundbuchstaben, deshalb steht Köstritzer bei K). Jede Zeile nennt Verpackung und Artikelnummer.
Die Händler-PDF selbst bleibt unangetastet und wird weiterhin exakt in der
Reihenfolge des Papierformulars gefüllt.
Es gibt keine Sprungleiste und keine Filterung; Rundgang, Warengruppe und Sortierung sind die
Reihenfolge der Liste selbst. Der Lagerortkopf bleibt beim Scrollen sichtbar und zeigt
„erfasst von gesamt“ für seinen Lagerort; die Warengruppen sind ruhige Versalien-Zwischenzeilen
und scrollen mit. Die Liste hat unterhalb von „Aktuelle Bestellung /
Bestellübersicht“ eine eigene Kante mit Abstand, damit gescrollte Zeilen nicht an den beiden
Beschriftungen kleben.
Eine Menge zählt ganze Verpackungen aus dem Händlerformular – Kiste, Fass, Gebinde oder Flasche,
also etwa 24 × 0,33 l, 50 l oder 10 kg. In der App heißen sie durchgehend „Einheiten“.
Mengen sind ganze Einheiten
(1–9999), leere Mengen und Null bestellen nichts. Große Mengenbuttons öffnen den
appinternen Zahlenblock (0–9, Leeren und letzte Ziffer löschen), ohne Handytastatur.
Einzige Ausnahme ist die Zeile „Bierdeckel“: Sie fragt Ja oder Nein (siehe oben).
Die erste Ziffer ersetzt beim erneuten Öffnen eine vorhandene Menge; weitere Ziffern
werden angehängt. Jede Eingabe speichert sofort. „Weiter“ geht in Listenreihenfolge – Lagerort,
Warengruppe, darin alphabetisch – zum nächsten Artikel, ohne Eingabe wird dessen Menge unverändert
übersprungen. „Fertig“
kehrt zur Liste zurück; beim letzten Artikel führt „Zur Übersicht“ zur Kontrolle.
Escape schließt zuerst den Zahlenblock, danach den Dialog. Physische Zifferntasten
sowie Rücktaste/Entfernen bleiben nutzbar.
Der Entwurf wird unter willys_supplier_order_v1 ausschließlich auf diesem Gerät im
Browser gespeichert; keine Synchronisierung zwischen Geräten. Dieser Dauerhinweis steht
als ruhige Fußnote, nicht als Meldung. Speicherfehler, Warnungen und Verbindungsfehler
erscheinen dagegen farblich als Fehler beziehungsweise Warnung.
„Leeren“ verlangt eine Bestätigung und ist als Löschaktion gekennzeichnet; die
Bestätigung nennt die Zahl der betroffenen Positionen. Die Kontrollansicht zeigt nur
gewählte Artikel, nach Lagerort und Warengruppe gegliedert, mit Positions-, Einheiten- und
Lagerortsumme.
„PDF erstellen“ füllt eine sauber nachgezeichnete, zweitseitige PDF-Vorlage und führt in
einen eigenen Übergabeschritt mit Dateiname, Umfang und drei beschrifteten Aktionszeilen:
„PDF an Mail-App geben“ übergibt die Datei an die Systemfreigabe (nur wenn das Gerät
Dateifreigabe unterstützt) und legt Betreff sowie Text bei, sofern die Plattform beides
zusammen annimmt; „E-Mail vorbereiten“ öffnet die Mail-App mit fertigem Betreff und Text
(Empfänger vorbelegt, sobald MERCHANT_MAIL in supplier-orders.js gesetzt ist), die PDF
muss dort noch angehängt werden; „PDF herunterladen“ legt die Datei ab.
Ein Browser kann keine Datei automatisch an eine Mail hängen und beim Teilen keinen Empfänger
setzen – deshalb die zwei Wege. Die App verschickt nichts selbst, es gibt keinen
SMTP-Versand und keine Versandbestätigung. Export löscht den Entwurf nicht.
„Bestellübersicht“ zeigt die auf dem Pub-Server gespeicherte Historie, neueste zuerst. Nach Übermittlung an den Händler „Als bestellt markieren“ wählen; die anschließende Frage bestätigt die Übermittlung ausdrücklich. Erst die bestätigte Speicherung leert den aktuellen Entwurf. Gespeichert werden der Zeitpunkt der manuellen Bestätigung, Artikelsnapshots und die exakte ausgefüllte PDF. Details und PDF-Download stehen auf allen angemeldeten Geräten zur Verfügung, auch nach späteren Vorlagenänderungen. Wiederholungen derselben Bestätigung sind idempotent. Die Historie beginnt mit dieser Funktion; frühere PDF-Downloads zählen nicht nachträglich als Bestellungen. Dies ist kein automatischer E-Mail- oder Liefernachweis.
supplier-orders.js lädt assets/orders/catalog.json und template.pdf beim ersten Öffnen.
Rundgang, Warengruppen und alphabetische Sortierung werden daraus einmalig berechnet; Katalog und
Vorlage bleiben unverändert.
Die laufende Ansicht funktioniert danach ohne weitere Netzaufrufe; ein Offline-Neustart ist
nicht zugesichert. PDF-Erzeugung findet lokal ohne neue Runtime-Abhängigkeit statt.
Für dieses Update alle Servicegeräte neu laden (CSS 20260915-5,
supplier-orders.js 20260915-6); ein Serverneustart ist nicht erforderlich.
scripts/build-supplier-template.py regeneriert mit PyMuPDF (nur Entwicklungswerkzeug)
die geprüfte Vorlage direkt aus den gepflegten Artikeln und dem Tabellenlayout.
bestellungen.pdf bleibt nur die ursprüngliche Scan-Referenz. Die Exportvorlage enthält
ausschließlich echte Schrift und Vektorlinien; Spalten, Reihenfolge, Leerzeilen und
Seitenumbruch folgen dem Papierformular. Ein- bis vierstellige Mengen werden mittig
in der Mengenspalte gesetzt; die Antwort „Ja“/„Nein“ der Bierdeckel-Zeile nutzt dasselbe
vierstellige Feld. Bei Vorlagenänderungen Artikel/Koordinaten
visuell prüfen, beide Assets gemeinsam erzeugen und deren URL-Version im Client erhöhen.
Sambuca Molinari läuft an die Theke (Standardzone Vorne). Beim Buchen öffnet sich der vorhandene Auswahldialog mit „Mit Kaffeebohne“ und „Ohne Kaffeebohne“. Die Wahl wird als Artikelnotiz in Deckel und Bon übernommen; Abbrechen bucht nichts.
Architektur, Setup & Entwickler-Handbuch
Echtzeit-Gastro-System für Service, Bar und Theke. Diese Datei erklärt Aufbau und Betrieb. Ergänzende Dokumente:
- Feature-Historie:
CHANGELOG.md(was wann umgesetzt wurde) - Geplante Features / Ideen:
ROADMAP.md - Aktueller technischer Arbeitsstand & No-Gos:
docs/AI_HANDOFF.md - Design-System / Farb-Token-Karte:
docs/DESIGN_SYSTEM.md - Regeln für Agents/KI:
.agents/AGENTS.md - Pub-Layout / Raumaufteilung:
docs/PUB_LAYOUT.md - Backups / Wiederherstellung:
docs/BACKUP_RESTORE.md - Funktions-, Nutzen- und Preisanalyse (Verkaufsunterlage):
docs/sales/index.htmlals fertige Präsentationsseite (im Browser öffnen, enthält alle Screenshots, einen Knopf „Termin vorführen“ mit sieben geführten Schritten und einen Knopf „Als PDF drucken“). Inhaltliche Quelle istdocs/sales/WILLYS_PUB_SYSTEMANALYSE.mdmit 69 echten Screenshots unterdocs/sales/screenshots/; die Seite wird mitnpm run build:salesneu erzeugt, die Screenshots mit den Entwicklungswerkzeugen inscripts/sales-demo/
Fertigmeldungen und Servicerufe (2026-09-14)
Das Fertigmeldungsfenster verwendet den vollständigen Bereichsnamen „Biergarten“. Ein neuer Tap auf den Hintergrund schließt es; die Geste muss außerhalb der Dialogkarte beginnen und enden. Herausziehen, abgebrochene Gesten und der Folge-Klick beim Öffnen schließen nicht. X, Escape und Fokusrückgabe bleiben erhalten.
Servicerufe und Fertigmeldungen lösen auf keinem Gerät Vibration aus. Hinweise erscheinen visuell und bei eingeschaltetem Abholton zusätzlich akustisch.
Die Übersicht bündelt alle aktuellen Fertigmeldungen pro Tisch in einem kompakten Eintrag,
auch über mehrere Bons und Fertigzeiten hinweg. Kopf: Tischname und Alter der ältesten noch
sichtbaren Fertigmeldung; darunter eine Artikelzeile je Ausgabe (Bar/Theke). Gleiche Artikel
mit identischer Variante und Notiz werden innerhalb derselben Ausgabe mengenmäßig addiert.
Unterschiedliche Notizen bleiben getrennt. Einzelne Meldungen verfallen weiterhin unabhängig;
Produktionsübergaben behalten ihre eigenen, unveränderten Bestätigungsaktionen.
Der Schließen-Button verwendet dieselbe close-coaster-Komponente wie Reservierungen und
Deckel, einschließlich Fläche, Kontur, Farbe und Hover. Keine eigene Farbüberschreibung.
Beim Öffnen erhält der Titel den Fokus;
Tastaturfokus bleibt am X sichtbar, Schließen stellt den vorherigen Fokus wieder her.
Fertigmeldungen aller Tische sind immer sichtbar, unabhängig vom Abholton. Neue Fertigmeldungen
werden über 2,5 Sekunden gebündelt: sechs Sekunden „Theke fertig“ / „Bar fertig“, bei beiden
„Abholbereit“, im bestehenden Haupt- und Tischkopf. Tischname und Artikel stehen ausschließlich
in der Übersicht; das Badge zählt unterschiedliche fertige Tische. Zwei nicht interaktive goldene
Seitenlichter pulsen einmal für 1,4 Sekunden, weich nach innen sowie oben/unten auslaufend.
Keine obere/untere Linie und keine Rahmenecken; die Systemstatusleiste wird nicht verändert. Weitere Fertigmeldungen während der Pille
aktualisieren deren Inhalt ohne neuen Impuls oder Verlängerung. Ein ausdrücklicher Serviceruf
hat während seiner sechssekündigen Pille Vorrang: „Theke ruft“ / „Bar ruft“ bzw. „Bar + Theke“
mit zwei Seitenlichtimpulsen in 2,8 Sekunden. Reduced Motion zeigt die Seitenlichter statisch.
Die Lichtflächen reichen bei Fertigmeldungen 32 px, bei ausdrücklichem Ruf 40 px nach innen.
Ein breiterer goldener Kern geht bei 22 % Breite in 60 % Gold, bei 58 % in Gold-Tint und
am inneren Ende vollständig in Transparenz über; oben/unten bleibt der weiche Auslauf.
Erstabgleich, Reconnect und Hintergrundrückkehr erzeugen keinen Ankunftseffekt; Trennung und
Hintergrundwechsel räumen laufende Effekte auf. CSS 20260914-6, Abholmodul 20260914-5.
Auf jedem eigenständigen Servicegerät sind Meldungen für Pub, Bar, Biergarten und freie
Tischnamen sichtbar. Im Detaildialog schaltet „Abholton an/aus“ ausschließlich die zusätzliche
Ansage (assets/audio/abholen.mp3). Einschalten spielt die Ansage zur Probe; nach Neuladen kann
„Ton aktivieren“ erforderlich sein. Die gerätebezogene Wahl wird unter willys_pickup_sound
gespeichert; eine vorhandene Biergartenmodus-Wahl wird als Tonpräferenz übernommen. Ohne
vorhandene Wahl ist Ton aus. Auch bei blockiertem Ton bleiben alle visuellen Hinweise aktiv.
Die Rufleuchte bleibt mit Anzahl und farbig hinterlegter Fläche sichtbar; auf der Karte gibt es keine zusätzliche Leiste oder Einblendung über den Artikeln. Die Tisch-/Deckelübersicht verwendet dieselbe Rufleuchte im vorhandenen Kopf. Systemstatus und Hell/Dunkel liegen im ⋯-Menü; Störungen bleiben als „!“ am Menü sichtbar. An Bar/Theke stehen Bon-Ton, Hell/Dunkel und das bisherige Admin-Menü rechts im Header. Die Stationsüberschrift steht im Fokusmodus wieder mittig. „Service rufen“ sitzt als runder Symbolbutton direkt neben Bon-Ton und Hell/Dunkel und verwendet dieselbe Button-Komponente. Das Admin-Menü enthält ausschließlich Reservierungen und Abschlüsse. Dienstplan und Arbeitszeit sind seit 2026-09-16 nicht mehr im Menü, weil sie derzeit nicht im Einsatz sind; beide Module bleiben geladen und lassen sich mit je einer Menüzeile wieder sichtbar machen. Produktionsübergaben bleiben über die Rufleuchte im Tisch-/Deckelkopf der eingebetteten Serviceansicht erreichbar. Ein Tipp öffnet die Details mit Tisch, Abholort, Artikeln und „vor … Min. fertiggestellt“. Orange ab einer Minute und Rot ab zwei Minuten nutzen die vorhandenen App-Farben. Nach drei Minuten verschwindet die Meldung automatisch, ohne Abholbestätigung und ohne die Behauptung, dass noch etwas steht oder bereits abgeholt wurde.
Im gesamten Service teilen die Kopfaktionen die gleiche runde Grundform und Badge-Geometrie, auch die Rufleuchte im Tisch-/Deckelkopf. Aktive Hinweise erhalten einen ruhig pulsierenden Lichtkranz; ausdrückliche Rufe zusätzlich eine kräftige Goldfläche. Orange/Rot folgen dem ältesten aktuellen Hinweis oder Ruf; ein neuer Ruf verdeckt keine ältere Warnung. Bei reduzierter Bewegung bleibt der Kranz statisch. Das Detailfenster verwendet die gemeinsamen Dialogfarben, Schließen-Aktion, Hintergrundunschärfe und 250-ms-Bewegung. Das Service-Menü teilt die Menüfläche und Aktionszeilen mit dem Tablet-Admin-Menü: goldene SVGs, Titel und zweite Zeile; „System“ zeigt einen Server-SVG und den aktuellen Verbindungsstatus.
Fertigmeldungen innerhalb von 2,5 Sekunden werden zu einer Ansage gebündelt; automatische Ansagen liegen mindestens 20 Sekunden auseinander. Es gibt keine dauernde Wiederholung. Erstabgleich, Rückkehr aus dem Hintergrund und Reconnect bleiben stumm. Der Ton benötigt die laufende, sichtbare Web-App; dies ist keine Push-Zustellung an gesperrte Geräte. Eingebetteter Service öffnet den Elternstationsdialog und erzeugt keine zweite Ansage.
Bar und Theke haben den runden Symbolbutton „Service rufen“ direkt im vorhandenen Header. Ein Tipp sendet einen neuen, allgemeinen Ruf an alle Servicegeräte, auch ohne aktuelle Fertigmeldung. Der Ruf nennt die Station und bleibt drei Minuten sichtbar. Serverbestätigung, Wiederholschutz und eine stationsweite Pause von 30 Sekunden verhindern Doppelrufe. Danach wechselt der Tablet-Button von „Gerufen“ wieder zu „Service rufen“; das dreiminütige Signal im Service läuft unabhängig davon weiter. „Ruf gesendet“ bestätigt die Serverannahme, nicht das Hören durch eine bestimmte Person. Manuelle Rufe bleiben bewusst flüchtig und werden nach Serverneustart nicht erneut zugestellt.
Stations-/Ofen-Fertig speichern weiterhin atomare Artikelsnapshots in pickup_entries.
list_pickups mit {mode:'notices'} liefert aktuelle Hinweise und offene Produktionsübergaben.
notice_expired_at begrenzt den aktiven Datenbestand, ohne picked_at zu setzen; Abrechnung und
Produktions-Undo bleiben davon unabhängig. Alte Abholaktionen und ihre Daten bleiben zur
Client-/DB-Kompatibilität vorhanden; neue Clients senden ausschließlich notwendige handoff-
Aktionen. Alle Geräte nach dem Update neu laden.
Bei Mango-, Maracuja- und Pink-Grapefruitschorle bestätigt die Bar nach der Übergabe noch „Fertig“; bei der Sprudel-Karaffe die Theke. Erst dann erscheint die Fertigmeldung am endgültigen Abholort. Diese Produktionsübergaben verfallen nicht nach drei Minuten. Reine Zulieferungen (Baileys/Espresso) erscheinen nicht als Gästeabholung; der Hauptartikel wartet auf die Zulieferstation. Routing, Papierdruck und Ofenablauf bleiben erhalten.
Für diese Clientänderung alle Geräte neu laden (CSS 20260914-6, pickups.js 20260914-5);
ein Serverneustart ist nicht erforderlich.
1. Systemarchitektur
Drei Stationen teilen sich ein Backend:
- Service/Kellner (
index.html): Bestellungen aufnehmen, Deckel führen, kassieren. - Bar-Station (
bar.html): Cocktails, Whisky und alle weiteren dort gelagerten Produkte. - Theken-Station (
theke.html): Zapf-/Kühlprodukte, ausgewählte Shots sowie Essen/Ofen.
Technischer Stack:
- Frontend: reines HTML/CSS/Vanilla-JS. Die Speise- und Getränkekarte (
GundDATA) liegt separat inproducts.jsund ist sowohl im Browser als auch in Node.js nutzbar. Ein seltenes nicht gelistetes Getränk wird dagegen kontrolliert als manuelle Position am Deckel gespeichert. - Backend (
server.js): Node.js mitexpress(Ausliefern der Dateien) undsocket.io(Echtzeit-Kommunikation über WebSockets). - Datenbank (
pub.db, SQLite): speichert Deckel (coasters), Bons (tickets), Belege (receipts), bestätigte/idempotente Abrechnungen (checkout_operations), bestätigte/idempotente Deckelumbuchungen (coaster_transfer_operations), unveränderliche Positionsstände erfolgreicher Abrechnungen (receipt_archives), die laufende Ofenbelegung (oven_slots), Arbeitsstunden (work_hours), Monatsabschlüsse (work_hours_month_closures), die Personenauswahl (work_hour_employees) und das globale Event-Menü (global_favorites). Dienstplanwochen, Einsätze, Einteilungen und Verfügbarkeiten liegen in denschedule_*-Tabellen. Das interne Reservierungsbuch wird inreservationsgespeichert und ist unabhängig von Dienstplan und Arbeitszeiterfassung. Kataloggetränke und ihre Preise stehen nicht in der DB, sondern inproducts.js; manuelle Positionen liegen mit Name, Einzelpreis und expliziter Station im jeweiligen Deckel, Bon und Belegarchiv. Häufig bestellte alkoholfreie Biermischgetränke sind als eigene Katalogartikel erfasst: Radler, saurer Radler und Colabier auf Basis von Licher Isotonisch sowie Russ und Colaweizen auf Basis von Benediktiner Weizen alkoholfrei. Sie folgen Größen und Preisen der jeweiligen normalen Varianten und laufen an Theke Hinten. - Stations-Routing (
station-routing.js): zentrale Logik, die entscheidet, ob ein Artikel zur Bar oder zur Theke gehört. Wird vonserver.js,bar.htmlundtheke.htmlgemeinsam genutzt. Kategorien, namentliche Ausnahmen und der Theke-Fallback bilden die reale Aufstellung ab. Kurstadt Kombo, Irish Car Bomb und Turbo Guinness werden anhand deklarativerpartstatsächlich auf beteiligte Stationen verteilt; die Bar sieht beim Irish Car Bomb nur den kompakten Zusatzauftrag „Baileys“, beim Turbo nur „Espresso“. In Deckel und Checkout zeigt der Service ebenfalls nur beschriftete Bestandteile; leere Routingteile erzeugen kein alleinstehendes+. Die drei Saftschorlen bleiben reine Thekenbons, während beide Karaffen reine Barbons sind; ihre weiteren Handgriffe folgen dem Papierbon über das Übergaberegal. Pink-Apple läuft vollständig an die Theke; Wild Times und Willy's Special bleiben normale Barbons ohne Systemhinweis. Details stehen indocs/PUB_LAYOUT.md(Abschnitte 5–6).
Servicekopf auf iOS: In der eigenständigen und eingebetteten iOS-Serviceansicht ist der Kopf fest am oberen
Fensterrand verankert. Ein automatisch an die tatsächliche Kopfhöhe angepasster Platzhalter
hält den Kartenbeginn frei; Safe Area, Suche und Kategorien bleiben enthalten. Nachfedern
am Dokumentrand ist dort unterdrückt. Android behält seinen Sticky-Kopf.
Bar und Theke verwenden ausschließlich auf iOS/iPadOS eine fixierte Stationsfläche mit
100dvh und Geräte-Safe-Areas; das Ofendock wird nicht zusammengestaucht. So bleiben
Ofen und Service in Fokus, Kombi und Bedienen innerhalb der sichtbaren Fläche.
Eine echte, fest verankerte station-status-surface stellt im äußeren iOS-Stationsfenster
die Kopffarbe für WebKits Statusleisten-Erkennung bereit. Sie bleibt auch bei Safe Area 0
sichtbar und wird beim tatsächlichen Themewechsel durch ein identisches neues Element ersetzt,
um die alte native Farbreferenz abzulösen. HTML und Body nutzen beide die Kopffarbe;
eine separate station-canvas bewahrt die bisherige Inhaltsfarbe.
Der äußere Hintergrund wechselt ohne Farbtransition gleichzeitig mit dem Kopf; auch die
Start-/Anmeldefläche verwendet dort bereits die Kopffarbe. --theme-frames im
Stations-Browsercheck prüft den Start und jeden Renderframe während der Themewechsel.
Die Android-Layoutregeln bleiben erhalten. CSS-Version: 20260913-9, Theme-Helfer 20260913-1.
Produktsuche: Namen, Kategorien und explizite Suchaliase werden wortweise durchsucht;
Zutatenbeschreibungen erzeugen keine Treffer. Groß-/Kleinschreibung und Akzente bleiben tolerant.
Wortanfänge verwenden die normale Schreibweise; fehlende oder zusätzliche Doppelbuchstaben
werden nur beim Vergleich ganzer Wörter toleriert (Guines findet Guinness).
Eistee findet die beiden Iced-Tea-Produkte, ohne Jägermeister einzubeziehen. Eindeutige
Synonymanfänge werden nur erweitert, wenn sie kein vorhandenes Katalogwort sind (Black).
Bewusst unterstützte Wortendungen erhalten zusammengesetzte Namen wie Hefeweizen, Colaweizen,
Weißweinschorle und Doppelkorn; Spritz umfasst auch Süß-/Sauergespritzter.
Touch-Bedienung: Die Bedienoberfläche unterdrückt Textmarkierung, Bildziehen und
Langdruck-Kontextmenüs auf Karten, Preisen, Mengen, Bons und übrigen Bedienelementen.
Eingabefelder behalten Auswahl, Einfügen und native Bearbeitung. Die Regeln stehen gemeinsam
in start-screen.css und gelten bereits beim App-Start sowie auf der Offline-Seite.
touch-controls.js steuert Fertig, Ofenaktionen und Suchergebnisse: Auslösen einmal
beim Loslassen, ohne Zeitlimit fürs Halten. Mehr als 12 px Fingerbewegung, Scrollen, ein zweiter
Finger, Abbruch, entfernte/gesperrte Ziele oder über 500 ms verspätet verarbeitete Ereignisse
verwerfen die Aktion. Native Folge-Klicks werden dort nicht erneut ausgeführt.
Preisbuttons behalten ihre bisherige Klick-/Pointer-Prüfung und ihre bisherige Darstellung;
der appweite Schutz vor Textmarkierung gilt auch auf der Produktkarte.
Suchtreffer sind ganzflächige Buttons; Text, Kategorie und Pfeil haben denselben Weg.
Der Suchfokus bleibt beim Aufsetzen erhalten, damit sich die Tastatur erst nach Auswahl schließt.
„Fertig“ besitzt mindestens 56 px Höhe und eine zusätzliche Trefferfläche von 8 px oben/unten
und seitlich (bei benachbarten Aktionen 6 px seitlich). Seine Rückmeldung verschiebt die
Trefferfläche nicht. Während des Haltens pausiert die Kartenanimation, und Bon-Updates ersetzen
oder verschieben die sichtbaren Karten erst nach Ende der Geste. Der Auftrag verwendet die
sichtbaren Ticket-IDs; später eingetroffene Runden bleiben offen. Bis zur Serverbestätigung steht
„Wird abgeschlossen …“ im gesperrten Button; Fehler geben ihn wieder frei.
Die Bar hat keine Ofenoberfläche und erhält keine Ofenstände. Ofengerichte einlegen, fertigstellen und Ofenbons drucken kann nur die Theke (sowie ein eigenständiger Adminzugang). Der Server sperrt Ofenänderungen auch für Bar-Stationen im Betrieb ohne Anmeldung. Normale Getränkebons der Bar behalten ihren bisherigen Abschluss und Druck.
An der Theke gilt: „In den Ofen“ verwendet denselben geschützten Touch-Pfad und hält die Zeile der Ofenwarteliste während des Drückens stabil. Die Etagenwahl erscheint immer, auch bei nur einer passenden freien Etage. Erst eine eigene Betätigung der Etage sendet den Ofenauftrag; ein nativer Folge-Klick des öffnenden Tipps darf keine Etage auswählen. Der Dialog erhält zunächst selbst den Fokus. Etagenbuttons, X, Abbrechen und „Andere Auswahl“ verwenden denselben Touch-Schutz einschließlich Scrollabbruch und Tastaturbedienung. Ein neuer Tipp auf den abgedunkelten Hintergrund schließt den Ofendialog weiterhin; Aufsetzen und Loslassen müssen beide außerhalb der Dialogkarte liegen. Der Folge-Klick des öffnenden Tipps schließt nicht. X/Abbrechen und Escape bleiben verfügbar. Die Ofenübersicht und Artikelliste behalten ihre berührten Flächen bis zum Loslassen; Timerberechnung und Thekenalarm laufen weiter. Ein währenddessen abgelaufener Timer macht aus „Etage öffnen“ keinen Druckauftrag. Vor der Auswahl oder dem Abschluss wird der aktuell belegte Ofenartikel mit der angezeigten Belegung verglichen. Entfernte, geänderte oder inzwischen neu belegte Plätze erfordern eine neue Auswahl. Die zweite Bestätigung im Etagenmodal bleibt bestehen; nach Ende der Geste erscheint wieder der aktuelle Ofenstand.
Produktionsabschluss: finish_ticket_station_batch meldet die beim Klick sichtbaren
Ticket-IDs einer Tisch-/Zahlungsgruppe gemeinsam an der betreffenden Station fertig. Der Server
sperrt alle IDs über dieselbe Queue wie Einzelabschluss/Rückruf und speichert die Statuswechsel
mit einem atomaren SQL-Update. Pro Klick entsteht ein gemeinsamer Stationsdruck; identische
Varianten werden addiert, andere Größen, Preise und Sonderwünsche getrennt gehalten.
Persistierte ready_stations verhindern einen erneuten Ausdruck bei Doppelklick, Retry oder Undo.
Später eintreffende Runden bleiben offen. Alte Clients können die einzelnen Events weiter verwenden;
für Sammeldruck müssen die Stationsseiten neu geladen sein.
Kassieren lässt offene Produktionsbons bestehen; ticket_payments speichert vollständig bezahlte
Ticketreferenzen unabhängig von der Produktion. Bezahlte Gruppen bleiben bei Wiederverwendung
desselben Tischnamens getrennt sichtbar.
Gebündelte Bonanzeige: Bar und Theke verarbeiten eingehende Ticketereignisse sofort, bereiten ihre Bonliste aber höchstens einmal pro angefordertem Bildschirmframe vor. Der Bonzähler und akustische Signale bleiben auch während einer Berührung unmittelbar aktuell. Während einer Bonberührung entfällt diese Vorbereitung; Loslassen oder Abbruch zeichnet den neuesten Stand nach. Anfangsbestand und lokale Bestätigungen bleiben unmittelbar und ersetzen gegebenenfalls einen schon vorgemerkten Render-Aufruf. Markup, CSS, Animationen, Sounds, Gruppierung, Pending-/Offline-Sperren und die sichtbaren Abschluss-IDs bleiben erhalten. Messwerte und Aussagegrenzen stehen in docs/STATION_PERFORMANCE.md.
Verdeckte Service-Anzeige: Im Fokusmodus von Bar/Theke stellt der eingebettete Service Live-Updates der Tischübersicht und des aktiven Deckels zurück. Beim Wechsel zu Kombi/Bedienen holt er den aktuellen Stand synchron vor der Bildausgabe nach, ohne zusätzliche Kartenanimation. Die Service-App bleibt geladen; Socket, unmittelbare lokale Speicherung, Bestätigungen, Offline-Abläufe und Stationsdialoge arbeiten weiter. Eigenständiger Service und sichtbare Service-Ansichten behalten ihre Live-Aktualisierung. Messung und Speicheranalyse: docs/SERVICE_BACKGROUND_PERFORMANCE.md.
Kommunikation: Sendet der Kellner in index.html einen Bon, geht das Event per
Socket.io an den Server. Dieser speichert den Bon in pub.db und sendet sofort
ticket_added an die passenden Stationsgeräte (Bar und/oder Theke, je nach Artikeln).
Der Server filtert zusätzlich den initialen Bonbestand beim Verbinden nach der physischen
Station; damit kann auch ein veralteter Browserstand keinen reinen Barbon an der Theke anzeigen.
Ergibt die Produktsuche keinen Treffer, kann der Service über Als diverses Getränk buchen
einen Namen, einen positiven Einzelpreis, die Menge und zwingend Bar oder Theke erfassen.
Die Preiseingabe akzeptiert Komma oder Punkt und den schnellen Ziffernweg: 9 wird 9,00 €,
98 wird 98,00 € und die dritte Ziffer formatiert direkt im Feld, etwa 999 zu 9,99;
eine Live-Anzeige bestätigt zusätzlich den Buchungswert.
Der Server normalisiert die Position getrennt vom Katalog, lehnt Katalognamen mit frei gesetztem
Preis ab und routet ausschließlich an die gewählte Station. Solche Positionen bleiben bei
Mengenänderung und Umbuchung eindeutig erhalten und laufen normal durch Bon, Kassieren und Beleg.
Der Service arbeitet tisch-zuerst: Nach Anmeldung, Bonversand und vollstaendiger Abrechnung
oeffnet sich verbindlich Tisch waehlen; ohne eindeutige Tisch-, Gast-/Platz- oder
Sofortverkaufswahl nimmt die Produktkarte keine Buchung an. Eine Auswahl fuehrt direkt in den
Bedienbereich mit Suche, Kategorien und Produkten. Ein Socket-Reconnect synchronisiert nur den
Serverstand und veraendert eine laufende Bedienansicht nicht; die Pflichtauswahl wird ausschliesslich
beim ersten App-Start oder durch einen fachlichen Abschluss ausgeloest. Auf Smartphones bis 700 px
ist die Auswahl eine vollhohe 100dvh-Arbeitsflaeche. Sie kann per X geschlossen werden, damit sich
die Karte etwa fuer eine schnelle Preisauskunft ansehen laesst; ohne Tisch bleibt jede Buchung gesperrt.
Beim normalen Einstieg ueber Tische schliesst nur das X zur Karte; der Zurueck-Pfeil erscheint
ausschliesslich beim Tischwechsel aus einem geoeffneten Deckel und fuehrt genau dorthin zurueck.
Die Tischübersicht gleitet mit einer transform-Animation von unten herein. Beim direkten
Öffnen ist die innere Ansicht bereits positioniert; beim Wechsel aus einem offenen Deckel
wechseln Deckel und Tischwahl mit einer dezenten Überblendung (180 ms).
Die gemeinsame theme-shell.js setzt die Plattformkennung für Android sowie iPhone/iPad
(einschließlich iPad-Desktopkennung mit mehreren Touchpunkten) vor dem ersten Layout.
Unter iOS sperrt eine offene Tisch-/Deckelansicht das Scrollen der verdeckten Produktseite.
Nur die aktive innere Ansicht ist per Geste scrollbar; ihre Listen begrenzen Scrollweitergabe
am Rand. Scrollpositionen und Überblendung bleiben erhalten. Ein nur unter iOS installierter
Klassen-Observer fixiert bei .coaster-sidebar.open den
Seitenhintergrund im Viewport. Er überträgt die Kartenposition vorübergehend auf den nicht per
Geste scrollbaren Body und stellt sie beim Schließen ohne Scrollanimation wieder her.
Das erhält die Kopfposition und vermeidet die unzureichende reine Root-Overflow-Sperre von iOS-PWAs.
Keine Touch-Handler, Polling oder Eingriffe in die Buchungslogik.
Prüfaufbau und Grenzen: docs/SERVICE_SCROLL_ISOLATION.md.
Gestaltung und Dauer der äußeren Öffnungsbewegung bleiben unverändert. Das gilt auch für den
Direkteinstieg in einen einzelnen Deckel: Ein unveränderter Deckel verwendet seine vorhandenen
Artikelzeilen. Mengen werden gezielt aktualisiert; Änderungen an anderen Tischen bauen den
aktiven Detaildeckel nicht neu auf. Übersicht und lokale Speicherung bleiben dabei aktuell.
Die Tischübersicht behält ihre Raumstruktur und vorhandene Deckelkarten bei Live-Updates.
Neue sichtbare Deckel und Sofortverkäufe erscheinen mit dem bestehenden Karten-Eintritt;
Nachbarkarten rücken weich nach. Beträge und Tischbelegung werden direkt aktualisiert,
ohne geöffnete Räume, Fokus oder Scrollposition zurückzusetzen. Verdeckte/neue Offscreen-Karten
und reduzierte Bewegung lösen keine Live-Animation aus.
Remote-Änderungen der Übersicht werden pro Bildschirmframe zusammengefasst; reine Wertänderungen
aktualisieren nur betroffene Karten und Tischbadges. Eine geschlossene Übersicht zeichnet erst
beim nächsten Öffnen aus dem aktuellen Datenstand. Daten und Revisionen werden weiterhin sofort verarbeitet und lokal gesichert.
Aktive Deckeldetails werden sofort gezeichnet, wenn sie sichtbar sind. Hinter der Übersicht oder
bei geschlossener Arbeitsfläche wird nur der Kontextanker aktualisiert; beim Öffnen erscheinen
die neuesten Artikelwerte. Eine vollständige Mengenaktualisierung verwendet die bereits
berechneten Artikelgruppen wieder. Messaufbau und Aussagegrenzen zur Scrollleistung stehen in
docs/SCROLL_PERFORMANCE.md.
Die Theke aktualisiert während eines Ofen-Countdowns nur Timertexte und zugängliche Labels.
Etagen und offene Detail-/Bestätigungszeilen bleiben erhalten, bis sich Belegung, Fertigzustand
oder Bedienbarkeit ändern. Der Ofenalarm klingt ausschließlich an der Theke; neue Bestellungen
klingeln weiterhin an beiden Stationen. Ofenaktionen bleiben serverbestätigt.
Der feste Service-Dock ist eine zweiteilige Arbeitsleiste: Tische oeffnet die Uebersicht mit
einem Tipp; der breite gruene Kontextanker nennt den aktiven Deckel, Betrag und Positionszahl und
oeffnet selbst dessen Detail. Nach der Auswahl bleibt der Kontext sichtbar, waehrend Tischwechsel
und Deckel jeweils direkt erreichbar sind. Auf Smartphones bis 520 px stehen beide Anker 8 px
ueber dem Safe-Area-Rand; in der installierten Android-Service-App gilt mindestens 24 px
Abstand zum Viewportrand (Fullscreen/Standalone, ohne Stationseinbettung). Der eigenständige iPhone-Service bis 520 px setzt beide Anker 2 px über die Safe Area
(6 px tiefer als bisher); Android und eingebettete Ansichten behalten ihre Abstände. Beide Buttons sind mindestens 60 px hoch und nutzen 16-px-Rundungen sowie eine dezente
rundum sichtbare Elevation. Beide Anker besitzen getrennte unsichtbare Hit-Areas:
20 px nach oben und 12 px nach aussen/unten schuetzen die darunterliegenden Produktpreise vor
Fehltipps; in der Mitte teilen sie sich die Luecke ohne Ueberlappung.
Normale Produktpreise sind semantisch native Buttons, behalten aber unverändert ihre bisherige
Gestalt. Eine Buchung akzeptiert nur eine aktuelle Pointer-Geste ohne relevante Finger- oder
Seitenscrollbewegung; nach einem blockierten Browser nachgelieferte beziehungsweise beim Scrollen
verschobene Eingaben werden verworfen. Der schnelle Buchungspfad aktualisiert nur den sichtbaren
Dockkontext und synchronisiert den vollständigen Deckel weiter revisionsgeschützt im Hintergrund.
Mengenänderungen im geöffneten Deckel ersetzen weder Liste noch gedrückten Button, sondern ziehen
bei ungesendeten Positionen Zeilenwert, Summe und Status direkt nach. Zusätzliche Mengen einer
bereits gesendeten Position bleiben intern korrekt für den nächsten Bon vorgemerkt, erzeugen während
der Bedienung weder eine zweite sichtbare Zeile noch eine Fluganimation. Identische Varianten
bleiben auch nach dem Senden über mehrere Bestellrunden in einer Zeile zusammengefasst;
Größe, Preis, Auswahl, Sonderwunsch und manuelle Station bleiben unterscheidbar.
Die einzelnen gesendeten Positionen samt Ticketreferenzen bleiben intern erhalten. Jede neue Buchung
setzt die sichtbare Zeile nach oben; auch bei einer zusammengefassten Zeile steigt die Menge dort
direkt. Ein
zahlenbezogenes Badge wie 10 Neu trennt die noch nicht gesendete Menge eindeutig vom gesendeten
Teil. Die Reihenfolge folgt dem gespeicherten last_added_at der jüngsten Ergänzung, unabhängig
von den für Bedienung und Abrechnung verwendeten Array-Indizes. Sie bleibt nach Senden, Neuladen
und Gerätewechsel erhalten. Manuelle Getränke und auf einen Zieldeckel umgebuchte Mengen folgen
derselben Regel. Minus verändert die Reihenfolge nicht aktiv; entfällt die letzte neue Teilmenge,
fällt die bisher zusammengefasste Zeile auf den Zeitpunkt ihrer verbleibenden Buchung zurück.
Alte Einträge ohne Sortierinformation behalten untereinander die vorhandene Reihenfolge.
Solange dieser neue Anteil besteht, erscheint an derselben Zeile der bekannte Notizstift und
bearbeitet ausschließlich die noch ungesendete Menge dieser Variante. Bei mehreren Stück
zeigt der Zusatzdialog eine Mengenauswahl, standardmäßig für alle noch ungesendeten Stück.
Die Schnellwahl ist in vier unabhängig aufklappbare Bereiche gegliedert. Beim Öffnen
des Stift-Dialogs sind sie geschlossen; gewählte Zusätze und direkte Gläseranzahlen bleiben
in den Bereichsköpfen sichtbar. Freitext und Speichern bleiben direkt erreichbar.
Die Schnellwahl enthält auch Mit Gurke unter Beilagen & Früchte und Mit Löffel
unter Zubehör; beide sind unabhängig von Zitrone beziehungsweise Strohhalm wählbar.
Mehrere Zusätze können gleichzeitig gelten; eine bewusst gewählte Teilmenge erhält eigene
Wünsche. Bereits gesendete Mengen bleiben unverändert. Der Zusatzdialog öffnet am Anfang
mit Fokus auf dem Dialog, sodass die Tastatur erst nach Antippen des Freitextfelds erscheint.
Auf Android folgt ausschließlich dieser Dialog der sichtbaren Fläche über der Tastatur;
beim Schließen werden die temporären Größen und Listener entfernt. Erst ein abweichender Zusatz wie
Mit Eis macht sie danach als eigene Neu-Zeile sichtbar, damit der Zusatz eindeutig zugeordnet
bleibt.
Das vorhandene Neu-Badge ist zugleich die kompakte Statusauswahl der Position. Ein Fingertipp
öffnet nur Normal senden und Nachbon; bei einer Menge groesser eins lässt sich dort zusätzlich
festlegen, wie viele bereits ausgegeben wurden. Der Stift bleibt dadurch ausschließlich für
Artikelzusätze zuständig. Vor dem Senden ersetzt Nachbon das
Neu-Badge, ohne eine weitere Aktion in der schmalen Smartphone-Zeile einzuführen.
Nachbon kennzeichnet eine bereits direkt ausgegebene Position, die nur noch auf dem Deckel
verbucht werden muss. Beim Senden wird die interne Kennzeichnung verbraucht; die Position gilt
normal als gesendet und bleibt vollständig kassierbar, erhält aber keine ticket_id und erzeugt
weder Stationskarte noch Produktionsdruck oder Ofeneintrag. In einer gemischten Runde gelangen
ausschließlich die normalen Positionen an Bar und Theke. Auch nach dem Senden, im Kassierdialog und
auf dem Kundenbeleg bleibt kein Nachbon-Hinweis sichtbar. Der Server filtert die exakte interne
Kennzeichnung zusätzlich defensiv aus eingehenden Produktionsaufträgen, damit auch ein noch nicht
aktualisiertes Servicegerät keinen bereits ausgegebenen Artikel erneut in die Produktion schickt.
Die Tischübersicht beginnt mit Bereiche: Pub, Bar und Biergarten zeigen ihr Symbol, den
Bereichsnamen und die Anzahl offener Tischdeckel sowie Gäste-/Platzdeckel. Bereichssummen und
zusammengefasste Hinweise auf ungesendete Artikel entfallen.
Die Zahlen beschreiben Deckel, keine physische Belegung oder Reservierungen. Leere benannte
Gäste-/Platzdeckel zählen als verfügbare Deckel mit; leere Bereiche zeigen Keine offenen Deckel.
Feste Tische erscheinen ausschließlich im jeweiligen Bereich in der Reihenfolge aus pub-tables.js;
benannte Gäste und freie Plätze folgen im Abschnitt Gäste / Plätze, auch solange sie leer sind.
Alte zugeordnete Sonderplätze bleiben dort erreichbar, Deckel ohne Bereich unter Sonstige.
Eine zweite Liste aller offenen Tischdeckel entfällt. Sofortverkäufe bleiben separat sichtbar.
Der aufgeklappte Bereich und die Scrollposition bleiben beim erneuten Öffnen erhalten. Der
Bereich wird zusätzlich pro Browser-Tab in sessionStorage gemerkt; bewusstes Zuklappen bleibt
ebenfalls erhalten. Ohne verfügbaren Browserspeicher funktioniert die Auswahl im laufenden Fenster.
Geschlossene Bereiche bauen ihre Tischlisten erst beim ersten Aufklappen auf und aktualisieren
vorhandene Inhalte erst beim Wiederöffnen. Ihre Kennzahlen bleiben live. Remote-Updates bündeln
sich weiterhin pro Frame und berechnen nur die geänderten Deckelsummen neu; unveränderte
Deckelkarten und Tischbuttons bleiben bestehen. Daten, Revisionen und lokale Sicherung bleiben sofort aktuell.
Namen dürfen umbrechen, Beträge bleiben einzeilig; ungesendete Mengen erscheinen als … neu
auch direkt am festen Tisch. Tisch- und Gastbuttons im Bereich sind mindestens 48 px hoch.
Die bestehende untere Aktionsleiste enthält Sofortverkauf starten und Gast / Platz eröffnen.
Bei Sofortverkaeufen sind ausschliesslich belegte, nummerierte Plaetze sichtbar. In der unteren
Aktionsleiste gibt es genau eine Aktion Sofortverkauf starten, solange im weiterhin auf zwoelf
begrenzten Pool
ein Platz frei ist. Die serverseitig serialisierte Vergabe verhindert doppelte Reservierungen auf
parallelen Servicegeraeten; leere DB-Zeilen bleiben unsichtbar und wiederverwendbar. Der fruehere
Schnell-Deckel wird weiterhin verlustfrei nach Sofortverkauf 1 migriert.
Über Umbuchen kann der Service wahlweise den ganzen Deckel oder einzelne Artikel und Mengen auf
einen anderen Tisch übertragen. Ein bereits belegter Zieltisch wird dabei ergänzt; seine vorhandenen
Positionen bleiben erhalten. Quelle und Ziel werden gemeinsam revisionsgeschützt und atomar in
SQLite geändert, die Operation ist über ihre ID wiederholbar und wird nicht offline vorgemerkt.
Offene Bar-/Thekenbons werden weder zurückgerufen noch neu erzeugt. Liegen danach alle Positionen
eines laufenden Bons eindeutig auf dem Zieldeckel, wechseln dessen Stationskarte und ein eventuell
laufender Ofeneintrag auf den neuen Tischnamen. Das gilt auch dann, wenn die letzte Bonposition
bereits vollständig im Ofen liegt und deshalb kein offener Ticket-Datensatz mehr existiert; der
spätere Ofenbon druckt den neuen Tisch. Bei einer Teilung desselben Bons über zwei Deckel bleibt die
Produktionskarte als eine Einheit unter ihrem bisherigen Tischnamen bestehen.
Bar und Theke gleichen sichtbare Bon-Karten inkrementell ab: Ein Ereignis ersetzt nur den
betroffenen Deckel, während unveränderte Karten und laufende Touch-Interaktionen stabil bleiben.
Die gemeinsame Arbeitsfläche station-workspace.js startet Bar und Theke im Bon-fokussierten
„Willy Fokus“. Der mittige Stationsname bildet dort den ruhigen Anker; der einheitliche, offene
Modusgriff aus kleinem Ansicht und dem aktuellen Modus steht getrennt links und öffnet Fokus,
Kombi und Bedienen als materielles Dreier-Triptychon. Das aktive Feld verwendet die vorhandene
dunkelgrüne Akzentfläche mit ruhiger grüner Kante; Gold bleibt den kleinen Orientierungszeichen
vorbehalten. Light und Dark bleiben im gemeinsamen Markensystem. Der Lautsprecher ist ausschließlich
eine Bar-/Theke-Aktion; die eigenständige Service-App zeigt nur Light/Dark. Im Fokus sitzt der
Stationslautsprecher direkt beim Theme-Schalter. In Kombi und Bedienen erscheint dieselbe
Stationssteuerung innerhalb des eingebetteten Service-Kopfes und ruft weiterhin Bar beziehungsweise
Theke auf. Lautsprecher und Light/Dark bilden mit nur 6 px Abstand ein klares Paar; der Tonstatus
wird zwischen Station und eingebettetem Service synchron gehalten. station-audio.js prüft beim
bewussten Aktivieren den Bonton, an der Theke zusätzlich den Ofenton tatsächlich: Erst nach erfolgreichen
Wiedergabestarts gilt Ton als bereit. Scheitert eine spätere Wiedergabe, wechselt der Schalter sichtbar
auf den orangefarbenen Zustand Ton reaktivieren. Nach einem echten Socket-Reconnect signalisiert
die Station genau einmal neu hinzugekommene relevante Bons; der normale Erstbestand bleibt lautlos.
Ein zweiter Klick auf denselben Ansichtsgriff schließt das Menü
deterministisch, unabhängig vom nativen <details>-Umschalten.
Im Fokus stehen darunter die verständlichen Darstellungen Groß, Balance und Überblick.
Ein gemeinsamer Faktor skaliert den gesamten Bon einschließlich Geometrie und Innenabständen
proportional; eine oder zwei Spalten teilen die gesamte verfügbare Bonfläche ebenso vollständig wie
drei Spalten. Groß nutzt die breite Karte als zweispaltiges Artikelmanifest, statt Inhalt in eine
künstliche schmale Lesespur zu zwingen; Balance bleibt einspaltig je Bon, aber deutlich größer.
Die Reihenfolge steht links im Kopf: die erste Nummer dunkelgrün, weitere goldfarben.
„Jetzt“ entfällt; vollständig bezahlte Produktionsbons zeigen daneben „✓ Bezahlt“ mit denselben
Success-Tokens wie „Fertig“. Das Zeitsiegel zeigt die Wartezeit ab „1 Min.“ in der ursprünglichen
Schriftgröße, mit fetter Zahl; ab 15 Minuten wechseln Schrift und Kontur zur Warning-Farbe.
Kopf- und Bonhöhe bleiben gleich. Lange Bereichsnamen werden vor der Tischnummer gekürzt.
Gelesen wird links nach rechts, dann in die nächste Zeile; die älteste offene Runde steht zuerst.
Jede Position folgt als Produktionszeile der festen Folge Menge, Produkt und Größe. Die Menge ist
eine große tabellarische Zahl an einer schmalen grünen Leitkante; die Größe schließt die Zeile als
kräftiger Goldmarker auf ruhiger Gold-Tint-Fläche ab. Eine feine, nach rechts dichter werdende
Gold-Tint-Linie verbindet Produkt und Größe, ohne eine zusätzliche Box zu bilden. Identische
Varianten aus mehreren Runden werden nur visuell addiert; unterschiedliche Größen, Notizen und
Ofengerichte stehen separat in der Warteliste am Ofen.
Unter 1080 px beziehungsweise 720 px begrenzt die Ansicht die wirksame Darstellung sicher auf
Balance oder Groß. Modus und gewünschte Darstellung bleiben getrennt pro Bar-/Thekegerät
im Browser gespeichert. Kombi zeigt Bons in einer schmaleren
clamp(420px, 32vw, 480px)-Spalte und Service parallel. Ihr Stationskopf verdichtet Admin zum
bekannten Schildsymbol, hält aber Station, Modus und roten Bon-Zähler vollständig sichtbar.
Bedienen gibt der geladenen
Service-App die volle Stationsbreite. In der vorhandenen Kopfzeile bilden der identische
Ansichtsgriff und Offene Bons / Zahl zwischen Wordmark und Statusaktionen eine offene,
gemeinsame Informationsgruppe; nur das Bon-Symbol hebt einen Zuwachs kurz hervor. Erst unter
760 px weicht die Gruppe mangels horizontalem Platz in die reservierte Zeile aus. Solange der
eingebettete Deckel-/Gastdialog offen ist, wird nur diese Navigation ausgeblendet.
Der eigenständige Service-Kopf bietet Reservierungen mit dem bekannten Kalender-Symbol und der Zahl der heute
noch erwarteten Gruppen. Im eingebetteten Service entfällt dieser doppelte Einstieg; dort gilt Admin → Reservierungen. Happy Hour läuft ausschließlich automatisch von 17:00 bis 20:00 Uhr
in Europe/Berlin. Der manuelle Schalter und der frühere „HH aktiv“-Hinweis unter dem Logo
entfallen beide; erkennbar ist die Happy Hour ausschließlich an den umgeschalteten Preisen.
An der Theke bleibt die Ofenbelegung in allen drei Arbeitsflächen operativ sichtbar. Jede der vier deutlich
nummerierten Etagen hat vier Plätze: Pizza/Flammkuchen belegt drei, ein Baguette einen. Eine feste
Überschrift und Legende oberhalb der Karten entfallen; stattdessen sitzt ein Ofensymbol direkt im
grünen Etagenrücken über der Nummer. Das sichtbare Wort Plätze ordnet die vier Gold-/Grün-Segmente
weiterhin als Kapazität ein – nicht als Zeitbalken. Damit passen exakt Pizza plus ein Baguette oder
bis zu vier Baguettes. Eine freie Etage sagt nur einmal Frei; ihre leere Segmentleiste zeigt die
verfügbare Kapazität ohne zweite Textwiederholung. Freie Etagen sind auf 66 px eingeklappt;
belegte Etagen bleiben unabhängig von einem oder vier Artikeln fest 142 px hoch. Pro Etage steht
der fertige beziehungsweise zeitlich nächste Artikel mit Gast, Artikel und Timer im Vordergrund.
Weitere Artikel bleiben als kleine Typ-/Timer-Chips sichtbar. Ein Tipp öffnet alle vollständigen
Artikelzeilen in einem Etagenblatt über der Arbeitsfläche, ohne Bons oder Ofen nach oben zu schieben.
FERTIG bleibt dort und im priorisierten Artikel als klare Aktion antippbar.
Ein noch laufender Artikel kann im Etagenblatt bewusst über die bereits vorhandene große
Ofen-Artikelzeile vorzeitig abgeschlossen werden; jede Detailzeile nennt eindeutig
Für Bon auswählen. Vor der Auswahl bleibt die vorhandene .modal-actions-Leiste vollständig
ausgeblendet. Der erste Tipp wählt den Artikel nur aus. Dasselbe Etagenmodal wechselt dann zu
Auswahl prüfen, zeigt ausschließlich den gewählten Artikel und fragt mit den vorhandenen Aktionen
Andere Auswahl und Bon drucken nach. Eine vorhandene Trennlinie sowie zusätzlicher Abstand
lösen die Aktionen klar von der ausgewählten Artikelzeile. Das gilt auch für bereits abgelaufene
FERTIG-Zeilen im Etagenblatt. Erst dann beendet der serverbestätigte Ofenabschluss den Timer,
räumt den Platz und
druckt den Ofenbon mit dem kanonischen Einzelpreis des fertigen Artikels. Nach dem bestätigten
Abschluss erscheint keine zusätzliche Erfolgsmeldung; der
herauskommende Bon ist die Rückmeldung. Nur ein tatsächlicher Druckfehler öffnet die vorhandene Karte
Druck nicht bestätigt mit Druckerstatus prüfen und Schließen.
Baguettes laufen 12 Minuten, alle Flammkuchen und Pizza jeweils 15 Minuten.
Die Etagenwahl nennt den Bedarf des Artikels, hält auch Sperrgründe lesbar und sperrt unpassende
Kombinationen. Ab 961 px stehen alle vier Etagen in einer Reihe; bis 960 px stehen zwei Etagen je
Reihe, mobil eine. Dort bleibt der Ofen auf 36dvh begrenzt und
intern scrollbar, damit die Bonfläche den größeren Arbeitsanteil behält.
Der zentrale Systemstatus in der eingebetteten Serviceansicht zeigt Verbindung, Infrastruktur und
die Zahl verbundener Service-, Bar- und Thekegeräte. Ohne Serververbindung bleiben Bons,
der Dauer-Glow des ältesten Bons und der Ofenstatus sichtbar; nur serverändernde Aktionen wie
Fertig, In den Ofen, Ofenabschluss und Rückgängig sind nativ gesperrt. Nach Wiederverbindung
werden sie automatisch wieder freigegeben. Ofenaktionen und Bonabschlüsse warten auf einen
Server-Ack und zeigen statt eines rein optimistischen Abschlusses eine sichtbare Fehlermeldung.
Wird ein Bon offline oder ohne Ack vorgemerkt, versucht der Service die Übertragung höchstens
15 Minuten lang. Danach wird er niemals automatisch an Bar oder Theke ausgespielt. Beim nächsten
eindeutigen Serverkontakt prüft der Client die Ticket-ID gegen einen dauerhaften Zustellnachweis:
Bereits empfangene Bons gelten als erledigt, tatsächlich fehlende erscheinen persistent als
Bon nicht gesendet. Erst Auf Deckel prüfen stellt deren Artikel bewusst als Neu wieder her;
Verwerfen entfernt den lokalen Altbon. Einträge ohne verlässlichen Zeitstempel gelten ebenfalls
als abgelaufen.
Ein versehentliches „Fertig“ lässt sich an Bar und Theke fünf Sekunden lang serverbestätigt
rückgängig machen.
Bar und Theke zeigen diese Frist als kompakte einzeilige Leiste im normalen Layout unter der
Bonliste. Sie reserviert 62 px, statt Karten zu überlagern; neue Bestellungen bleiben vollständig
sicht- und bedienbar. Im Service kann ein gerade gesendeter Bon fünf Sekunden lang zurückgeholt
werden, solange Bar oder Theke seine Bearbeitung noch nicht begonnen hat. Der Service-Toast nutzt
dafür eine kompakte einzeilige Leiste mit der vorhandenen 44-px-Schließen-Aktion. Das neutrale
X blendet ausschließlich die Leiste aus und beendet die lokale Rücknahmemöglichkeit; es sendet
niemals einen Rückholbefehl. Ausschließlich der räumlich getrennte Danger-Button
Rückgängig fordert die serverbestätigte Rückholung an.
Die Hinweisfläche hat auf allen Plattformen wieder ihre ursprüngliche Trefferfläche;
Rückgängig, X und Countdown arbeiten unabhängig von der Hintergrund-Scrollsperre.
Rückholen und Stationsstart werden pro Bon serverseitig serialisiert, sodass genau eine Aktion
gewinnt. Ein vollständiger Checkout beendet außerdem einen noch sichtbaren Stations-Countdown
sofort – auch wenn der fertige Bon als unveränderliche Historie in der Datenbank erhalten bleibt.
Kurzmeldungen wie „Bestellung wird bereits bearbeitet“, ein lokal vorgemerkter Offline-Bon oder
„WLAN wieder da“ erscheinen in einem eigenen ruhigen Statusfenster. Meldungen ersetzen sich
atomar; ein ablaufender älterer Timer kann keine neuere Meldung ausblenden.
Die Abrechnung zeigt standardmäßig die Komplettzahlung; Artikelmengen erscheinen erst im bewusst
gewählten Modus „Getrennt zahlen“. Die Aktion erscheint nur ab zwei gesendeten Stück auf dem Deckel
und bleibt zwischen aufeinanderfolgenden Teilzahlungen aktiv, solange noch mindestens zwei
gesendete Stück offen sind.
In der Split-Auswahl werden identische gesendete Positionen aus mehreren Bestellrunden zu einer
Mengenzeile zusammengefasst. Variante, Preis, Kombo-Auswahl und Zusätze müssen dafür übereinstimmen;
Normal- und Happy-Hour-Preis bleiben damit getrennt. Die Auswahl wird intern weiterhin auf die
ursprünglichen Deckelzeilen verteilt.
Kassierbar sind in beiden Modi ausschließlich bereits gesendete Positionen. Noch ungesendete
Neu-Mengen werden im Dialog nicht als Zahlbetrag angeboten, erscheinen aber transparent im
Hinweis und in der verbleibenden Deckelsumme. „Alles kassieren“ heißt bei einem gemischten Deckel
deshalb sichtbar „Gesendetes kassieren“; die ungesendeten Mengen bleiben anschließend auf dem
offenen Deckel. Besteht ein Deckel nur aus ungesendeten Positionen, ist Kassieren nativ deaktiviert.
Beim Kassieren sendet der Service eine einmalige Checkout-ID, den Deckelnamen, die Belegwahl und
bei einer Teilzahlung nur Zeilenindex und Menge – niemals Produkt- oder Preisdaten.
Die Bewirtungsbeleg-Auswahl ist eine eigene markenkonforme Checkbox: Aktiv zeigt sie auf dem dunkelgrünen Pub-Hintergrund einen goldenen Haken statt des nativen schwarzen Browser-Hakens. Auf dem gedruckten Bewirtungsbeleg werden identische Positionen aus mehreren Bestellrunden zu einer Mengenzeile zusammengefasst. Artikel, Größe, Preis, Kombo-Auswahl und Zusatz müssen dafür übereinstimmen; unterschiedliche Varianten und Preise bleiben getrennt.
Der Server lädt und kanonisiert den gespeicherten Deckel und bestätigt erst nach einer gemeinsamen
SQLite-Transaktion für Deckel, offene Bons, Beleg und Idempotenz-Historie. Erst diese Bestätigung
ändert die Service-Ansicht. Währenddessen zeigt die gesperrte Hauptaktion eindeutig
„Wird bestätigt …“; nur bei einem wirklich unklaren Ergebnis bietet sie „Erneut bestätigen“ an.
Bei einer unklaren Verbindung wird dieselbe Checkout-ID wiederholt;
dadurch kann dieselbe Zahlung weder doppelt abgezogen noch doppelt gedruckt werden. Der Server
wendet die Gesendet-Sperre selbst an und lehnt auch manipulierte Teilzahlungen auf ungesendete
Zeilen ohne Deckeländerung ab. Nach erfolgreicher Zahlung endet außerdem die Rückgängigfrist der
tatsächlich bezahlten, serverseitig diesem Deckel zugeordneten Bons sofort.
Jeder neue bestätigte Voll- oder Teilcheckout speichert außerdem einen kanonischen Positions-Snapshot
im Abschlussarchiv – unabhängig davon, ob ein Papierbeleg gewählt wurde. Über Admin →
Abschlüsse zeigen Bar und Theke standardmäßig denselben gesamten Pub: alle archivierten Kassierungen,
Artikel und Umsätze aus beiden Stationen. „Gesamt · Theke · Bar“ ist auf jedem Tablet frei wählbar.
Der Bereich filtert Artikel, Umsatzanteile, Produktwahl und sämtliche Auswertungen; gemischte Bons
erscheinen in beiden Einzelbereichen, ihre Beträge zählen jeweils nur anteilig. Komboumsätze zählen
einmal beim Hauptartikel beziehungsweise der gewählten Biersorte. Details nennen den ursprünglichen
Gesamtbetrag zusätzlich. Beim erneuten Öffnen ist immer „Gesamt“ gewählt. Die Chef-PIN wird bei jedem Öffnen neu geprüft.
Eine eigene Archivfreigabe gilt nur für dieses geöffnete Fenster und höchstens 15 Minuten;
Schließen widerruft sie auch serverseitig. Arbeitsstunden-/Dienstplanfreigaben öffnen das Archiv
nicht. Nach Serverupdate müssen die Appseiten neu geladen werden.
Die 30 neuesten passenden Vorgänge sind durchsuchbar; Kennzahlen zählen sämtliche Treffer im
gewählten Zeitraum. Gleiche Artikel mit identischer Größe, Preis und Auswahl werden für die
Detailansicht konsolidiert, gespeicherte Snapshots und getrennte Teilzahlungen bleiben erhalten.
Der Produktfilter zeigt den tatsächlichen Produktumsatz zusätzlich zum Gesamtbetrag der betreffenden
Bons beziehungsweise deren Bereichsanteil. Ohne Produktfilter steht der Umsatz des gewählten Bereichs im Mittelpunkt. Top/Flop 10 nach Menge und Umsatz, eine
vollständige Artikeltabelle je Größe, die Liste nicht verkaufter Katalogartikel und
Kassierungen je Uhrzeit stehen in einer aufklappbaren Auswertung. Ranglisten und Artikeltabelle
trennen die Größen, der Produktfilter fasst sie zusammen. Die Hauptansicht konzentriert
sich auf drei Kennzahlen: Umsatz, Abschlüsse und Durchschnitt je Abschluss. Artikelmengen,
Erklärungen und genaue Vergleichszeiten liegen in den drei aufklappbaren Bereichen
„Artikel & Größen“, „Tage & Uhrzeiten“ und „Hinweise & Ausblick“.
Die Wochenansicht zeigt sieben Betriebstage mit Umsatzspitze und dem Tag mit den meisten
Abschlüssen; laufende Tage sind markiert, zukünftige Tage bleiben ohne Ist-Wert. Abschlüsse sind
Zahlungsvorgänge und erlauben keine Aussage über Gästezahl oder tatsächliche Belegung.
Unter den Wochendetails berechnet die App lokal einen Umsatzrichtwert für die nächsten sieben
Betriebstage: Median vergleichbarer Wochentage der letzten acht Wochen, mindestens vier erfasste
Verkaufstage je Wochentag. Die Spanne zeigt historische Min-/Max-Werte, kein Prognoseintervall.
Erster Archivtag, laufender Tag und Tage ohne Pub-Archivdaten liefern keine Vergleichswerte;
bei nachgewiesenen Pub-Verkäufen zählt ein fehlender Stationsumsatz dagegen als echte Null.
Bei zu wenig Historie erscheint ein Hinweis. Suche/Produktfilter deaktivieren den Ausblick,
Gesamt/Theke/Bar bleiben wählbar. Es werden keine externen KI-Dienste oder Wetterdaten verwendet. Die Auswertung öffnet und schließt mit
dem bestehenden 220-ms-Größenwechsel; reduzierte Bewegung wird respektiert. Nummerierte
SVG-Rangbalken und ein Stundenverlauf ergänzen die lesbaren Zahlen und die genaue Tabelle.
Zeitraumwahl und Blätterleiste sind getrennt: Heute/Woche/Monat legt die Einheit fest, die
Pfeile darunter gehen Betriebstag, Woche beziehungsweise Monat zurück. Der Betriebstag beginnt
um 06:00 Uhr in Berlin; wann der Dienst tatsächlich beginnt (17–02, 15–03 Uhr oder anders),
spielt dafür keine Rolle. Alle Vergleiche und Tagesreihen zählen Betriebstage, nicht Kalendertage.
Eine offene Übersicht aktualisiert sich nach Kassierungen automatisch. Ohne Archivfreigabe liefert
der Server weder Liste noch Details. Ein manueller Nachdruck ist in dieser Ansicht noch nicht freigegeben.
In Bar und Theke werden Abschlüsse und Systemstatus beim Öffnen in die Stationsseite
portaliert. Fokus, Kombi oder Bedienen bleiben deshalb exakt als aktuelle Hintergrundansicht
erhalten; der Service-Iframe wird dafür nicht mehr auf Vollfläche vergrößert.
2. Setup & Betrieb
npm install # nach jedem git pull auf einem neuen Rechner nötig (node_modules ist nicht getrackt)
npm start # startet server.js, Standard-Port 3000 (per PORT-Env überschreibbar)
Unbehandelte Prozessfehler, fehlgeschlagene Datenbankverbindungen und HTTP-Startfehler beenden
den Server nach einem geordneten Socket-/SQLite-Shutdown mit Exit-Code 1. Damit der Betrieb nach
einem seltenen fatalen Fehler automatisch wieder verfügbar wird, liegt unter
deploy/willyspub.service.example eine gehärtete systemd-Vorlage mit
Restart=on-failure und geordnetem SIGTERM-Stopp. Sie geht von einem eigenen Nutzer
willyspub, /opt/willyspub als App-Pfad und /mnt/willy-backups als externem Backup-Ziel
aus. Nutzer, Pfade und RequiresMountsFor müssen vor der Installation an den echten Pi
angepasst werden; die aktive Konfiguration ist anschließend mit systemctl cat <dienstname>
zu prüfen.
npm run preflight
Die zugehörige secret-freie Environment-Vorlage liegt unter
deploy/willyspub.env.example. Sie darf nicht unverändert gestartet werden: Alle
CHANGE_ME-Werte ersetzen, als /etc/willyspub/willyspub.env ausschließlich root-lesbar
ablegen (chmod 600) und die produktive Datei niemals committen. deploy/, scripts/
und tests/ werden vom Webserver grundsätzlich mit 404 abgewiesen.
Der rein lesende Produktionscheck prüft ohne Ausgabe von Secrets unter anderem:
aktive Authentifizierung, getrennte Stationspasswörter, Chef-PIN, TLS-Paar und
Schlüsselrechte, Datenbankrechte, Druckeradresse sowie Erreichbarkeit und Mount-Situation
des Backup-Ziels. Blocker führen zu Exit-Code 1; die systemd-Vorlage führt den Check deshalb
als ExecStartPre vor jedem Start aus. Eine bestehende pub.db muss wegen ihrer Betriebs-
und Personaldaten chmod 600 besitzen. Der Check ersetzt keine Vor-Ort-Prüfung von
Prozessneustart, Drucker, Zertifikatsvertrauen, PWA und Endgeräten.
2.1 Anwendungszugang konfigurieren
Der Server startet absichtlich nicht ohne konfigurierte Zugangsdaten. Auf dem Raspberry Pi werden ein zufälliges Token-Geheimnis und getrennte Passwörter für die drei Ansichten gesetzt:
export AUTH_TOKEN_SECRET="$(openssl rand -hex 32)"
export AUTH_SERVICE_PASSWORD="ein-langes-eigenes-service-passwort"
export AUTH_BAR_PASSWORD="ein-anderes-langes-bar-passwort"
export AUTH_THEKE_PASSWORD="ein-anderes-langes-theke-passwort"
export AUTH_PAYROLL_PIN="4729"
npm start
Die Werte gehören in die geschützte EnvironmentFile der systemd-Unit (Dateirechte 600),
nicht ins Git-Repository. AUTH_PAYROLL_PIN besteht aus 4 bis 12 Ziffern und schützt die
Chef-Abrechnung, die Chef-Planung des Dienstplans und das Abschlussarchiv. Die PIN wird serverseitig
geprüft; fünf Fehlversuche sperren weitere Versuche derselben IP für 15 Minuten. Eine erfolgreiche
Freigabe gilt 15 Minuten für die aktuelle Socket-Verbindung. Alle-Personen-Daten, Lohnwerte,
Export, Monatsabschluss, der Abrechnungsbon sowie Abschlusslisten und -details sind ohne Freigabe
gesperrt. Nach Ablauf, Verbindungsabbruch oder Verlassen der jeweiligen Chefansicht entfernt der
Client die geladenen sensiblen Daten. Fehlt AUTH_PAYROLL_PIN, bleiben Service, Bar und Theke
verfügbar; die geschützten Chefbereiche bleiben bis zur Konfiguration gesperrt.
Nach erfolgreicher Stationsanmeldung speichert das Gerät pro Ansicht ein
eigenes signiertes, 30 Tage gültiges Token. Dadurch können Service, Bar und Theke auch in
mehreren Tabs desselben Browsers gleichzeitig geöffnet und unabhängig neu geladen werden.
Die eingebettete Service-Karte in Bar/Theke verwendet das Token ihrer Elternstation. Eine
Änderung von AUTH_TOKEN_SECRET meldet sofort alle Geräte ab; eine Passwortänderung betrifft
die nächste Anmeldung. Fünf falsche Versuche von einer IP sperren weitere Anmeldungen für
15 Minuten.
Der Login-Layer wird vor der eigentlichen Oberfläche angezeigt und erst entfernt, wenn die authentifizierte Socket-Verbindung samt Anfangszustand bereit ist. Beim ersten Laden nach dem Umstieg wird das vorhandene alte Token in den passenden Stationsschlüssel übernommen; die anderen Ansichten müssen gegebenenfalls einmalig neu angemeldet werden.
Nur für lokale Entwicklung ohne echte Daten ist AUTH_DISABLED=true vorgesehen. Dieser
Schalter darf auf dem Pub-Raspberry niemals gesetzt werden.
Socket.IO prüft das Token vor dem Senden des Anfangszustands und prüft danach jedes eingehende Ereignis gegen die Rolle. Bar/Theke dürfen wegen der dort eingebetteten Service-Ansicht auch Bestellungen bedienen; Stationsmeldungen sind jedoch an die passende Rolle gebunden.
Die betrieblichen iPhones, Android-Smartphones und Android-Tablets öffnen anschließend
index.html, bar.html bzw. theke.html über die LAN-IP des Servers. Die ESC/POS-Bondrucker
werden in server.js getrennt konfiguriert (TCP, normalerweise Port 9100):
BAR_PRINTER_IP/BAR_PRINTER_PORT für Barbons und
THEKE_PRINTER_IP/THEKE_PRINTER_PORT für Thekenbons, Ofenbons, Bewirtungsbelege und sonstige
Ausdrucke. PRINTER_IP/PRINTER_PORT bleiben als kompatibler Fallback für den Thekendrucker
erhalten. Ohne explizite Bar-Konfiguration wird der bestätigte Bardrucker 192.168.178.124:9100
verwendet. Beide Ziele besitzen getrennte serielle Queues, damit ein nicht erreichbarer Drucker den
anderen nicht blockiert.
Produktionsbons an Bar und Theke drucken jede Position in 2× Breite und einfacher Höhe. Artikelname
und linksbündiger Zeilengesamtpreis stehen fett und ohne redundantes PREIS untereinander;
Sonderwünsche bleiben normalgewichtig und als zusammenhängender Text erhalten. Trennlinien mit je
einer Leerzeile davor und danach gliedern mehrere Positionen. Der Umbruch trennt keine
Wörter und behandelt passende Klammerzusätze als Einheit; passt ein Klammerblock nicht mehr in die
24 Zeichen breite Zeile, beginnt er vollständig in der nächsten.
2.2 HTTPS im LAN (Pflicht für die PWA-Installation)
Android erhält über theme-shell.js vor dem Rendern data-platform="android".
Der Seitenrahmen unterbindet Browser-Overscroll (Pull-to-refresh und browserseitige
Wisch-Navigation), auch im eingebetteten Service. Listen und Kategorien bleiben scrollbar.
Der Ambiente-Glow bleibt auf allen Geräten statisch. Dialoge und Kombinationsauswahl
verwenden auf Android, iOS und Desktop dieselbe Hintergrundunschärfe.
Android-Systemgesten am Bildschirmrand werden dadurch nicht
gesperrt; eine vollständige Gerätesperre erfordert eine gesonderte Kioskeinrichtung.
Nach einem Update die App vollständig schließen und neu öffnen; eine Neuinstallation
ist für diese CSS-/Script-Änderungen nicht erforderlich. Hardwareprüfung siehe ROADMAP.md.
Android und iOS installieren eine Web-App nur dann als echte PWA (Vollbild ohne
Browser-Adressleiste), wenn sie über einen secure context ausgeliefert wird. Reines HTTP
über die LAN-IP reicht nicht – dann entsteht nur ein Browser-Shortcut mit Adressleiste
und der Service-Worker (sw.js) registriert sich nicht.
server.js startet automatisch als HTTPS-Server, sobald Zertifikat und Key vorliegen, sonst
als HTTP (nur für lokale Entwicklung). Pfade über Env-Variablen, Default im Projektordner:
SSL_CERT– Defaultcerts/cert.pemSSL_KEY– Defaultcerts/key.pem
certs/ ist bewusst nicht im Git und wird vom Static-Handler geblockt – der private Key
darf nie per HTTP erreichbar sein.
Zertifikate erzeugen (auf dem Raspberry Pi, feste Server-IP 192.168.178.72):
sudo apt update && sudo apt install -y libnss3-tools
# mkcert-Binary passend zur Pi-Architektur (arm64 bei 64-bit Raspberry Pi OS; sonst arm):
curl -L "https://dl.filippo.io/mkcert/latest?for=linux/arm64" -o mkcert
chmod +x mkcert && sudo mv mkcert /usr/local/bin/
mkcert -install # erzeugt einmalig die lokale Root-CA
cd /pfad/zu/willyspub && mkdir -p certs
mkcert -cert-file certs/cert.pem -key-file certs/key.pem 192.168.178.72 localhost
npm start # startet nun via HTTPS
Aufruf danach: https://192.168.178.72:3000.
Root-CA auf jedem Android-Pad vertrauen (einmalig pro Gerät):
rootCA.pemvom Pi holen (Ablageort zeigtmkcert -CAROOT) und auf das Pad übertragen.- Einstellungen → Sicherheit → „Verschlüsselung & Anmeldedaten“ → „Zertifikat installieren“
→ „CA-Zertifikat“ →
rootCA.pemwählen (Warnung bestätigen; für eine eigene CA normal). - Chrome öffnen,
https://192.168.178.72:3000laden – es darf keine Zertifikatswarnung kommen. Falls zuvor über HTTP getestet wurde: alten Shortcut entfernen und die Website-Daten der Seite in Chrome leeren. - Chrome-Menü → „App installieren“ (nicht „Zum Startbildschirm hinzufügen“).
Danach starten Service/Bar/Theke auf Android im echten fullscreen-Anzeigemodus: ohne
Adressleiste sowie ohne dauerhaft sichtbare Status- und Navigationsleiste. Die Android-Systemleisten
lassen sich weiterhin mit einer Wischgeste vom Bildschirmrand vorübergehend einblenden. Nach einer
Änderung des Manifest-Anzeigemodus muss eine bereits installierte Test-PWA entfernt und neu
installiert werden; der Client-Code bleibt unverändert: Socket.IO nutzt relative URLs und wechselt
unter HTTPS automatisch auf wss://.
Die Manifeste verwenden echte 192- und 512-px-Kreisicons für purpose: any sowie ein separat
komponiertes, vollflächiges 512-px-Icon für purpose: maskable. Service und Bar verwenden dabei
Grün auf Gold, die goldene Theken-Grafik liegt kontrastreich auf Grün. iOS erhält pro Station ein
eigenes 180-px-Touch-Icon. Neue Dateinamen verhindern, dass Android die früheren weich skalierten
Manifest-Icons weiterverwendet; bereits installierte WebAPKs müssen trotzdem neu installiert werden.
Für Chrome-PWAs genügt die User-CA; die „ab Android 7 nur System-CAs“-Einschränkung betrifft
nur native Apps mit eigenem Netzwerk-Stack, nicht Chrome selbst.
2.3 Gesundheitsmonitor und automatische Backups
Ein Klick auf die Verbindungsampel öffnet den authentifizierten Gesundheitsmonitor. Er prüft Serverlaufzeit, Datenbankzugriff, freien Speicher, Erreichbarkeit des Bondruckers, verbundene Stationsgeräte und den Zustand der automatischen Datensicherung. Jeder Browser erhält dafür eine lokale, zufällige Geräte-ID; mehrere Verbindungen desselben Geräts werden zusammengefasst. Die eingebettete Service-Karte von Bar und Theke zählt nicht als zusätzliches Gerät. Der Druckertest öffnet nur eine TCP-Verbindung und erzeugt keinen Testbon; Papiermangel kann deshalb erst durch einen echten fehlgeschlagenen Druck auffallen. In Bar und Theke erweitert die eingebettete Service-Karte den Statusdialog vorübergehend auf die gesamte Stationsfläche; beim Schließen wird das normale Bon-/Service-Layout wiederhergestellt.
Der Server erzeugt beim ersten Betrieb ohne Sicherung und anschließend täglich ab 04:00 Uhr
(Europe/Berlin) einen konsistenten, integritätsgeprüften SQLite-Snapshot. Standardziel ist
runtime-backups/; über DB_BACKUP_DIR kann ein USB- oder NAS-Mount gesetzt werden. Die
sieben neuesten Sicherungen samt SHA-256-Prüfsumme bleiben erhalten. Ablauf, manuelle Prüfung
und Wiederherstellung stehen in docs/BACKUP_RESTORE.md.
runtime-backups/ ist von Git ausgeschlossen und durch das Static-Hardening nicht erreichbar.
Eine lokale Sicherung auf derselben SD-Karte ersetzt kein externes Backup.
Reservierungsbuch für Service, Bar und Theke
Personen, Aufnahmeweg und Status verwenden die gestalteten Dropdowns der App. Bereichswahl, Tischwahl und Buchungsartwechsel geben direktes visuelles Feedback. Personenanzeige und Datum/Uhrzeit verwenden dieselbe Schriftgröße und Wertfarbe. Die Tischfläche verwendet dieselbe Grundfarbe, denselben Rand und dieselbe Rundung wie die Deckelübersicht. Der Zuordnungspfeil bleibt dem aufgeklappten Deckel-Popover vorbehalten. Native Zeitwerte mit angehängten Nullsekunden werden vor dem Speichern auf Stunden/Minuten normalisiert. Nach der Zeitwahl bleibt das Formular per Touch scrollbar; Tischzuordnung ist optional. Scrollleisten haben einen eigenen Abstand zum Inhalt, auch in beiden Tabletspalten. Innenabstand und Scrollleistenspur werden links und rechts optisch gleich ausgeglichen. In der Doppelansicht gilt das vom Dialogrand bis zum Inhalt und an der mittleren Trennlinie; das äußere Dialogpadding wird nicht ein zweites Mal addiert. Dieselbe Abstandsregel gilt für weitere Inhaltslisten der App. Die Systemeinstellung für reduzierte Bewegung wird berücksichtigt.
Über Admin → Reservierungen öffnen beide Tablets dasselbe interne Tagesbuch. Im Service öffnet
der runde Kalender-Button im Stil der Hell-/Dunkel-Taste dasselbe Buch. Die kleine Zahl am Rand
zählt heute noch erwartete Gruppen, nicht Personen.
Bis 500 px füllt das Fenster den Bildschirm, mit denselben Funktionen und kompakteren Abständen.
Im eingebetteten Service (Kombi/Bedienen) entfällt der zusätzliche Kalender-Button; der Einstieg
liegt im Admin-Menü der Station. Bestellung, Tisch und Scrollposition bleiben erhalten. Bei Offline/fehlgeschlagener Zählerabfrage entfällt die Zahl;
Reconnect, Tageswechsel und Live-Änderungen aktualisieren sie. Telefonische,
per E-Mail eingegangene und vor Ort vereinbarte Reservierungen werden manuell eingetragen:
Name, Datum, Uhrzeit, Personenzahl und explizite Raumwahl Theke, Bar oder Biergarten (geschlossene Veranstaltungen sind ganztägig). Die Personenwahl nutzt
Plus/Minus und eine gestaltete Auswahl von 1–200 ohne Zahlentastatur.
Neue Buchungen verlangen eine bewusste Auswahl; vorhandene Zahlen bleiben exakt erhalten. Kontakt und Notiz sind
optional. Die Liste verwendet die Bereichskarten der Deckelübersicht: Theke, Bar und Biergarten,
darunter die nach Ankunftszeit sortierten Buchungen. Bereich antippen fokussiert die Liste;
„Alle Bereiche“ zeigt wieder alle. „Tisch offen“ liegt unter „Filter“ und bleibt bei aktiver Auswahl sichtbar. „Raum noch offen“ erscheint für uneindeutige Alt-Einträge.
Das Buch öffnet mit „Erwartet“ und zeigt nur ausstehende Ankünfte. Mit dem
Haken „Da“ wird die Gruppe direkt als angekommen bestätigt; nach Serverbestätigung verschwindet
sie aus dieser Liste. „Angekommen“ und „Alle“ mit Anzahl erhalten den Tagesüberblick. „Zurück“
setzt versehentlich abgehakte Gruppen wieder auf erwartet. Stornierungen/Nicht-Erscheinen werden
weiter im Formular gepflegt und bleiben unter „Alle“ sichtbar. Bereich und „Tisch offen“ filtern
zusätzlich; die Tagessumme zählt erwartete/angekommene Reservierungen, keine Laufgäste.
Neue Buchungen starten immer mit „Tisch reservieren“: Datum, Uhrzeit, Personen und Theke/Bar/Biergarten mit einzelnen Tischflächen. „Bereich mieten“ ist eine getrennte Buchungsart. Der Mietumfang ist eine feste Radio-Auswahl: Gesamtes Pub, Bar, Theke, Biergarten. Gesamtes Pub steht immer zuerst; der gemeinsame Ganztagshinweis bleibt unter der Liste. Bei einer Auswahl öffnen oder verschieben sich keine Mietoptionen. Ein Bereichstipp ändert nie die Buchungsart. Bei einem Wechsel zur Miete und zurück bleiben Uhrzeit, gewählter Raum und Tische des normalen Entwurfs erhalten. Vorhandene Buchungen öffnen in ihrer gespeicherten Buchungsart. Eine neue Buchung startet auch nach einer Miete wieder als Tischreservierung.
Ein Tisch ist bei der Aufnahme nicht erforderlich. Beide Buchungsarten verwenden dieselben
Radio-Zeilen für die Bereichswahl. SVG-Motive für Theke, Bar und Biergarten greifen die
gefüllten Service-Symbole samt Iconflächen auf; ein Haus steht für das gesamte Pub. Platzsymbole stehen vor dem Namen,
Radio- und Häkchenmarkierungen rechts. Deckel und Reservierungen nutzen dieselben Tisch-SVGs
aus pub-tables.js und dieselbe Grundfläche mit Rahmen. Bei Deckeln steht rechts weiterhin
Betrag/Status, bei Reservierungen die Auswahlmarkierung. Im Tischmodus stehen die optionalen Tische in einer eigenen
Fläche unter der Bereichsliste: eckige Auswahlfelder mit Häkchen erlauben mehrere Tische.
Erneutes Antippen eines Bereichs erhält die offene Tischfläche und ihre Auswahl. Ausgewählte
Tische sowie alte Freitextzuordnungen erscheinen als Zusammenfassung.
Theke/Bar-Wechsel erhält Tische; Innen-/Außenwechsel entfernt inkompatible
Zuordnungen mit Hinweis. Die spätere Tischzuweisung, Änderung oder
Aufhebung steht dem gesamten Team im Service und auf Bar- und Theken-Tablet ohne zusätzliche PIN offen,
über eine Mehrfachauswahl der vorhandenen Plätze: drinnen Pub/Bar, draußen Biergarten.
pub-tables.js liefert dieselben Tische und Gruppen wie die Service-Tischanlage. Vorhandene freie
Tischangaben bleiben bei normalen Bearbeitungen erhalten, bis das Team sie ausdrücklich durch
eine Auswahl ersetzt oder aufhebt. Mangels fester Tischkapazitäten und Aufenthaltszeiten findet
keine automatische Belegungsprüfung einzelner Tische statt; ganztägige Bereichsbuchungen werden dagegen geprüft. E-Mails werden nicht automatisch importiert oder beantwortet.
Geschlossene Gesellschaften werden ganztägig mit einem der vier Mietumfänge gebucht:
- Alles · gesamtes Pub: Bar, Theke und Biergarten zusammen.
- Nur Bar.
- Nur Theke: der bisher technisch als
Pubgeführte Bereich. - Nur Biergarten (Sommer).
Unter „Bereich mieten“ stehen alle vier Mietumfänge als eigene Bereichskarten bereit. Uhrzeit und einzelne Plätze entfallen, Name, Datum und Personenzahl bleiben erforderlich. Eine Buchung des gesamten Pubs erscheint einmal im Tagesbuch und kennzeichnet alle drei Bereiche als ganztägig gemietet. Auch nach Ankunft bleibt die Sperre bestehen. „Tisch offen“ zählt solche Veranstaltungen nicht als fehlende Zuweisung. Für den Sommerbetrieb werden keine unbestätigten festen Saisonmonate erzwungen; der Hinweis steht direkt bei der Biergartenmiete.
Der Server prüft den Mietumfang in beide Richtungen: „Alles“ kollidiert mit jeder aktiven Reservierung des Tages, eine Einzelmiete nur mit Reservierungen des betroffenen Bereichs. Bar, Theke und Biergarten können unabhängig voneinander vermietet werden. Mischzuweisungen und uneindeutige alte Inneneinträge werden berücksichtigt; letztere müssen vor einer betroffenen Bereichsbuchung geklärt werden. Storniert/Nicht erschienen gibt wieder frei; Wiederherstellen prüft erneut. Prüfung und Speichern laufen pro Datenbankverbindung serialisiert. Die Sperre betrifft das Reservierungsbuch, nicht das Bonieren auf vorhandene Deckel. Mietpreise, Zahlungen und Verträge werden hier nicht erfasst.
Der feste Kopf enthält „+ Neu“ für die häufige Aufnahme am Handy. Datum und Status bleiben beim Scrollen der Tagesliste sichtbar; Summen stehen einmal kompakt unter dem Datum. Die Zusatzfelder Kontakt, Notiz und Aufnahmeweg sind unter „Weitere Angaben“ gebündelt. Nach bestätigter Ankunft bietet „Ankunft rückgängig“ eine direkte Rücknahme mit dem bestätigten Versionsstand; parallele Änderungen bleiben konfliktgeschützt. Auf breiten Tablets (ab 1000 CSS-Pixeln Breite) bleibt beim Bearbeiten links die Tagesliste als nicht bedienbare Orientierung sichtbar; rechts scrollt das Formular unabhängig. „Zurück zur Liste“ beendet die Bearbeitung mit bestehendem Eingabeschutz.
Die feste Speichern-Leiste steht außerhalb des scrollenden Formularinhalts; die Scrollleiste
endet oberhalb der Aktionen. Unter dem Button bleibt ein einzelner kompakter Abstand.
Auf dem Handy bleibt die Reservierungsfläche auch bei kurzen Listen mindestens
bildschirmhoch und schließt mit der vorhandenen Dialogfarbe bündig unten ab.
Alle Änderungen werden erst nach Serverbestätigung übernommen und auf den anderen Geräten
aktualisiert. Versionskonflikte erhalten die eigenen Eingaben und bieten den aktuellen Stand an.
Bei einem Timeout wiederholt „Erneut bestätigen“ dieselbe Buchung, ohne Duplikat. Ohne Verbindung
wird nichts als gespeichert ausgegeben. Unbestätigte Formulare werden vor dem Verwerfen geschützt.
Die Daten liegen zentral in SQLite; reservations-store.js legt Tabelle/Index beim ersten Zugriff
an und ergänzt Tischzuweisung, Raum, booking_type und rental_scope bei bestehenden Datenbanken automatisch.
Alte Buchungen erhalten table; geschlossene Veranstaltungen verwenden exclusive und intern 00:00.
rental_scope trennt all (gesamtes Pub) von Pub (nur Theke), Bar und Biergarten.
Bestehende exklusive Pub-Einträge bleiben Thekenbuchungen und werden nicht auf alles erweitert.
Das technische Feld area wird aus dem Raum abgeleitet; bestehende Clients bleiben kompatibel.
Es gibt keine Kopplung an Schichtplan, Arbeitszeiten oder die Umsatzprognose.
2.4 Dienstplan und Verfügbarkeit
Derzeit nicht im Einsatz (2026-09-16): Dienstplan und Arbeitszeit sind aus dem Admin-Menü von Bar und Theke entfernt. Der beschriebene Funktionsumfang bleibt vollständig im Code (
schedule.js,timesheet.js,schedule_*- undwork_hours-Tabellen) und unverändert gültig; es fehlt nur der Einstieg. Wiedereinschalten heißt: je Station einestation-admin-action-Zeile mitWillySchedule.open()beziehungsweiseWillyTimesheet.open()zurück ins Admin-Popover.
Bar und Theke bündeln Dienstplan und Arbeitszeit im einzeiligen Stationskopf unter einer goldenen „Admin“-Pille. Das kleine Menü lässt sich später um weitere Stationswerkzeuge wie eine Bestellverwaltung ergänzen, ohne den Bon-Titel oder den operativen Bon-Zähler zu verdrängen. Eine neue Woche erhält automatisch die normalen Pub-Einsätze: Montag, Mittwoch, Donnerstag und Sonntag 17:00–00:00 Uhr, Freitag und Samstag 17:00–01:00 Uhr; Dienstag bleibt als Ruhetag leer. Diese regulären Pub-Öffnungstage lassen sich nicht löschen oder umbenennen; bearbeitbar sind bei ihnen nur Beginn, Hinweis und der Personalbedarf. Zusätzliche Anlässe wie Konzerte werden als „Sondertermin“ am selben Tag ergänzt und sind als einzige frei umbenennbar, verschiebbar und löschbar. Als reine Startwerte tragen normale Wochen Montag/Sonntag vier, Mittwoch/Donnerstag fünf und Freitag/Samstag sechs Personen. Das sind keine festen Regeln: Der Chef ändert das Soll je Einsatz frei, etwa sonntags auf sechs Personen für ein WM-Finale.
Die Oberfläche trennt die drei Aufgaben konsequent in „Plan“, „Verfügbarkeit“ und die PIN-geschützte „Chef-Planung“; sichtbar ist immer nur der gewählte Arbeitsbereich. Der normale Dialog bleibt kompakt, während sich ausschließlich die Chef-Planung für die zweispaltige Arbeitsfläche verbreitert. Der veröffentlichte Plan erscheint als ruhige Tagesliste statt als horizontale Kartenwand. Der Dienstplan zeigt und bearbeitet ausschließlich den geplanten Beginn. Eine geplante Endzeit ist wegen der schwankenden Nacharbeiten kein Dienstplanwert; die tatsächliche Endzeit wird nur in der Arbeitszeiterfassung gespeichert. Das „bis“-Feld bleibt ausschließlich bei einer teilweisen Verfügbarkeit erhalten, weil es dort die persönliche zeitliche Grenze beschreibt.
Mitarbeiter wählen ihre Person und melden pro Öffnungstag über drei direkte Schalter „Ja“, „Teilweise“ mit Von-/Bis-Zeit oder „Nein“. Ein erneuter Tipp auf die aktive Wahl hebt sie wieder auf. Native Status-Dropdowns und die kaum genutzte Bemerkungszeile entfallen; die Zeitfelder erscheinen nur bei „Teilweise“ und liegen platzsparend unter dem Ja/Teilweise/Nein-Schalter; die Wochenliste bleibt bewusst auf eine ruhige Lesebreite begrenzt. Jede Angabe wird analog zur Arbeitszeit sofort gespeichert – es gibt keinen „Woche speichern“-Knopf mehr, sondern nur den Hinweis „Änderungen werden automatisch gespeichert“ und eine kurze „Gespeichert.“-Rückmeldung. Danach gibt es keinen weiteren Rückfluss: Die PIN-geschützte Chef-Planung teilt je Einsatz Personen durch direktes Antippen ein oder wieder aus. Sie ist in zwei Reiter getrennt: „Einteilen“ zeigt nur Einsatzliste und Einsatz-Editor mit Team, „Woche im Blick“ zeigt Wochenverteilung und die Liste „Noch keine Rückmeldung“; letztere nennt die Personen, die für die angezeigte Woche noch gar nichts gemeldet haben, damit gezieltes Nachfragen leichter fällt. So sieht der Chef beim Öffnen nur Woche, Einsatzliste und ein Team; die Auswertung ist einen Tipp entfernt. Hinzufügen lassen sich ausschließlich Personen mit „Ja“ oder „Teilweise“; das Backend lehnt fehlende und negative Rückmeldungen ebenfalls ab. Caro, Stefan und Mixi sind eine feste Ausnahme: Sie werden automatisch als fest angestellt angelegt, erscheinen in jeder Chef-Planung als „Immer verfügbar“ und brauchen weder eine Verfügbarkeitsmeldung noch eine Arbeitszeiterfassung. Deshalb stehen sie in den entsprechenden Mitarbeiter-Auswahlen und in der Monatsabrechnung nicht zur Wahl. Nach dem Antippen einer Person wählt der Chef den Beginn direkt in 30-Minuten-Schritten zwischen 17:00 und 21:00 Uhr; erst diese Zeitwahl speichert die Einteilung. Bei „Teilweise“ bleibt das gemeldete Verfügbarkeitsende erhalten. Bereits eingeteilte Personen erhalten dieselbe Startzeitleiste; eine geplante Endzeit wird weder angezeigt noch bearbeitet. Nach der Serverbestätigung zeigt der Teamkopf vier Sekunden lang Name und gespeicherte Startzeit. Die Chef-Planung ist in zwei Reiter getrennt. Im Reiter „Einteilen“ steht links die Einsatznavigation mit Datum und „eingeteilt / Soll“ neben dem jeweils ausgewählten Einsatz; rechts liegt genau ein Einsatz-Editor mit Team. Der Reiter „Woche im Blick“ zeigt separat die auswertenden Listen (Wochenverteilung und „Noch keine Rückmeldung“), damit sie den Arbeitsbereich nicht mehr überlagern. Zur besseren Übersicht ist der Einteilen-Reiter in klar abgegrenzte Zonen gegliedert: Die Einsatznavigation sitzt in einer eigenen Schiene mit je einer umrandeten Karte pro Einsatz, die das Datum, eine farbige Besetzungs-Pille (amber = noch nicht voll besetzt, grün = vollständig besetzt) und bei Sonderterminen ein „Sondertermin“-Kennzeichen trägt; die Teamliste ist eine geschlossene Fläche mit einem sichtbaren Trennband zwischen eingeteilten und noch verfügbaren Personen, und der Besetzungsstand erscheint als ablesbares Badge. „Veröffentlichen“ und der standardmäßig aufgeklappte Hinweis-Streifen bleiben über beiden Reitern sichtbar. „+ Sondertermin“ ist als Ausnahme direkt bei den Einsätzen eingeordnet. Die kompakte Maske fragt nur Datum, Bezeichnung, Beginn und Personenzahl ab und ersetzt währenddessen den normalen Editor, statt ein zweites Formular darüberzustapeln; sie liegt dabei als flache Fläche im Arbeitsbereich wie der reguläre Editor, nicht mehr als abweichende Karte. Angelegte Sondertermine sind in Navigation und Editor ausdrücklich gekennzeichnet und besitzen dort eine direkt sichtbare, zweistufig bestätigte Löschaktion. Im „Woche im Blick“-Block zeigt die geschützte Wochenverteilung ausschließlich eingeteilte Personen und für sie, an wie vielen unterschiedlichen Tagen sie eingeplant sind und an wie vielen Tagen sie sich verfügbar gemeldet haben. „Minijob“ und „Fest“ bleiben dabei sichtbar, damit angebotene Tage nicht versehentlich als gewünschte Anzahl von Arbeitstagen verstanden werden. Mehrere Einsätze am selben Kalendertag zählen als ein Arbeitstag; es gibt bewusst keine pauschale rote Grenze ohne Stundenbezug. Hinweise stehen direkt unter dem Wochenkopf und sind standardmäßig aufgeklappt. Mehrere Unterbesetzungen werden darin zu einem Hinweis zusammengefasst; die genauen Werte bleiben in der Einsatznavigation sichtbar. Fehlende Rückmeldung, Nichtverfügbarkeit, Zeitabweichungen und Doppelbelegung blockieren eine bewusste Veröffentlichung nicht. Ruhige Einblend- und Wechselanimationen unterstützen die Orientierung und werden bei reduzierter Bewegung abgeschaltet. Beim Öffnen folgt der Dienstplan derselben 250-ms-Fade-und-Scale-Bewegung wie Checkout, Rechner, Ofendialog und Arbeitszeit; seine internen Ansichts- und Größenwechsel bleiben bewusst kürzer, damit Navigation nicht träge wirkt. Wie alle echten App-Modals verwendet er außerdem die gemeinsame abgedunkelte und dezent weichgezeichnete Hintergrundebene. Jede Änderung an Einsatz, Soll oder Einteilung setzt die Woche wieder auf Entwurf. Mitarbeiter sehen keine Entwurfseinteilungen; erst „Plan veröffentlichen“ macht den fertigen Wochenplan für alle lesbar. Unterbesetzte Einsätze nennen auch im veröffentlichten Plan direkt unter den eingeteilten Personen, wie viele Personen noch fehlen. Eine zusätzliche Planbestätigung gibt es nicht. „Teilen“ steht nur für veröffentlichte Pläne bereit und übergibt den Plan als Text an die Systemfreigabe oder kopiert ihn in die Zwischenablage. Leere Von-/Bis-Felder zeigen je Öffnungstag direkt 17:00–00:00 Uhr beziehungsweise am Freitag und Samstag 17:00–01:00 Uhr; beim Status „teilweise“ werden diese Werte als Zeitfenster gespeichert. Vollständige Verfügbarkeiten werden beim Verlassen der Chef-Planung, beim Schließen, bei Verbindungsabbruch und nach Ablauf der 15-Minuten-Freigabe wieder aus dem Client entfernt.
Planzeiten und geleistete Zeiten bleiben getrennt. Das Arbeitsstunden-Modal zeigt den heutigen geplanten Einsatz nur als Hinweis; „Dienst starten“ und „Dienst beenden“ speichern weiterhin ausschließlich die tatsächlichen Zeiten. Beim Öffnen wird keine zuletzt verwendete Person automatisch übernommen; die Person wird bewusst gewählt. Danach bleibt zunächst der heutige Dienst sichtbar, während Monatsübersicht, Monatswahl und Personenverwaltung über sekundäre Schalter erreichbar sind. Mitarbeiterprofile unterscheiden „fest angestellt“ und „Minijob“ und können ein frei gepflegtes monatliches Planlimit tragen. Caro, Stefan und Mixi werden dauerhaft als fest angestellt und arbeitszeitbefreit geführt, alle übrigen als Minijob. Wie im Dienstplan begleiten kurze Einblendungen die Auswahl, Monatsdetails, Verwaltung und Chef-Abrechnung; Dialoghöhe und -breite wechseln kontrolliert statt zu springen. Bei aktivierter Systemoption „Bewegung reduzieren“ entfallen diese Effekte. Im Dienstplan ist davon ausschließlich die Beschäftigungsart in der PIN-geschützten Wochenverteilung sichtbar; Profilpflege und Monatslimit bleiben bei der Arbeitszeit.
Die Rückmeldung funktioniert derzeit innerhalb des Pub-Netzes. Ein Zugriff von zu Hause ist bewusst noch nicht freigeschaltet; dafür ist später ein getrennter, sicherer Mitarbeiterzugang statt einer Portfreigabe des Kassensystems nötig.
3. Design-System & UI-Richtlinien
Modernes Light/Dark-Theme, vollständig über CSS-Variablen gesteuert. Single Source of
Truth ist style.css – dort stehen im :root-Block (Light) und im
[data-theme="dark"]-Block (Dark) alle Tokens. Diese README beschreibt nur die
Rollen; konkrete Hex-Werte immer in style.css nachschlagen bzw. dort pflegen.
Die vollständige Rollen-/Verwendungskarte und die Migrations-Checkliste stehen in
docs/DESIGN_SYSTEM.md.
3.1 Markenpalette
Das Design hat eine feste Identität aus drei Leitfarben plus Statusfarben:
| Rolle | Variable | Light | Dark | Einsatz |
|---|---|---|---|---|
| Pub-Grün (Primär) | --pub-green |
#1c4634 |
#2b6148 |
Marken-Grün, aktive Chips, Hintergrund-Glow |
| Gold (Akzent) | --gold |
#c69c53 |
#dcb363 |
Flächen/Rahmen/Icons/Highlights. Preise/Zahlen als Text: --gold-text (lesbar, WCAG AA) |
| Happy-Hour (Sekundär-Akzent) | --accent-hh |
#b55333 |
#db7651 |
HH-Kennzeichnung (--accent-hh-bg als Fläche) |
| Erfolg | --success |
#27ae60 |
(geerbt) | „Abholbereit“, positive Zustände |
| Gefahr/Storno | --danger |
#e74c3c |
(geerbt) | Stornos, Warn-Badges, Löschen |
3.2 Token-Gruppen
Alle weiteren Variablen sind nach Funktion gruppiert (Light/Dark jeweils gesetzt):
- Flächen:
--bg-body,--bg-header,--bg-card,--bg-search,--bg-chip,--bg-chip-active,--bg-pv(Produktkachel),--bg-badge,--modal-backdrop. - Text:
--text-primary,--text-secondary,--text-muted,--text-chip,--text-chip-active,--text-pv-size,--text-pv-price,--text-badge. Kontrast/Lesbarkeit:--gold-text(Gold als Text auf hellem Grund, WCAG AA),--on-gold/--on-green(Kontrasttext auf Gold-/Grün-Flächen statt#000/#fff). - Rahmen:
--border-subtle,--border-strong,--border-chip,--border-pv. - Effekte/Glow:
--glow-color,--bg-glow-green/--bg-glow-green-0,--bg-glow-gold/--bg-glow-gold-0,--shadow-card(zweischichtig),--shadow-search,--shadow-pv/--shadow-pv-hover, Elevation-Leiter--elev-1|2|3. - Geometrie/Bewegung:
--radius-xs|sm|md|lg|xl|full,--transition, Easing--ease-standard|out|spring. - Toggle:
--toggle-bg,--toggle-border.
3.3 Verbindliche Regeln
- Keine Hardcoded-Farben (Hex/
rgba) in HTML oder für wiederkehrende Muster. Immer Tokens ausstyle.cssverwenden. Fehlt eine Rolle, erst dort ein Token anlegen. - Light + Dark gemeinsam pflegen: jedes neue Token in
:rootund in[data-theme="dark"]setzen, sonst bricht ein Theme. - Für wiederkehrende Muster semantische CSS-Klassen statt funktionaler Inline-Styles
nutzen (z.B.
.btn-gold,.badge-time). - Freitext (Deckelnamen, Notizen, Artikelnamen) vor der HTML-Ausgabe escapen
(
esc(...)), besonders inbar.html/theke.html. - Dialoge: Aktive Modals verwenden den gemeinsamen
.modal-Baukasten, bleiben innerhalb von100dvh/Safe Areas scrollbar und unterstützen Escape, Fokusfalle sowie Fokus-Rückgabe. Operative Touchziele sind mindestens 44 px groß; deaktivierte Aktionen nutzen echtesdisabled. Beim Öffnen erhält der rahmenlose Dialogcontainer den Startfokus, nicht automatisch das X; dessen Goldring erscheint daher nur bei echter Tastaturnavigation. Modals nutzen den theme-spezifischen Backdrop-Token--modal-backdropund die 250-ms-Modalbewegung. - Bewegung: Zustände (160 ms), Popover (180 ms), kontrollierte Größenwechsel (220 ms),
Modals (250 ms) und neue Karten/Bons (500 ms) sind getrennte Rollen. Dauerbewegung kennzeichnet
neben dem Ambiente bewusst ungelöste operative Zustände: offene Bons, offene Bon-Zähler und
laufende Ofenaufgaben.
prefers-reduced-motionschaltet sie ab.
Design-System-Stand: Farben laufen vollständig über Tokens (
docs/DESIGN_SYSTEM.md).--bg-panelist als warmer transluzenter Panel-Ton definiert (Lightrgba(198,156,83,.05), Darkrgba(220,179,99,.05), Gold-Basis). Keine bekannten Altlasten mehr:theme-colorwird mit dem aktiven Kopf-Theme synchronisiert,--bg-hoverist als eigenes Token definiert,--border→--border-subtle. Premium-Polish (2026-07-08): lesbares--gold-textfür Preise (WCAG AA), Kontrast-Tokens--on-gold/--on-green, Elevation-Leiter, erweiterte Radius-/Easing-Skala sowie globalprefers-reduced-motion,:focus-visibleund ein deckender Sticky-Header. Details:CHANGELOG.mdunddocs/DESIGN_SYSTEM.md(Phase 4).
Auf iPhones nutzt die installierte Homescreen-App viewport-fit=cover zusammen
mit der iOS-Safe-Area. Die Statusleiste (Uhrzeit, WLAN, Akku) setzt dadurch mit --bg-header
die jeweilige Kopffläche fort: #faf8f1 im Light- und #0c0d0c im Dark-Mode. Das gemeinsame
theme-shell.js synchronisiert beim Start und beim manuellen Theme-Wechsel auch theme-color;
der iOS-Stil default wählt dazu kontrastierende Systemzeichen. Die drei Android-Manifeste
verwenden für ihre Fullscreen-Shell weiterhin #0c0d0c. Auf Android überschreibt das dynamische
Seiten-Meta diesen Manifest-Fallback zur Laufzeit: In Chrome sowie bei vorübergehend eingeblendeter
Systemleiste setzt sich deshalb ebenfalls die aktive Light-/Dark-Kopffarbe fort.
Ist die Service-Ansicht in Bar oder Theke eingebettet, übernimmt ausschließlich die Eltern-App
diese obere Safe Area; das Service-Iframe zeichnet dort keinen zweiten Farbbalken.
Auf Android verwenden die drei Stationsmanifeste dagegen display: fullscreen, weil die festen
Pub-Tablets weder die obere Statusleiste noch den unteren Gesten-/Navigationsbereich dauerhaft
zeigen sollen. Kritische App-Aktionen bleiben innerhalb der vorhandenen Safe-Area-Abstände.
Gestalteter Start und Öffnen ohne Verbindung
Service, Bar und Theke zeigen beim Start das vorhandene Pub-Logo auf dem gespeicherten Theme.
Eine schmale goldene Ladebewegung begleitet die Verbindung, danach blendet die Startfläche zur
bereiten App über. Es gibt keine künstliche Wartezeit. Während der laufenden Bedienung bleiben
die bisherige Offline-Pille und der Systemstatus zuständig.
Nach einmaliger erfolgreicher Service-Worker-Installation funktioniert auch der Offline-Start:
Eine lokal hinterlegte, eigenständige Ansicht bietet WLAN-Hinweis, automatischen Wiederholversuch
und Erneut versuchen. Sie benötigt weder Logo-Download noch externe Schriftdateien. Es werden
keine App-Daten oder API-Antworten gecacht. Ein allererster Aufruf ohne vorherige Installation
kann diesen Fallback noch nicht nutzen; der erste native PWA-Splash wird vom Betriebssystem gezeichnet.
Startgestaltung liegt in start-screen.css, die Offline-Wiederverbindung in offline-start.js.
npm run build:start erzeugt aus diesen Quellen und den Tokens aus style.css die frühen,
markierten Blöcke aller drei HTML-Seiten sowie offline.html und die passende SW-Cacheversion.
Nach Änderungen an diesen Quellen neu generieren und die erzeugten Dateien mit committen.
npm run check:syntax prüft zusätzlich, dass die Artefakte aktuell sind.
4. Datei-Übersicht
| Datei | Zweck |
|---|---|
server.js |
Express, Socket.IO, SQLite, Druckerqueue, Ofenlogik |
index.html |
Service/Kellner-Ansicht |
bar.html |
Bar-Station |
theke.html |
Theken-Station |
station-admin.js |
Schließen und Tastaturverhalten des Admin-Menüs in Bar/Theke |
station-workspace.js |
Willy Fokus, Kombi/Bedienen-Wechsel und responsive Fokus-Darstellung |
timesheet.js |
Direktes gemeinsames Arbeitsstunden-Modal für Bar und Theke |
timesheet.css |
Gemeinsames Layout der monatlichen Stundenliste |
station-audio.js |
gemeinsamer geprüfter Tonzustand für Bon- und Ofensignale in Bar/Theke |
pub-tables.js |
Gemeinsame Tischdefinitionen für Service, Reservierungsauswahl und serverseitige Validierung |
reservations.js |
Gemeinsames Reservierungs-Popup für Service, Bar und Theke |
reservations-store.js |
Validierung, SQLite-Ablage und Versionsschutz für Reservierungen |
schedule.js |
Dienstplan, Verfügbarkeit, Chef-Planung und Teilen |
schedule.css |
Gemeinsames tokenbasiertes Layout des Dienstplan-Modals |
products.js |
Produktdaten (G + DATA), Browser- und Node-kompatibel |
station-routing.js |
gemeinsame Stationsentscheidung Bar/Theke |
sw.js / offline.html |
Netzwerkbasierte App-Navigation mit eigenständigem Offline-Startfallback |
start-screen.css / offline-start.js |
Gemeinsame Startgestaltung und Offline-Wiederverbindung |
scripts/build-start-screen.js |
Reproduzierbare frühe Theme-/Startblöcke und Offline-Artefakte |
offline-outbox.js |
Ablauf, Quarantäne und bewusste Wiederherstellung lokaler Offline-Bons |
style.css |
Design-System / CSS-Variablen |
oven-store.js |
persistente Ofenbelegung: Tabelle, Schreiben je Änderung, Wiederherstellung beim Start |
pub.db |
aktive SQLite-Datenbank (im Projektroot, nicht verschieben) |
assets/ |
Bilder, Sounds, PWA-Manifeste |
docs/sales/ |
Verkaufsunterlage: Systemanalyse mit Nutzen- und Preisbewertung, 69 echte Screenshots |
scripts/sales-demo/ |
Entwicklungswerkzeuge für Demo-Betriebsstand, simulierte Bondrucker und Screenshot-Aufnahme (siehe dortige README.md) |
5. Entwickler-Workflow
- Projekt wird über die selbst gehostete Forgejo-Instanz synchronisiert:
https://git.bude3000.de/emdev/willyspub.gitalsorigin, Branchmain. GitHub ist seit dem 2026-08-19 nicht mehr angebunden. Der HTTPS-Zugang läuft über einen Forgejo-Access-Token, der im lokalen Credential-Helper liegt und nicht in.git/configgehört.node_modulesist bewusst nicht getrackt – nachgit pullimmernpm installausführen. - Sensible Runtime-Dateien (
pub.db,server.js,package*.json,backup/,docs/, Dotfiles etc.) werden vom Static-Handler geblockt; betriebsrelevante HTML-, Asset- und Client-JS-Dateien bleiben erreichbar. - Vor Änderungen in dieser Reihenfolge
.agents/AGENTS.md, dieses Handbuch,docs/AI_HANDOFF.mdundROADMAP.mdlesen; die dort genannte Fachdokumentation kommt je nach Aufgabe hinzu.ROADMAP.mdist die einzige Quelle für offene Arbeiten,CHANGELOG.mdenthält nur Umgesetztes. Validierungen laufen lokal vor dem Push:npm run check:syntax,npm testsowie Inline-Script-Parsing der HTML-Dateien (siehedocs/AI_HANDOFF.md). Eine serverseitige CI gibt es nicht: Die Workflowdatei liegt für Forgejo Actions unter.forgejo/workflows/ci.yml, auf der Instanz ist aber kein Forgejo Runner eingerichtet. Sie wird deshalb von niemandem ausgeführt und ersetzt die lokalen Prüfungen nicht.