Bon fuer die Kasse auf einem USB-Bondrucker ausgeben
Der Kunde soll mit einem Zettel zur Kasse gehen koennen, auf dem steht, was er zahlen muss. Drei Wege fuehren jetzt zu einem Bon: der Knopf "Bon" auf der Timer-Karte druckt den Zwischenstand und laesst den Timer weiterlaufen, "Beenden" druckt den Endbeleg, und in der Historie laesst sich jeder Beleg nachdrucken. Der Drucker haengt per USB an einem Raspberry Pi und ist damit vom Timer-Server aus nicht direkt erreichbar. Auf dem Pi laeuft deshalb eine kleine Bruecke (deploy/bondrucker/, nur Python-Standardbibliothek), die fertige ESC/POS-Daten per HTTP annimmt. Das Bon-Layout bleibt bewusst hier im Repo, damit Aenderungen daran nicht auf dem Pi angefasst werden muessen. Beim Beenden wird erst gespeichert und dann gedruckt. Eine leere Papierrolle ist damit ein Papierproblem und kein Datenproblem - der Vorgang steht in der Historie, die Oberflaeche sagt ausdruecklich, dass der Bon nicht gedruckt wurde, und nennt den Grund. Ohne PRINTER_HOST in der .env ist die Druckfunktion aus und die App verhaelt sich unveraendert. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5e83999950
commit
70756f3762
+105
-4
@@ -219,7 +219,8 @@
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
input[type="number"],
|
||||
input[type="date"] {
|
||||
input[type="date"],
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 0.875rem 1.125rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
@@ -231,14 +232,16 @@
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
input:focus,
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
border-color: #4d35a9;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
box-shadow: 0 0 0 3px rgba(45, 27, 105, 0.35);
|
||||
}
|
||||
|
||||
input::placeholder { color: rgba(255, 255, 255, 0.35); }
|
||||
input::placeholder,
|
||||
textarea::placeholder { color: rgba(255, 255, 255, 0.35); }
|
||||
|
||||
/* Datumsfelder im Dark Theme lesbar halten */
|
||||
input[type="date"]::-webkit-calendar-picker-indicator { filter: invert(1); opacity: 0.6; cursor: pointer; }
|
||||
@@ -551,6 +554,66 @@
|
||||
margin-top: 1rem !important;
|
||||
}
|
||||
|
||||
/* ---------- Bondrucker ---------- */
|
||||
|
||||
.settings-section {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.12);
|
||||
margin: 2rem 0;
|
||||
padding-top: 1.5rem;
|
||||
}
|
||||
|
||||
.settings-section h3 {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
|
||||
.settings-section > p {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.settings-section textarea {
|
||||
box-sizing: border-box;
|
||||
resize: vertical;
|
||||
min-height: 4.5rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.form-group + .form-group { margin-top: 1.25rem; }
|
||||
|
||||
.checkbox-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.75rem;
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
.checkbox-row input[type="checkbox"] {
|
||||
width: 1.15rem;
|
||||
height: 1.15rem;
|
||||
margin-top: 0.15rem;
|
||||
flex-shrink: 0;
|
||||
accent-color: #6d4aff;
|
||||
}
|
||||
|
||||
.checkbox-row label { font-weight: 500; cursor: pointer; }
|
||||
.checkbox-row small { display: block; color: rgba(255, 255, 255, 0.5); font-size: 0.8rem; margin-top: 0.25rem; }
|
||||
|
||||
.printer-state {
|
||||
font-size: 0.9rem;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 1rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-left: 3px solid rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.printer-state.ok { border-left-color: #22c55e; }
|
||||
.printer-state.error { border-left-color: #ef4444; }
|
||||
.printer-state.off { border-left-color: rgba(255, 255, 255, 0.2); color: rgba(255, 255, 255, 0.6); }
|
||||
|
||||
.settings-actions { display: flex; gap: 1rem; margin-bottom: 1.5rem; }
|
||||
.settings-actions .btn { flex: 1; padding: 1rem; }
|
||||
|
||||
@@ -582,6 +645,8 @@
|
||||
max-width: 90vw;
|
||||
font-size: 0.95rem;
|
||||
text-align: center;
|
||||
/* Meldungen zum Bondruck sind zweizeilig - Umbrueche sollen stehen bleiben. */
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.toast.show { transform: translateX(-50%) translateY(0); opacity: 1; }
|
||||
@@ -716,6 +781,42 @@
|
||||
<p class="preview-examples" id="previewExamples"></p>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h3>🖨️ Bon für die Kasse</h3>
|
||||
<p>Was auf dem Zettel steht, den der Kunde mit zur Kasse nimmt.
|
||||
Betrag, Zeiten und Tarif kommen automatisch dazu.</p>
|
||||
|
||||
<div id="printerState" class="printer-state">Druckerzustand wird geprüft …</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="receiptHeader">Kopfzeilen</label>
|
||||
<textarea id="receiptHeader" maxlength="240" rows="3"
|
||||
placeholder="Handy-Insel Hauptstraße 12, 12345 Musterstadt Tel. 01234 / 56789"></textarea>
|
||||
<small>Erste Zeile wird groß gedruckt. Eine Zeile je Zeilenumbruch, maximal 240 Zeichen.</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="receiptFooter">Fußzeilen</label>
|
||||
<textarea id="receiptFooter" maxlength="240" rows="2"
|
||||
placeholder="Bitte an der Kasse bezahlen."></textarea>
|
||||
<small>Steht unter dem Betrag, maximal 240 Zeichen.</small>
|
||||
</div>
|
||||
|
||||
<div class="checkbox-row">
|
||||
<input type="checkbox" id="printAutoOnStop" checked>
|
||||
<label for="printAutoOnStop">Bon beim Beenden automatisch drucken
|
||||
<small>Aus: der Bon wird nur über die Knöpfe 🖨 gedruckt.</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="settings-actions" style="margin-top: 1.25rem;">
|
||||
<button type="button" class="btn btn-secondary" id="printerTestBtn"
|
||||
onclick="testPrint()">🖨️ Probedruck</button>
|
||||
<button type="button" class="btn btn-secondary"
|
||||
onclick="refreshPrinterStatus()">🔄 Drucker prüfen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-actions">
|
||||
<button type="submit" class="btn">💾 Speichern</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="resetSettings()">🔄 Zurücksetzen</button>
|
||||
@@ -733,6 +834,6 @@
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script src="/app.js?v=20260803-sso"></script>
|
||||
<script src="/app.js?v=20260904-bon"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user