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
186 lines
7.1 KiB
Python
186 lines
7.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Pruefstand: das gemeinsame Nummernbuch von Rechnungstool und Steuerjournal.
|
|
|
|
Der Fall, um den es geht (sein Satz vom 03.09.2026):
|
|
|
|
"er hat manuelle eingetragen die das andere programm die rechnungs nummer
|
|
nicht kennt - mit der sqlite beheben wir das"
|
|
|
|
Im Steuerjournal stehen Buchungen aus der alten Excel-Mappe, zu denen es keine
|
|
PDF gibt. Das Rechnungstool sucht die naechste freie Nummer bisher nur in
|
|
seinem eigenen Ordner - und vergibt so eine Nummer zum zweiten Mal
|
|
(§ 14 Abs. 4 Nr. 4 UStG: jede Nummer nur einmal).
|
|
|
|
Geprueft wird mit BEIDEN echten Programmen auf EINEM gemeinsamen Ordner.
|
|
"""
|
|
import json
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import threading
|
|
|
|
TOOL = r"C:\claude\rechnungstool"
|
|
JOURNAL = r"C:\claude\beherbergungssteuer"
|
|
|
|
|
|
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" # kein Erststart-Dialog
|
|
|
|
tmp = tempfile.mkdtemp(prefix="buch_pruef_")
|
|
gemeinsam_ordner = os.path.join(tmp, "install") # hier liegt das Buch
|
|
rechnungen = os.path.join(tmp, "rechnungen")
|
|
journal_daten = os.path.join(tmp, "journal_daten")
|
|
for d in (gemeinsam_ordner, rechnungen, journal_daten):
|
|
os.makedirs(d)
|
|
|
|
# ---- Erst das Steuerjournal (eigener Datenordner) --------------------------
|
|
sys.path.insert(0, JOURNAL)
|
|
import ablage # noqa: E402
|
|
|
|
ablage.daten_ordner = lambda: journal_daten
|
|
import app as JA # noqa: E402 - das Journal
|
|
from modell import Buchung # noqa: E402
|
|
import gemeinsam # noqa: E402
|
|
|
|
del sys.modules["app"]
|
|
sys.path.remove(JOURNAL)
|
|
|
|
from datetime import date # noqa: E402
|
|
from tkinter import messagebox # noqa: E402
|
|
from pruefhelfer import fehler, pruefe
|
|
|
|
messagebox.showinfo = lambda *a, **k: None
|
|
messagebox.showwarning = lambda *a, **k: None
|
|
|
|
journal = JA.App()
|
|
journal.withdraw()
|
|
# Altbestand wie aus der Excel-Mappe: Nummern OHNE PDF.
|
|
for nr, name in (("001", "Alt Eins"), ("002", "Alt Zwei"), ("003", "Alt Drei")):
|
|
journal.journal.speichern(Buchung(date(2026, 5, 1), nr, 2026, name, 2, 50.0,
|
|
quelle="xlsx"))
|
|
journal.jahr.set(2026)
|
|
journal.aktualisieren()
|
|
journal.journal.setze("gemeinsam_ordner", gemeinsam_ordner)
|
|
journal._buch_oeffnen()
|
|
journal._buch_fuellen()
|
|
|
|
buch = gemeinsam.Nummernbuch(gemeinsam_ordner)
|
|
pruefe(os.path.exists(gemeinsam.pfad_zur_datei(gemeinsam_ordner)),
|
|
"das Journal legt das Nummernbuch im gemeinsamen Ordner an")
|
|
nummern = buch.nummern()
|
|
pruefe(nummern == {"2026-001", "2026-002", "2026-003"},
|
|
f"die drei Altbestands-Nummern stehen drin ({sorted(nummern)})")
|
|
eintrag = buch.eintrag("2026-002")
|
|
pruefe(eintrag and eintrag["herkunft"] == "xlsx" and eintrag["quelle"] == "journal",
|
|
f"mit Herkunft und Quelle ({eintrag and (eintrag['quelle'], eintrag['herkunft'])})")
|
|
|
|
# ---- Jetzt das Rechnungstool auf denselben Ordner --------------------------
|
|
sys.path.insert(0, TOOL)
|
|
import config # noqa: E402
|
|
|
|
shutil.copy(os.path.join(TOOL, "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
|
|
# Der Erststart-Zustand gehoert dem Pruefstand, nicht der config des
|
|
# Entwicklerrechners: sonst laeuft er in einen anderen Zweig, sobald hier
|
|
# jemand die Nummer einmal eingerichtet hat.
|
|
roh["nummer_eingerichtet"] = False
|
|
roh["output_dir"] = rechnungen
|
|
roh["gemeinsam_ordner"] = gemeinsam_ordner
|
|
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
|
|
|
import app as A # noqa: E402 - das Rechnungstool
|
|
import bestand as BE # noqa: E402
|
|
|
|
antworten = {"Fertig": False, "endgültig erstellen": True, "Hausnummer": True,
|
|
"Storno wirklich": True}
|
|
|
|
|
|
def askyesno(titel, text, **kw):
|
|
for schlue, wert in antworten.items():
|
|
if schlue in titel:
|
|
return wert
|
|
raise AssertionError(f"unangemeldeter Dialog: {titel!r}")
|
|
|
|
|
|
# Die Freigabe vor dem Beleg (§ 14 UStG) wird bewusst bejaht - geprueft wird hier
|
|
# der Ablauf DANACH. Dass ein "Nein" keinen Beleg erzeugt, prueft pruef_freigabe.py.
|
|
antworten["jetzt erstellen?"] = True
|
|
# Der Erprobungshinweis wird hier bejaht - geprueft wird er in pruef_hinweis.py.
|
|
antworten["Erprobungsfassung"] = True
|
|
messagebox.askyesno = askyesno
|
|
|
|
tool = A.RechnungsApp()
|
|
tool.cfg["output_dir"] = rechnungen
|
|
tool._buch_oeffnen()
|
|
pruefe(tool.buch is not None, "das Rechnungstool findet dasselbe Buch")
|
|
tool._neue_nummer_vorschlagen()
|
|
pruefe(tool.r_nummer.get() == "2026-004",
|
|
f"es schlaegt 2026-004 vor und ueberspringt den Altbestand ({tool.r_nummer.get()})")
|
|
|
|
# ---- Eine Rechnung schreiben: sie muss im Buch landen ---------------------
|
|
tool.k_vorname.set("Anna")
|
|
tool.k_name.set("Neu")
|
|
tool.k_strasse.set("Musterweg 1")
|
|
tool.k_plzort.set("12345 Musterstadt")
|
|
tool.r_anreise.set("01.09.2026")
|
|
tool.r_abreise.set("03.09.2026")
|
|
tool.anzahl_vars[sorted(tool.anzahl_vars)[0]].set("2")
|
|
tool.update()
|
|
tool.pdf_erstellen()
|
|
pruefe(os.path.exists(os.path.join(rechnungen, "Rechnung_2026-004_Neu.pdf")),
|
|
"die Rechnung 2026-004 ist geschrieben")
|
|
e4 = buch.eintrag("2026-004")
|
|
pruefe(e4 is not None, "und steht im gemeinsamen Buch")
|
|
pruefe(e4 and e4["quelle"] == "rechnungstool" and e4["nachname"] == "Neu",
|
|
f"mit Quelle rechnungstool und Namen ({e4 and (e4['quelle'], e4['nachname'])})")
|
|
pruefe(e4 and abs(float(e4["zwischensumme"]) - 26.0) < 0.005,
|
|
f"und dem Betrag ({e4 and e4['zwischensumme']})")
|
|
|
|
# ---- Storno: eigene Nummer ins Buch, beim Verwerfen wieder raus -----------
|
|
offen = [m for m in BE.rechnungen_im_ordner(rechnungen) if m.get("art") != "storno"]
|
|
tool._storno_anlegen(offen[0], rechnungen)
|
|
storno_nr = tool.storno_stand["nummer"] if tool.storno_stand else ""
|
|
pruefe(buch.belegt(storno_nr), f"der Storno {storno_nr} steht im Buch")
|
|
e_storno = buch.eintrag(storno_nr)
|
|
pruefe(e_storno and e_storno["storno_zu"] == "2026-004",
|
|
f"und sagt, welche Rechnung er aufhebt ({e_storno and e_storno['storno_zu']})")
|
|
tool._storno_verwerfen("Pruefstand")
|
|
pruefe(not buch.belegt(storno_nr),
|
|
f"beim Verwerfen ist die Nummer wieder frei ({storno_nr})")
|
|
|
|
# ---- Das Journal sieht die neue Rechnung im Buch --------------------------
|
|
journal._buch_oeffnen()
|
|
pruefe("2026-004" in journal.buch.nummern(),
|
|
"das Journal sieht die Nummer des Rechnungstools im Buch")
|
|
|
|
# ---- Faellt das Buch aus, laeuft trotzdem alles weiter --------------------
|
|
tool.cfg["gemeinsam_ordner"] = os.path.join(tmp, "gibtsnicht")
|
|
tool._buch_oeffnen()
|
|
pruefe(tool.buch is None, "ein nicht erreichbarer Ordner schaltet das Buch ab")
|
|
pruefe(tool._belegte_nummern() == set(), "die belegten Nummern sind dann leer")
|
|
tool._neue_nummer_vorschlagen()
|
|
pruefe(bool(tool.r_nummer.get()), f"und es wird trotzdem eine Nummer vorgeschlagen "
|
|
f"({tool.r_nummer.get()})")
|
|
|
|
tool.destroy()
|
|
journal.journal.schliessen()
|
|
journal.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)
|