# -*- coding: utf-8 -*- """Pruefstand: der Rechnungszaehler kann nicht mehr zurueck. Sein Einwand: *"er kann jetzt bescheissen, indem er das in den einstellungen aendert"* - und der Vorschlag dazu: *"dann koennte man noch die pdfs nutzen als referenz, die haben auch die nummern"*. Geprueft wird beides als Quelle: * die PDFs im Rechnungsordner (Dateiname traegt die Nummer) * das gemeinsame Nummernbuch (kennt auch Nummern OHNE PDF - Altbestand) Vorwaerts darf der Zaehler: eine lueckenlose Folge verlangt das Gesetz nicht. Zurueck nicht: jede Nummer darf es nur einmal geben (ยง 14 Abs. 4 Nr. 4 UStG). """ import json import os 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 kodierung = sys.stdout.encoding or "utf-8" print(zeile.encode(kodierung, "replace").decode(kodierung)) if not bedingung: fehler.append(text) def _reissleine(): print("ABBRUCH | Pruefstand haengt (Zeitgrenze)") os._exit(2) _wecker = threading.Timer(150, _reissleine) _wecker.daemon = True _wecker.start() os.environ["CAMPINGHOF_TEST"] = "1" tmp = tempfile.mkdtemp(prefix="sperre_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.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["output_dir"] = ordner json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2) import app as A # noqa: E402 import gemeinsam # noqa: E402 from tkinter import messagebox # noqa: E402 warnungen = [] messagebox.askyesno = lambda t, x, **k: ("Fertig" not in t) messagebox.showinfo = lambda *a, **k: None 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.cfg["output_dir"] = ordner JAHR = 2026 if int(f.cfg.get("rechnung_jahr", 0)) != JAHR: print("HINWEIS | Der Pruefstand rechnet mit 2026 - heute ist ein anderes Jahr.") def rechnung(name): f.reset_formular() f.k_vorname.set("Anna") f.k_name.set(name) f.k_strasse.set("Musterweg 1") f.k_plzort.set("12345 Musterstadt") f.r_anreise.set("01.08.2026") f.r_abreise.set("03.08.2026") f.anzahl_vars[sorted(f.anzahl_vars)[0]].set("2") f._testwahl = "rechnung" f.update() f.pdf_erstellen() # ---- 1. Drei Rechnungen schreiben ---------------------------------------- for name in ("Eins", "Zwei", "Drei"): rechnung(name) dateien = sorted(d for d in os.listdir(ordner) if d.endswith(".pdf")) pruefe(len(dateien) == 3, f"drei Rechnungen geschrieben ({dateien})") pruefe(f.cfg.get("rechnung_zaehler") == 3, f"Zaehler steht auf 3 ({f.cfg.get('rechnung_zaehler')})") pruefe(f.hoechster_stand(JAHR) == 3, f"hoechster Stand aus den PDFs: 3 ({f.hoechster_stand(JAHR)})") # ---- 2. Zaehler von Hand zurueckdrehen (der Betrugsfall) ----------------- f.cfg["rechnung_zaehler"] = 0 config.save_config(f.cfg) f._neue_nummer_vorschlagen() pruefe(f.r_nummer.get() == "2026-004", f"trotz zurueckgedrehtem Zaehler wird 2026-004 vorgeschlagen ({f.r_nummer.get()})") pruefe(f.cfg.get("rechnung_zaehler") == 3, f"und der Zaehler ist wieder angehoben ({f.cfg.get('rechnung_zaehler')})") # ---- 3. Auch die Einstellungen lassen ihn nicht zurueck ------------------ def knoepfe_von(w, aus=None): aus = [] if aus is None else aus for k in w.winfo_children(): if k.winfo_class() in ("TButton", "Button"): aus.append(k) knoepfe_von(k, aus) return aus def felder_von(w, aus=None): aus = [] if aus is None else aus for k in w.winfo_children(): if k.winfo_class() in ("TEntry", "Entry"): aus.append(k) felder_von(k, aus) return aus def nummerndialog(neuer_zaehler): """Dialog oeffnen, Zaehler eintragen, Speichern druecken.""" f.dlg_nummer() f.update() top = [w for w in f.winfo_children() if w.winfo_class() == "Toplevel"][-1] felder = felder_von(top) felder[1].delete(0, "end") felder[1].insert(0, str(neuer_zaehler)) f.update() for k in knoepfe_von(top): if "Speichern" in str(k.cget("text")): k.invoke() break f.update() noch_offen = [w for w in f.winfo_children() if w.winfo_class() == "Toplevel"] for w in noch_offen: w.destroy() return bool(noch_offen) warnungen.clear() blieb_offen = nummerndialog(1) pruefe(f.cfg.get("rechnung_zaehler") == 3, f"die Einstellungen setzen den Zaehler NICHT auf 1 ({f.cfg.get('rechnung_zaehler')})") pruefe(any("vergeben" in w for w in warnungen), f"mit Meldung an den Benutzer ({warnungen})") pruefe(blieb_offen, "und der Dialog bleibt offen, statt still zu schlucken") warnungen.clear() nummerndialog(50) pruefe(f.cfg.get("rechnung_zaehler") == 50, f"nach VORN darf er (Luecken sind erlaubt) ({f.cfg.get('rechnung_zaehler')})") pruefe(not warnungen, f"und dabei wird nicht gewarnt ({warnungen})") # ---- 4. Das Nummernbuch kennt Nummern OHNE PDF -------------------------- f.cfg["gemeinsam_ordner"] = gemeinsam_ordner f._buch_oeffnen() pruefe(f.buch is not None, "das gemeinsame Nummernbuch ist da") f.buch.eintragen("2026-090", jahr=2026, nachname="AltbestandOhnePDF", quelle="journal", herkunft="xlsx") pruefe(f.hoechster_stand(JAHR) == 90, f"der hoechste Stand kommt jetzt aus dem Buch: 90 ({f.hoechster_stand(JAHR)})") f.cfg["rechnung_zaehler"] = 5 config.save_config(f.cfg) f._neue_nummer_vorschlagen() pruefe(f.r_nummer.get() == "2026-091", f"vorgeschlagen wird 2026-091 - hinter dem Altbestand ({f.r_nummer.get()})") warnungen.clear() nummerndialog(10) pruefe(f.cfg.get("rechnung_zaehler") == 90, f"und die Einstellungen kommen auch nicht hinter das Buch zurueck " f"({f.cfg.get('rechnung_zaehler')})") # ---- 5. Ohne PDFs im Ordner: das Buch allein haelt die Sperre ----------- for d in os.listdir(ordner): if d.endswith(".pdf"): os.remove(os.path.join(ordner, d)) pruefe(f.hoechster_stand(JAHR) == 90, f"auch ohne PDFs bleibt der Stand bei 90 ({f.hoechster_stand(JAHR)})") # ---- 6. Und ohne Buch: die PDFs allein halten sie ---------------------- rechnung("Nachher") # schreibt 2026-091 f.buch = None f.cfg["gemeinsam_ordner"] = "" pruefe(f.hoechster_stand(JAHR) == 91, f"ohne Buch zaehlen die PDFs: 91 ({f.hoechster_stand(JAHR)})") warnungen.clear() nummerndialog(2) pruefe(f.cfg.get("rechnung_zaehler") == 91, f"und auch dann geht es nicht zurueck ({f.cfg.get('rechnung_zaehler')})") f.destroy() if not fehler: shutil.rmtree(tmp, ignore_errors=True) else: print(f" (Wegwerf-Ordner bleibt: {tmp})") print("---") print("alles gruen" if not fehler else f"{len(fehler)} Fehler: {fehler}") sys.exit(1 if fehler else 0)