rechnungstool/pruef_dialoge.py
TheMockTv d67448c58f Funde der drei Agenten: zwei kaputte Dialoge, haengende Flags, scharfe Pruefstaende
REGRESSION aus der Modulaufteilung (Code-Review-Agent):
- einstellungen.py fehlten "berechnung" und "datetime". Menue > Leistungskatalog
  und > Rechnungsnummer stuerzten mit NameError ab und liessen ein leeres,
  modales Fenster stehen. Preise und Nummernkreis waren nicht mehr einstellbar.
  KEIN Pruefstand hat das gemerkt - keiner oeffnete je einen Dialog. Neu:
  pruef_dialoge.py oeffnet ALLE Dialoge und schliesst sie wieder.

HAENGENDE ZWISCHENSPEICHER (derselbe Agent, gemessen):
- "Zuruecksetzen" liess storno_stand/berichtigung_stand stehen. Danach gehoerten
  die Daten eines FREMDEN Gastes zum Storno der alten Rechnung, und die
  Berichtigung schrieb ein Blatt mit Nummer A im Kenndatensatz und Nummer B auf
  dem Blatt. reset_formular fragt jetzt nach und verwirft sauber.
- _neue_nummer_vorschlagen fasst die Nummer nicht mehr an, solange ein Vorgang
  laeuft (traf auch dlg_nummer).
- Fenster schliessen mit offenem Storno fragt jetzt nach (WM_DELETE_WINDOW).
- _storno_verwerfen bricht ab, wenn das PDF nicht geloescht werden kann, statt
  den Zaehler trotzdem zurueckzudrehen.
- Der Storno prueft die Kundenangaben und fragt, wenn die alte Rechnung sie
  nicht hergibt.
- Zaehler nicht speicherbar -> sichtbare Meldung statt Traceback ins Nichts.
- storno.py: v1-Rueckfall nimmt den ersten Satz OHNE extra_blatt.
- EINE Quelle fuer die naechste Nummer: _neue_nummer_vorschlagen benutzt jetzt
  naechste_freie_nummer, der Jahreswechsel steht nur noch dort. Vorher schlug
  das Formular 2026-001 vor, waehrend der Storno-Weg 2025-088 nahm.

PRUEFSTAENDE (Audit-Agent: 16 von 27 eingebauten Fehlern blieben unbemerkt):
- Neu pruef_kernregeln.py (27 Pruefungen) fuer die zwei Regeln, um die es geht:
  Nummer von HAND auf eine vergebene setzen, Zaehler hinter dem Ordner,
  Jahreswechsel, Zaehler nur vorwaerts, Storno ueber den DIALOGKNOPF statt der
  internen Methode, zweite Berichtigung am selben Tag. Der Bestand wird ueber
  SHA256 verglichen - "Datei ist noch da" heisst nicht "unveraendert".
- Der Text des Storno-PDFs wird gelesen: Positionszeile negativ, nicht nur der
  Summenblock.
- Tautologien raus: Selbstvergleich beim storno_datum, all() ueber eine leere
  Liste, "nicht" in einem deutschen Text.
- Unangemeldeter Dialog laesst den Lauf scheitern, statt still "Nein" zu sagen.
- pruef_nummern.py: .PDF gross geschrieben, Rechnung_*.txt, echte Praefix-Falle.
- Gegenprobe: sechs Mutationen eingebaut, die vorher gruen blieben - alle sechs
  werden jetzt rot.

RECHT (Gegenpruefungs-Agent): Zitate praezisiert. § 146 Abs. 4 AO verbietet nur
Aenderungen, bei denen der urspruengliche Inhalt nicht mehr feststellbar ist -
ein protokollierter Storno-Vermerk waere erlaubt, wir verzichten trotzdem
bewusst darauf. Der § 14c-Hinweis bleibt, gilt aber gegenueber Endverbrauchern
nicht (EuGH C-378/21, BMF v. 27.02.2024) - er zielt auf Firmengaeste.

142 Pruefungen gruen (100 + 27 + 8 + 7).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKvGkdNW1vKdnMPAM9Bp3X
2026-09-02 19:57:31 +02:00

91 lines
2.8 KiB
Python

# -*- coding: utf-8 -*-
"""Pruefstand: geht jeder Dialog ueberhaupt auf?
Klingt banal, ist es nicht: beim Aufteilen von app.py in Module sind zwei
Dialoge stumm kaputtgegangen (fehlende Importe), und kein anderer Pruefstand
hat es gemerkt, weil keiner sie oeffnet. Dieser hier oeffnet ALLE.
Geprueft wird ausserdem, dass jeder Dialog auch wieder zugeht - ein modales
Fenster, das haengenbleibt, sperrt das Programm.
"""
import json
import os
import shutil
import sys
import tempfile
import tkinter as tk
QUELLE = r"C:\claude\rechnungstool"
sys.path.insert(0, QUELLE)
tmp = tempfile.mkdtemp(prefix="dialog_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
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False)
import app as A # noqa: E402
from tkinter import messagebox # noqa: E402
messagebox.showinfo = lambda *a, **k: None
messagebox.showwarning = lambda *a, **k: None
messagebox.showerror = lambda *a, **k: None
messagebox.askyesno = lambda *a, **k: False
fehler = 0
f = A.RechnungsApp()
f.update()
# Damit "Storno"/"Berichtigen" nicht an "nichts zu korrigieren" scheitern:
# eine echte Rechnung in den Wegwerf-Ordner legen.
f.k_vorname.set("Anna")
f.k_name.set("Beispiel")
f.k_strasse.set("Musterweg 1")
f.k_plzort.set("12345 Musterstadt")
f.anzahl_vars[sorted(f.anzahl_vars)[0]].set("2")
f.update()
f.pdf_erstellen()
DIALOGE = ["dlg_steuern", "dlg_katalog", "dlg_firma", "dlg_nummer",
"dlg_storno", "dlg_berichtigen"]
for name in DIALOGE:
offen_vorher = [w for w in f.winfo_children() if isinstance(w, tk.Toplevel)]
try:
getattr(f, name)()
f.update()
except Exception as e: # noqa: BLE001 - genau das wollen wir sehen
print(f"ABSTURZ | {name}: {type(e).__name__}: {e}")
fehler += 1
continue
neu = [w for w in f.winfo_children()
if isinstance(w, tk.Toplevel) and w not in offen_vorher]
if not neu:
print(f"FEHLER | {name}: kein Fenster aufgegangen")
fehler += 1
continue
titel = neu[-1].title()
for w in neu:
w.grab_release()
w.destroy()
f.update()
print(f" OK | {name} -> „{titel}“")
# Der Speicherort-Dialog oeffnet einen Systemdialog - nur die Vorstufe pruefen
try:
f._frage_speicherort_falls_noetig()
f.update()
print(" OK | _frage_speicherort_falls_noetig")
except Exception as e: # noqa: BLE001
print(f"ABSTURZ | _frage_speicherort_falls_noetig: {type(e).__name__}: {e}")
fehler += 1
f.destroy()
print("---")
print("alles gruen" if not fehler else f"{fehler} Fehler")
sys.exit(1 if fehler else 0)