Logo Das Programmlogo steht jetzt klein neben "powered by ravokk" - dort sitzt die Kennung des Werkzeugs, nicht die des Kunden. Eine eigene Kopfzeile hat dieses Programm nicht; oben sind die Reiter. bauen.py Der Weg von der Quelle zur Setup-Datei war eine Folge von Befehlen, die jemand von Hand eintippen musste. Wer einen Schritt vergisst, liefert eine Setup-Datei mit einer alten EXE aus - und sieht es ihr nicht an. Genau das ist heute passiert. Jetzt ist es ein Befehl, die Nummer kommt aus version.py, und am Ende stehen die SHA256-Summen fuer den Release-Text. pruef_start_exe.py Prueft, was kein anderer Pruefstand prueft: ob die GEBAUTE EXE ein sichtbares Fenster zeigt. Alle uebrigen fahren das Programm aus der Quelle, dort trat der Fehler nie auf. "Der Prozess laeuft" beweist dabei nichts - er lief ja, waehrend nichts zu sehen war. Aufgeraeumt "Rechnung Campinghof.exe" ist weg; gebaute Dateien gehoeren ins Release, nicht ins Repo. Die .spec-Dateien dagegen schon: ohne sie kann niemand die EXE nachbauen, die der Updater ausliefert. Fassung 1.7.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
193 lines
6.3 KiB
Python
193 lines
6.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Pruefstand: der Hinweis auf die Erprobungsfassung und die Kenntnisnahme.
|
|
|
|
Sein Wortlaut vom 06.09.2026:
|
|
|
|
"wenn er nein drueckt oder ablehnt, schliesst sich die software und geht
|
|
beim naechsten start wieder mit dem selben auf, bis er auf ja drueckt"
|
|
"und das alles auch noch mal in die info mit signatur: user hat dies
|
|
gelesen und zur kenntnis genommen"
|
|
|
|
Geprueft wird die ganze Strecke, weil jeder Teil fuer sich wertlos waere:
|
|
|
|
1. Der Hinweis kommt beim Start und nennt Haftung, Vorschau und die
|
|
Fehleradresse.
|
|
2. NEIN -> das Fenster ist zu, und in der Konfiguration steht NICHTS.
|
|
Ein "Nein", das trotzdem vermerkt wird, waere eine Luege.
|
|
3. Neuer Start -> es wird WIEDER gefragt.
|
|
4. JA -> Signatur mit Benutzer, Zeit und Fassung; danach wird nicht mehr
|
|
gefragt.
|
|
5. Andere Fassung des Textes -> es wird erneut gefragt. Eine Zustimmung gilt
|
|
fuer das, was dagestanden hat, nicht fuer alles Kuenftige.
|
|
"""
|
|
import json
|
|
import os
|
|
import time
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import threading
|
|
|
|
QUELLE = r"C:\claude\rechnungstool"
|
|
sys.path.insert(0, QUELLE)
|
|
|
|
fehler = []
|
|
|
|
|
|
def pruefe(bedingung, text):
|
|
zeile = (" OK | " if bedingung else "FEHLER | ") + text
|
|
kod = sys.stdout.encoding or "utf-8"
|
|
print(zeile.encode(kod, "replace").decode(kod))
|
|
if not bedingung:
|
|
fehler.append(text)
|
|
|
|
|
|
def _reissleine():
|
|
print("ABBRUCH | Pruefstand haengt (Zeitgrenze)")
|
|
os._exit(2)
|
|
|
|
|
|
_wecker = threading.Timer(120, _reissleine)
|
|
_wecker.daemon = True
|
|
_wecker.start()
|
|
|
|
tmp = tempfile.mkdtemp(prefix="hinweis_pruef_")
|
|
ordner = os.path.join(tmp, "rechnungen")
|
|
os.makedirs(ordner)
|
|
|
|
import config # noqa: E402
|
|
|
|
shutil.copy(os.path.join(QUELLE, "config.json"), os.path.join(tmp, "config.json"))
|
|
config.CONFIG_PATH = os.path.join(tmp, "config.json")
|
|
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
|
roh["output_dir"] = ordner
|
|
roh["nummer_eingerichtet"] = True
|
|
for weg in ("hinweis_fassung", "hinweis_zeit", "hinweis_benutzer"):
|
|
roh.pop(weg, None)
|
|
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"),
|
|
ensure_ascii=False, indent=2)
|
|
|
|
os.environ["CAMPINGHOF_TEST"] = "1"
|
|
import app as A # noqa: E402
|
|
import hinweis as H # noqa: E402
|
|
from tkinter import messagebox # noqa: E402
|
|
|
|
antworten = {"Fertig": False, "Hausnummer": True}
|
|
gesehen = []
|
|
|
|
|
|
def askyesno(titel, text, **kw):
|
|
gesehen.append((str(titel), str(text)))
|
|
for schlue, wert in antworten.items():
|
|
if schlue in titel:
|
|
return wert
|
|
raise AssertionError(f"unangemeldeter Dialog: {titel!r}")
|
|
|
|
|
|
messagebox.askyesno = askyesno
|
|
messagebox.showinfo = lambda *a, **k: None
|
|
messagebox.showwarning = lambda *a, **k: None
|
|
messagebox.showerror = lambda *a, **k: print("[fehler]", a[0] if a else "")
|
|
|
|
|
|
def pumpen(fenster, bedingung, sekunden=6):
|
|
"""Ereignisschleife drehen, bis die Bedingung stimmt.
|
|
|
|
Der Hinweis wartet, bis das Fenster gezeichnet ist, und meldet sich per
|
|
after(). Ein einzelnes update() erreicht das nicht - hier wird die Schleife
|
|
gedreht wie mainloop() es taete. Abgekuerzt wird nichts.
|
|
"""
|
|
ende = time.time() + sekunden
|
|
while time.time() < ende:
|
|
try:
|
|
fenster.update()
|
|
except Exception: # noqa: BLE001 - Fenster kann zwischendurch weg sein
|
|
return bedingung()
|
|
if bedingung():
|
|
return True
|
|
time.sleep(0.02)
|
|
return bedingung()
|
|
|
|
|
|
def gelesen():
|
|
return json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
|
|
|
|
|
# ---- 1./2. Ablehnen: Fenster zu, nichts vermerkt -----------------------
|
|
antworten["Erprobungsfassung"] = False
|
|
f = A.RechnungsApp()
|
|
pumpen(f, lambda: any("Erprobungsfassung" in t[0] for t in gesehen))
|
|
|
|
treffer = [t for t in gesehen if "Erprobungsfassung" in t[0]]
|
|
pruefe(bool(treffer), "der Hinweis kommt beim Start")
|
|
if treffer:
|
|
inhalt = treffer[0][1]
|
|
pruefe("ERPROBUNGSFASSUNG" in inhalt.upper(), "er nennt die Erprobungsfassung")
|
|
pruefe("Haftung" in inhalt, "er nennt die Haftung")
|
|
pruefe("Vorschau" in inhalt, "er verweist auf die Vorschau")
|
|
pruefe("git.pcore.de/TheMockTv/rechnungstool" in inhalt,
|
|
"er nennt die Adresse fuer Fehlermeldungen")
|
|
|
|
def fenster_zu(fenster):
|
|
"""True, wenn es das Fenster nicht mehr gibt.
|
|
|
|
Nach destroy() antwortet winfo_exists() nicht mehr mit 0, sondern wirft -
|
|
die ganze Anwendung ist weg. Beides heisst hier dasselbe.
|
|
"""
|
|
import tkinter
|
|
try:
|
|
return not fenster.winfo_exists()
|
|
except tkinter.TclError:
|
|
return True
|
|
|
|
|
|
pruefe(fenster_zu(f), "nach Nein ist das Fenster zu")
|
|
cfg = gelesen()
|
|
pruefe(not cfg.get("hinweis_zeit"), "nach Nein steht nichts in der Konfiguration")
|
|
|
|
# ---- 3. Neuer Start: es wird wieder gefragt ----------------------------
|
|
gesehen.clear()
|
|
antworten["Erprobungsfassung"] = True
|
|
f = A.RechnungsApp()
|
|
pumpen(f, lambda: any("Erprobungsfassung" in t[0] for t in gesehen))
|
|
pruefe(any("Erprobungsfassung" in t[0] for t in gesehen),
|
|
"beim naechsten Start wird wieder gefragt")
|
|
|
|
# ---- 4. Zustimmen: Signatur steht ---------------------------------------
|
|
cfg = gelesen()
|
|
pruefe(cfg.get("hinweis_fassung") == H.FASSUNG,
|
|
f"die Fassung ist vermerkt ({cfg.get('hinweis_fassung')!r})")
|
|
pruefe(bool(cfg.get("hinweis_zeit")), "der Zeitpunkt ist vermerkt")
|
|
pruefe(bool(cfg.get("hinweis_benutzer")), "der Benutzer ist vermerkt")
|
|
sig = H.signatur(f.cfg)
|
|
pruefe("gelesen und zur kenntnis genommen" in sig.lower(),
|
|
f"die Signaturzeile ist lesbar ({sig!r})")
|
|
|
|
# ---- 5. Kein zweites Mal fragen -----------------------------------------
|
|
gesehen.clear()
|
|
f.destroy()
|
|
f = A.RechnungsApp()
|
|
pumpen(f, lambda: any("Erprobungsfassung" in t[0] for t in gesehen), sekunden=2)
|
|
pruefe(not any("Erprobungsfassung" in t[0] for t in gesehen),
|
|
"nach dem Ja wird nicht noch einmal gefragt")
|
|
|
|
# ---- 6. Andere Fassung -> wieder fragen ---------------------------------
|
|
gesehen.clear()
|
|
f.destroy()
|
|
H.FASSUNG = "9999-12-31" # so, als waere der Text geaendert worden
|
|
f = A.RechnungsApp()
|
|
pumpen(f, lambda: any("Erprobungsfassung" in t[0] for t in gesehen))
|
|
pruefe(any("Erprobungsfassung" in t[0] for t in gesehen),
|
|
"bei geaenderter Fassung wird erneut gefragt")
|
|
|
|
_wecker.cancel()
|
|
try:
|
|
f.destroy()
|
|
except Exception: # noqa: BLE001
|
|
pass
|
|
shutil.rmtree(tmp, ignore_errors=True)
|
|
|
|
if fehler:
|
|
print(f"ROT | {len(fehler)} Punkt(e) nicht erfuellt")
|
|
sys.exit(1)
|
|
print("GRUEN | Hinweis, Ablehnung und Signatur wirken")
|