rechnungstool/pruefsteuerung.py
TheMockTv ca9ac212cf Amt-Blatt raus, Bedienung ueber die API, Erststart fragt nach zwei Wegen
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
2026-09-05 18:24:59 +02:00

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")