Das zweite Blatt "fuer das Amt" trug DIESELBE Rechnungsnummer, aber einen anderen Gesamtbetrag. Mehrere Blaetter zum selben Umsatz mit verschiedenen Endbetraegen sehen aus, als werde ueber verschiedene Umsaetze abgerechnet - bei einer Pruefung ein Problem. Ersatzlos entfallen; was die Stadt braucht, rechnet das Steuerjournal aus den Kenndaten der PDF. Mit dem Blatt fielen _AmtMarke, gesamtrechnung_betont und der doppelte Renderlauf weg. Die API kann das Fenster jetzt BEDIENEN - mit Schluessel wie bei einem SSH-Key: 32 Byte Zufall in api_schluessel.txt im gemeinsamen Ordner, den nur das Steuerjournal und der Betreiber lesen. Ohne gueltigen Schluessel kein Zutritt (compare_digest). ping und schau_nach bleiben schluessellos, sonst kann ein aelteres Journal nicht mehr anklopfen. Was einen BELEG schreibt, geht nur im Testlauf - eine Fernbedienung, die echte Rechnungen ausstellt, hat in einer Buchhaltung nichts zu suchen. api.py liegt wie immer in beiden Programmen. Der Erststart fragte nach "der zuletzt vergebenen Nummer" - das holt niemanden ab, der bei 0 anfaengt. Jetzt zwei Wege: bei 0 anfangen ODER die eigene Zaehlung weiterfuehren. Unter das schon Vergebene kommt keiner der beiden. pruef_nummernsperre bediente den Dialog nur halb: nach einer abgelehnten Zahl bleibt er offen (so soll er), niemand drueckte "Spaeter" - das Programm wartete in wait_window() ewig. Das war schon vor diesem Umbau so. Neu: pruef_api.py. Alle 14 Pruefstaende gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
103 lines
3.3 KiB
Python
103 lines
3.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Steuerung fuer Pruefstaende: keine Fenster, keine Popups, keine Maus.
|
|
|
|
WARUM
|
|
-----
|
|
Ein Pruefstand baut das Programm auf, um es zu BEDIENEN - nicht, um es zu
|
|
zeigen. Solange er wie ein Benutzer arbeitet, reisst jeder Lauf dem Menschen am
|
|
Rechner Fokus und Maus weg, und jeder Dialog bleibt stehen und wartet auf einen
|
|
Klick, den niemand macht.
|
|
|
|
Deshalb zwei Dinge, beide nur im Testlauf (`--test` / `CAMPINGHOF_TEST=1`):
|
|
|
|
* Das Fenster wird gar nicht erst gezeigt (`withdraw()` in app.py).
|
|
* Alle Dialoge laufen hierueber statt ueber echte Fenster.
|
|
|
|
WAS EIN DIALOG HIER TUT
|
|
-----------------------
|
|
`showinfo/showwarning/showerror` werden ins PROTOKOLL geschrieben und
|
|
verschwinden. `askyesno` und Verwandte schlagen im Fahrplan nach:
|
|
|
|
pruefsteuerung.antwortet("Hausnummer", True)
|
|
|
|
Passt kein Eintrag, bricht der Lauf mit einer Meldung ab - ABSICHTLICH. Ein
|
|
unbekannter Dialog heisst: das Programm fragt etwas Neues, und niemand hat
|
|
entschieden, was der Pruefstand darauf antworten soll. Stillschweigend "ja" zu
|
|
sagen waere die faule Loesung und wuerde genau die Faelle uebergehen, wegen
|
|
denen es die Pruefung gibt.
|
|
|
|
BENUTZUNG IM PRUEFSTAND
|
|
-----------------------
|
|
import pruefsteuerung as PS
|
|
PS.antwortet("Storno wirklich", True)
|
|
...
|
|
PS.gemeldet("Kundenangaben fehlen") -> True, wenn das Programm es sagte
|
|
"""
|
|
from tkinter import messagebox
|
|
|
|
# Was das Programm gemeldet haette: Liste aus (art, titel, text).
|
|
PROTOKOLL = []
|
|
# Fahrplan fuer Ja/Nein-Fragen: Textstueck im Titel -> Antwort.
|
|
ANTWORTEN = {}
|
|
_aktiv = False
|
|
|
|
|
|
def antwortet(titelstueck, wert):
|
|
"""Legt fest, wie eine Frage beantwortet wird, deren Titel das Stueck enthaelt."""
|
|
ANTWORTEN[titelstueck] = wert
|
|
|
|
|
|
def zuruecksetzen():
|
|
PROTOKOLL.clear()
|
|
ANTWORTEN.clear()
|
|
|
|
|
|
def meldungen(stueck=""):
|
|
"""Alle Meldungen (oder die, in denen das Stueck vorkommt)."""
|
|
return [m for m in PROTOKOLL if stueck in m[1] or stueck in m[2]]
|
|
|
|
|
|
def gemeldet(stueck):
|
|
"""Hat das Programm etwas gemeldet, in dem das Stueck vorkommt?"""
|
|
return bool(meldungen(stueck))
|
|
|
|
|
|
def _melden(art):
|
|
def gemeldet_(titel="", text="", **_kw):
|
|
PROTOKOLL.append((art, str(titel), str(text)))
|
|
print(f"[dialog] {art}: {titel}")
|
|
return "ok"
|
|
return gemeldet_
|
|
|
|
|
|
def _fragen(vorgabe=None):
|
|
def gefragt(titel="", text="", **_kw):
|
|
PROTOKOLL.append(("frage", str(titel), str(text)))
|
|
for stueck, wert in ANTWORTEN.items():
|
|
if stueck in str(titel) or stueck in str(text):
|
|
print(f"[dialog] frage: {titel} -> {wert}")
|
|
return wert
|
|
if vorgabe is not None:
|
|
return vorgabe
|
|
raise AssertionError(
|
|
f"unangemeldeter Dialog: {titel!r}\n"
|
|
f" {text!r}\n"
|
|
" -> im Pruefstand mit pruefsteuerung.antwortet(<Stueck>, True/False) "
|
|
"beantworten")
|
|
return gefragt
|
|
|
|
|
|
def einschalten():
|
|
"""Dialoge umleiten. Mehrfaches Aufrufen schadet nicht."""
|
|
global _aktiv
|
|
if _aktiv:
|
|
return
|
|
messagebox.showinfo = _melden("info")
|
|
messagebox.showwarning = _melden("warnung")
|
|
messagebox.showerror = _melden("fehler")
|
|
messagebox.askyesno = _fragen()
|
|
messagebox.askokcancel = _fragen()
|
|
messagebox.askretrycancel = _fragen(False)
|
|
messagebox.askquestion = _fragen()
|
|
_aktiv = True
|
|
print("[pruefsteuerung] Dialoge laufen ohne Fenster")
|