Die Runde vom 07.09.2026 hat neben den zwei echten Fehlern eine Reihe Wiederholungen und stumme Stellen gemeldet. Abgearbeitet in Etappen, jede fuer sich pruefbar - nach jeder liefen beide Pruefketten gruen. * Menueleiste: der Wechsel von einem Titel zum anderen brauchte ZWEI Klicks. Der grab_set der offenen Liste schluckt den ersten, die <Button-1>-Bindung des Titels feuert gar nicht. Das native tk.Menu, das diese Leiste ersetzt hat, wechselte schon beim blossen Ueberfahren - zwei Klicks waren also eine Verschlechterung gegenueber dem, was vorher da war. Die Klappliste wird ausserdem jetzt wie jede andere Position ueber theme.auf_bildschirm() begrenzt; am unteren Fensterrand lief sie vorher hinaus. Neu: pruef_menueleiste.py misst beides ueber echte Klickereignisse. * %LOCALAPPDATA%/ravokk wurde an drei Stellen einzeln ausgerechnet. Jetzt fragen alle gemeinsam.standard_ordner(). Laufen die je auseinander, fuehren die beiden Programme zwei Nummernbuecher - und das faellt erst auf, wenn eine Rechnungsnummer zum zweiten Mal vergeben ist (§ 14 Abs. 4 Nr. 4 UStG). * pruefe() stand 35-mal im Quelltext, in sechs Fassungen -> pruefhelfer.py. Die zwei Ausreisser sind mitgezogen: pruef_bilder.py schrieb ein eigenes Format, pruef_storno_verrechnet.py zaehlte in einer Zahl und konnte am Ende nicht sagen, WAS fehlschlug. * dlg_darstellung() und die drei Handgriffe des Umschaltens standen in beiden app.py fast wortgleich -> ThemeMixin in theme.py. Beide trugen inzwischen denselben langen Kommentar zu demselben Fehler; das war der Beweis, dass es eine Sache ist. Programmspezifisch bleiben der Hinweistext im Dialog (R31) und die Frage, ob die Einstellungen ausdruecklich gespeichert werden muessen. * pruef_gemeinsam_automatisch.py sagte im Text "genau ein Buch" und prueft ">= 1" mit einem any() - eine Pruefung, die nicht rot werden kann. Jetzt == 1, und der Dateiname wird mitgeprueft. Dazu neu: die neun Dateien, die im Kopf zusagen, sie laegen in beiden Programmen gleich, werden byteweise verglichen. Bei hinweis.py stimmte die Zusage seit dem 06.09. nicht mehr - gleicher Inhalt, CRLF gegen LF. * .gitattributes, damit die Zeilenenden nicht von der Maschine abhaengen. Ohne das meldet genau diese Pruefung nach einem frischen Checkout einen Unterschied, den es im Repository gar nicht gibt. * Sechs stumme "except OSError" sagen jetzt, warum sie schweigen duerfen, und einer meldet statt zu schweigen: schlaegt die Uebernahme einer alten config.json fehl, faengt das Programm ohne Firmendaten, Katalog und Zaehler neu an - und der Erfolgsfall schrieb eine Zeile, der Fehlerfall nicht. Die 24 stummen tk.TclError bleiben: dort wird ein Widget angefasst, das gerade zerstoert wurde, und eine Meldung waere Rauschen. * Tote Parameter und Variablen aus dem Umbau des Vortags: symbol_setzen(dunkel=), kachel(grund=, radius=), mit_trennlinie, sechs x/y-Berechnungen, die von theme.mittig() sofort ueberschrieben wurden, vier lokale "import ctypes". 39 Pruefstaende, 0 rot. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
155 lines
5.1 KiB
Python
155 lines
5.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Pruefstand: die Freigabe vor dem Beleg - und ihre Spur in der SQLite.
|
|
|
|
Sein Satz vom 06.09.2026:
|
|
|
|
"ich will fuer den pruefer sichtbar machen: ich wollte das so. die software
|
|
dokumentiert das, und damit wird es nachvollziehbar, weil es einen
|
|
checkmark nun gibt in der sqlite" - "das soll alles von anfang bis ende
|
|
pruefbar sein"
|
|
|
|
Geprueft wird beides, weil jedes einzeln wertlos waere:
|
|
|
|
1. NEIN -> es entsteht KEIN Beleg, die Nummer bleibt frei, im Buch steht
|
|
nichts. Ein Popup, das man verneint und trotzdem eine Rechnung
|
|
bekommt, waere schlimmer als gar keins.
|
|
2. JA -> der Beleg entsteht UND im Nummernbuch steht ein Zeitstempel der
|
|
Freigabe. Ohne diesen Eintrag koennte spaeter niemand belegen,
|
|
dass ausdruecklich freigegeben wurde.
|
|
"""
|
|
import json
|
|
import os
|
|
import re
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import threading
|
|
|
|
QUELLE = r"C:\claude\rechnungstool"
|
|
sys.path.insert(0, QUELLE)
|
|
|
|
|
|
def _reissleine():
|
|
print("ABBRUCH | Pruefstand haengt (Zeitgrenze)")
|
|
os._exit(2)
|
|
|
|
|
|
_wecker = threading.Timer(120, _reissleine)
|
|
_wecker.daemon = True
|
|
_wecker.start()
|
|
|
|
tmp = tempfile.mkdtemp(prefix="freigabe_pruef_")
|
|
ordner = os.path.join(tmp, "rechnungen")
|
|
install = os.path.join(tmp, "install")
|
|
os.makedirs(ordner)
|
|
os.makedirs(install)
|
|
|
|
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["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0
|
|
roh["nummer_eingerichtet"] = False
|
|
roh["output_dir"] = ordner
|
|
roh["gemeinsam_ordner"] = install
|
|
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 gemeinsam # noqa: E402
|
|
from tkinter import messagebox # noqa: E402
|
|
from pruefhelfer import fehler, pruefe
|
|
|
|
antworten = {"Fertig": False, "Hausnummer": True}
|
|
gefragt = []
|
|
|
|
|
|
def askyesno(titel, text, **kw):
|
|
gefragt.append(titel)
|
|
for schlue, wert in antworten.items():
|
|
if schlue in titel:
|
|
return wert
|
|
raise AssertionError(f"unangemeldeter Dialog: {titel!r}")
|
|
|
|
|
|
# Der Erprobungshinweis wird hier bejaht - geprueft wird er in pruef_hinweis.py.
|
|
antworten["Erprobungsfassung"] = True
|
|
messagebox.askyesno = askyesno
|
|
messagebox.showinfo = lambda *a, **k: None
|
|
warnungen = []
|
|
messagebox.showwarning = lambda *a, **k: warnungen.append(a[0] if a else "")
|
|
messagebox.showerror = lambda *a, **k: print("[fehler]", a[0] if a else "")
|
|
|
|
f = A.RechnungsApp()
|
|
f.update()
|
|
f.cfg["output_dir"] = ordner
|
|
# Im laufenden Programm oeffnet ein 400-ms-Timer das gemeinsame Buch. Den
|
|
# erreicht ein Pruefstand nie - also wird dieselbe Stelle direkt gerufen.
|
|
# Abgekuerzt wird damit nur das Warten, nicht der Weg.
|
|
f._frage_gemeinsam_falls_noetig()
|
|
pruefe(f.buch is not None, "das gemeinsame Nummernbuch ist offen")
|
|
|
|
|
|
def kunde_fuellen():
|
|
f.k_vorname.set("Anna")
|
|
f.k_name.set("Beispiel")
|
|
f.k_strasse.set("Musterweg 1")
|
|
f.k_plzort.set("12345 Musterstadt")
|
|
f.r_anreise.set("01.08.2026")
|
|
f.r_abreise.set("05.08.2026")
|
|
erste = sorted(f.anzahl_vars)[0]
|
|
f.anzahl_vars[erste].set("4")
|
|
f.update()
|
|
|
|
|
|
# ---- 1. NEIN: es darf nichts entstehen ----------------------------------
|
|
antworten["jetzt erstellen?"] = False
|
|
kunde_fuellen()
|
|
zaehler_vorher = f.cfg.get("rechnung_zaehler")
|
|
nummer_vorher = f.r_nummer.get()
|
|
f.pdf_erstellen()
|
|
|
|
pdfs = [x for x in os.listdir(ordner) if x.lower().endswith(".pdf")]
|
|
pruefe(not pdfs, f"nach Nein: kein PDF im Ordner (gefunden: {pdfs})")
|
|
pruefe(any("jetzt erstellen?" in t for t in gefragt),
|
|
"die Freigabe wurde ueberhaupt gefragt")
|
|
pruefe(f.cfg.get("rechnung_zaehler") == zaehler_vorher,
|
|
"nach Nein: der Rechnungszaehler ist unveraendert")
|
|
pruefe(f.r_nummer.get() == nummer_vorher,
|
|
"nach Nein: die Nummer im Feld ist unveraendert")
|
|
|
|
buch = gemeinsam.Nummernbuch(install)
|
|
pruefe(not buch.nummern(), f"nach Nein: das Nummernbuch ist leer ({buch.nummern()})")
|
|
|
|
# ---- 2. JA: Beleg entsteht und die Freigabe steht in der SQLite ---------
|
|
antworten["jetzt erstellen?"] = True
|
|
kunde_fuellen()
|
|
f.pdf_erstellen()
|
|
|
|
pdfs = [x for x in os.listdir(ordner) if x.lower().endswith(".pdf")]
|
|
pruefe(len(pdfs) == 1, f"nach Ja: genau ein PDF ({pdfs})")
|
|
|
|
nummern = sorted(buch.nummern())
|
|
pruefe(len(nummern) == 1, f"nach Ja: eine Nummer im Buch ({nummern})")
|
|
|
|
freigabe = ""
|
|
if nummern:
|
|
with buch._verbindung() as con:
|
|
zeile = con.execute("SELECT freigabe FROM nummern WHERE nummer=?",
|
|
(nummern[0],)).fetchone()
|
|
freigabe = (zeile["freigabe"] if zeile else "") or ""
|
|
|
|
pruefe(bool(freigabe), "die Freigabe steht in der SQLite")
|
|
pruefe(bool(re.fullmatch(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}", freigabe)),
|
|
f"der Zeitstempel ist ein ISO-Datum ({freigabe!r})")
|
|
|
|
_wecker.cancel()
|
|
f.destroy()
|
|
shutil.rmtree(tmp, ignore_errors=True)
|
|
|
|
if fehler:
|
|
print(f"ROT | {len(fehler)} Punkt(e) nicht erfuellt")
|
|
sys.exit(1)
|
|
print("GRUEN | Freigabe wirkt und wird dokumentiert")
|