Sein Fund: "der ohne gemeinsamen ordner popup ist noch hell" und "er hebt den aktiven tab nicht hervor, der inaktive ist der hervorgehobene". - Meldungen und Ja/Nein-Fragen kommen jetzt aus eigenen Fenstern im Theme (MeldungMixin). messagebox sind Fenster von WINDOWS - sie bleiben hell, egal was das Programm einstellt. Dieselbe Erfahrung steckt schon in der eigenen Menueleiste. 49 Aufrufe umgestellt; das Ordner-Fenster meldet ueber das Hauptfenster mit. ⚠️ Im Pruflauf laeuft beides weiter ueber messagebox - dort haengen die Pruefstaende ihre Antworten ein. Sonst warten sie auf einen Klick. - Reiter: der OFFENE ist jetzt der hellere (Kachelfarbe), die ruhenden liegen auf dem dunklen Grund. Vorher war es andersherum. - config.TESTLAUF ist eine FUNKTION geworden: Pruefstaende importieren config oft, bevor sie CAMPINGHOF_TEST setzen - der einmal berechnete Wert stand dann den ganzen Lauf auf False, und das Programm oeffnete echte Dialoge, auf die niemand klickt. Genau daran hing pruef_api. - Der Erststart-Dialog hat einen eigenen Schalter (_erststart_zeigen), statt am ganzen Pruefschalter zu haengen: ein Pruefstand kann IHN zeigen, ohne dass alle anderen Meldungen wieder echte Fenster aufmachen. - Pruefstand-Fenster stehen nicht mehr in der Taskleiste (-toolwindow): sie liegen neben dem Bildschirm, und ein Eintrag, den man anklickt, ohne dass etwas erscheint, ist schlimmer als gar keiner. - pruef_nummernsperre drueckt jetzt den Radioknopf "Meine Zaehlung weiterfuehren", statt nur ins Zahlenfeld zu schreiben - das Feld ist ohne diesen Klick gesperrt, wie beim Benutzer auch. Alle 14 Pruefstaende gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
174 lines
6.6 KiB
Python
174 lines
6.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Pruefstand: das Fenster ueber die API bedienen - mit Schluessel.
|
|
|
|
Warum es das gibt (seine Ansage): *"bau dir steuer logik per api rein, dann
|
|
kannst damit die sachen triggern, brauchst meine maus nicht"* und *"mach pw
|
|
rein, also schluessel wie bei ssh key, das nur du und das beherbergungssteuer
|
|
ding rein kann, weil muss ja sprechen"*.
|
|
|
|
Geprueft wird:
|
|
|
|
1. Ohne Schluessel geht NICHTS - auch nicht "nur nachsehen".
|
|
2. Mit falschem Schluessel ebenfalls nicht.
|
|
3. Mit dem Schluessel aus dem gemeinsamen Ordner laesst sich das Fenster
|
|
fuellen, nachlesen und eine Vorschau erzeugen.
|
|
4. Was einen BELEG schreibt, geht nur im Testlauf - und schreibt dann
|
|
tatsaechlich die Rechnung.
|
|
5. ping und schau_nach bleiben schluessellos, sonst kann ein aelteres
|
|
Steuerjournal nicht mehr anklopfen.
|
|
"""
|
|
import json
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import threading
|
|
|
|
QUELLE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, QUELLE)
|
|
|
|
tmp = tempfile.mkdtemp(prefix="api_pruef_")
|
|
ordner = os.path.join(tmp, "rechnungen")
|
|
gemeinsam_ordner = os.path.join(tmp, "install")
|
|
os.makedirs(ordner)
|
|
os.makedirs(gemeinsam_ordner)
|
|
|
|
import config # noqa: E402
|
|
shutil.copy(os.path.join(QUELLE, "config.json"), os.path.join(tmp, "config.json"))
|
|
config.BASE_DIR = tmp
|
|
config.CONFIG_PATH = os.path.join(tmp, "config.json")
|
|
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
|
roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0
|
|
# Der Erststart-Zustand gehoert dem Pruefstand, nicht der config des
|
|
# Entwicklerrechners: sonst laeuft er in einen anderen Zweig, sobald hier
|
|
# jemand die Nummer einmal eingerichtet hat.
|
|
roh["nummer_eingerichtet"] = False
|
|
roh["output_dir"] = ordner
|
|
roh["gemeinsam_ordner"] = gemeinsam_ordner
|
|
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
|
|
|
os.environ["CAMPINGHOF_TEST"] = "1"
|
|
import api # noqa: E402
|
|
import app as A # noqa: E402
|
|
import gemeinsam # noqa: E402
|
|
from tkinter import messagebox # noqa: E402
|
|
|
|
|
|
def _reissleine():
|
|
print("ABBRUCH | Pruefstand haengt (Zeitgrenze)")
|
|
os._exit(2)
|
|
|
|
|
|
_wecker = threading.Timer(120, _reissleine)
|
|
_wecker.daemon = True
|
|
_wecker.start()
|
|
|
|
fehler = []
|
|
|
|
|
|
def pruefe(bedingung, text):
|
|
zeile = (" OK | " if bedingung else "FEHLER | ") + text
|
|
k = sys.stdout.encoding or "utf-8"
|
|
print(zeile.encode(k, "replace").decode(k))
|
|
if not bedingung:
|
|
fehler.append(text)
|
|
|
|
|
|
messagebox.showinfo = messagebox.showwarning = messagebox.showerror = lambda *a, **k: None
|
|
messagebox.askyesno = lambda *a, **k: True
|
|
|
|
f = A.RechnungsApp()
|
|
f.cfg["output_dir"] = ordner
|
|
f.buch = gemeinsam.Nummernbuch(gemeinsam_ordner)
|
|
f.api.starten(f.buch, ordner=gemeinsam_ordner)
|
|
f.update()
|
|
|
|
port = f.api.port
|
|
pruefe(bool(port), f"die API laeuft ({port})")
|
|
|
|
schluessel = api.schluessel_holen(gemeinsam_ordner)
|
|
pruefe(len(schluessel) == 64, f"ein Schluessel liegt im gemeinsamen Ordner ({len(schluessel)} Zeichen)")
|
|
pruefe(os.path.exists(os.path.join(gemeinsam_ordner, api.SCHLUESSEL_DATEI)),
|
|
"und zwar als Datei neben dem Nummernbuch")
|
|
pruefe(api.schluessel_holen(gemeinsam_ordner) == schluessel,
|
|
"beim zweiten Lesen kommt derselbe zurueck (kein neuer je Start)")
|
|
|
|
|
|
def rufen(**nachricht):
|
|
"""Direkt anrufen - der Pruefstand spielt hier das andere Programm."""
|
|
was = nachricht.pop("was", "aktion")
|
|
return api._reden(port, was, zeitgrenze=20.0, **nachricht)
|
|
|
|
|
|
# Das Fenster muss waehrend des Anrufs weiterlaufen: die Aktion wird in seinem
|
|
# Faden ausgefuehrt. Also nebenher anrufen und die Ereignisschleife drehen.
|
|
def anrufen_und_drehen(**nachricht):
|
|
ergebnis = {}
|
|
|
|
def anruf():
|
|
ergebnis["antwort"] = rufen(**nachricht)
|
|
|
|
t = threading.Thread(target=anruf, daemon=True)
|
|
t.start()
|
|
while t.is_alive():
|
|
f.update()
|
|
return ergebnis.get("antwort")
|
|
|
|
|
|
# ---- 1. + 2. Ohne (oder mit falschem) Schluessel: kein Zutritt -------------
|
|
pruefe(anrufen_und_drehen(name="zustand") == "kein zutritt",
|
|
"ohne Schluessel: kein Zutritt")
|
|
pruefe(anrufen_und_drehen(name="zustand", schluessel="0" * 64) == "kein zutritt",
|
|
"falscher Schluessel: kein Zutritt")
|
|
|
|
# ---- 5. Die alten zwei Woerter bleiben schluessellos -----------------------
|
|
pruefe(anrufen_und_drehen(was="ping") == api.ANTWORT_HALLO,
|
|
"ping antwortet weiterhin ohne Schluessel")
|
|
|
|
# ---- 3. Mit Schluessel: fuellen, nachlesen, Vorschau -----------------------
|
|
antwort = anrufen_und_drehen(
|
|
name="setze", schluessel=schluessel,
|
|
daten={"kunde": {"vorname": "Anna", "nachname": "Beispiel",
|
|
"strasse": "Musterweg 1", "plzort": "12345 Musterstadt"},
|
|
"anreise": "01.09.2026", "abreise": "04.09.2026",
|
|
"positionen": [{"nr": sorted(f.anzahl_vars)[0], "anzahl": 3}]})
|
|
pruefe(isinstance(antwort, dict) and antwort.get("kunde", {}).get("nachname") == "Beispiel",
|
|
f"mit Schluessel laesst sich das Formular fuellen ({str(antwort)[:60]}…)")
|
|
pruefe(antwort.get("gesamt", 0) > 0, f"und es ist gerechnet ({antwort.get('gesamt')})")
|
|
|
|
antwort = anrufen_und_drehen(name="vorschau", schluessel=schluessel)
|
|
pfad = (antwort or {}).get("pfad", "")
|
|
pruefe(bool(pfad) and os.path.exists(pfad), f"die Vorschau entsteht ohne Maus ({os.path.basename(pfad)})")
|
|
pruefe(not os.listdir(ordner), "und sie landet NICHT im Rechnungsordner")
|
|
|
|
# ---- 4. Beleg schreiben ---------------------------------------------------
|
|
antwort = anrufen_und_drehen(name="rechnung", schluessel=schluessel)
|
|
pdfs = [n for n in os.listdir(ordner) if n.lower().endswith(".pdf")]
|
|
pruefe(len(pdfs) == 1, f"im Testlauf schreibt die API die Rechnung ({pdfs})")
|
|
|
|
# Und im ECHTEN Betrieb nicht: dieselbe Aktion, ohne den Pruefschalter.
|
|
# Umgeschaltet wird die Umgebungsvariable selbst - das Programm fragt sie bei
|
|
# jeder Aktion neu ab, genau wie beim echten Start.
|
|
os.environ.pop("CAMPINGHOF_TEST", None)
|
|
antwort = anrufen_und_drehen(name="rechnung", schluessel=schluessel)
|
|
os.environ["CAMPINGHOF_TEST"] = "1"
|
|
pruefe(isinstance(antwort, dict) and "nur im Testlauf" in str(antwort.get("fehler", "")),
|
|
f"im echten Betrieb verweigert sie den Beleg ({antwort})")
|
|
pruefe(len([n for n in os.listdir(ordner) if n.lower().endswith(".pdf")]) == 1,
|
|
"und es kam keine zweite Rechnung dazu")
|
|
|
|
# Unbekannter Name sagt, was es gibt - statt still nichts zu tun.
|
|
antwort = anrufen_und_drehen(name="tanz", schluessel=schluessel)
|
|
pruefe("kenne ich nicht" in str(antwort), f"unbekannte Aktion wird benannt ({str(antwort)[:50]}…)")
|
|
|
|
f.api.stoppen(f.buch)
|
|
f.destroy()
|
|
print()
|
|
if fehler:
|
|
print(f"FEHLGESCHLAGEN: {len(fehler)}")
|
|
for x in fehler:
|
|
print(" -", x)
|
|
print(f"(Ordner bleibt: {tmp})")
|
|
sys.exit(1)
|
|
shutil.rmtree(tmp, ignore_errors=True)
|
|
print("ALLES GRUEN")
|