- "Rechnung endgueltig erstellen?" nennt Nummer, Kunde und Betrag und weist darauf hin, dass danach nur noch Storno oder Berichtigung bleibt (§ 14 Abs. 4 Nr. 4 UStG, § 31 Abs. 5 UStDV). Geprueft ist auch, dass "Nein" wirklich nichts schreibt - sonst waere die Frage eine Attrappe. - Als EXE legt das Programm sich beim ersten Start einen Ordner "Daten" neben die EXE (config.json). Ist dort nicht schreibbar (EXE unter "Programme"), weicht es nach LOCALAPPDATA aus. Lag die config.json noch direkt neben der EXE, zieht sie um - sonst faengt das Programm ohne Firmendaten neu an. - Aufruf von _storno_fortsetzen entfernt: die Methode gibt es nicht mehr, seit die Kette auf dem Storno-Blatt steht. War unerreichbar, waere aber beim naechsten Anfassen ein AttributeError geworden. - Pruefstaende raeumen ihren Wegwerf-Ordner weg und haben eine Reissleine (Abbruch nach zwei Minuten), damit kein Dialog mehr ewig offen steht. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EKvGkdNW1vKdnMPAM9Bp3X
116 lines
3.7 KiB
Python
116 lines
3.7 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: True # Sicherheitsfrage bejahen
|
|
|
|
|
|
# --- Reissleine ---------------------------------------------------------
|
|
# Ein Pruefstand darf NIE haengen. Bleibt er in einem Dialog stehen (etwa weil
|
|
# eine Aenderung eine neue Frage eingefuehrt hat), bricht er nach zwei Minuten
|
|
# von selbst ab, statt ein Fenster offen zu lassen, auf das niemand klickt.
|
|
import threading as _th
|
|
|
|
|
|
def _reissleine():
|
|
import os as _os
|
|
print("ABBRUCH | Pruefstand haengt (Zeitgrenze) - vermutlich ein Dialog, "
|
|
"der nicht beantwortet wird")
|
|
_os._exit(2)
|
|
|
|
|
|
_wecker = _th.Timer(120, _reissleine)
|
|
_wecker.daemon = True
|
|
_wecker.start()
|
|
|
|
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()
|
|
|
|
# Wegwerf-Ordner nur bei Fehlern behalten - sonst laeuft das Temp-Verzeichnis
|
|
# voll. Ein Pruefstand, der 400 Ordner hinterlaesst, ist selbst ein Problem.
|
|
if not fehler: # fehler ist hier ein Zaehler
|
|
import shutil as _sh
|
|
_sh.rmtree(tmp, ignore_errors=True)
|
|
print("---")
|
|
print("alles gruen" if not fehler else f"{fehler} Fehler")
|
|
sys.exit(1 if fehler else 0)
|