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
149 lines
6.3 KiB
Python
149 lines
6.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Pruefstand: finden sich die beiden Programme von SELBST?
|
|
|
|
Sein Einwand vom 07.09.2026, wortwoertlich:
|
|
|
|
"wie soll das rechnungstool und steuertool wissen wo es ist, das muss im
|
|
... ravokk liegen und dann zusammen, sonst kommt das eine nicht an das
|
|
andere ran - die sind doch nur ueber die gemeinsame sqlite verbunden und
|
|
mit api, die sagt hallo da ist was neues"
|
|
|
|
Er hat recht: die beiden haengen an genau zwei Faeden - dieser SQLite-Datei
|
|
und der kleinen API. Vorher fragte jedes Programm beim ersten Start nach einem
|
|
Ordner, und der Nutzer musste in BEIDEN denselben eintragen. Ein Buchstabe
|
|
daneben, und jedes Programm fuehrt sein eigenes Buch weiter - auffallen wuerde
|
|
das erst, wenn eine Rechnungsnummer zum zweiten Mal vergeben ist
|
|
(§ 14 Abs. 4 Nr. 4 UStG).
|
|
|
|
GEPRUEFT WIRD
|
|
|
|
1. Beide Programme nennen denselben Standardordner. Das ist die Grundlage;
|
|
weichen sie hier ab, hilft alles Weitere nichts.
|
|
2. Der Ordner laesst sich anlegen, und darin entsteht EIN Nummernbuch.
|
|
3. Was das eine eintraegt, findet das andere - ohne dass jemand einen Pfad
|
|
eingegeben hat.
|
|
4. Die Dateien, die von sich sagen, sie laegen in beiden Programmen gleich,
|
|
sind auch wirklich gleich.
|
|
|
|
Diese Datei liegt in BEIDEN Programmen gleich. Wer sie aendert, kopiert sie
|
|
hinueber - und sie prueft sich unter Punkt 4 selbst mit, denn sie traegt
|
|
denselben Satz wie die anderen.
|
|
|
|
Angefasst wird dabei nichts Echtes: der Standardordner wird ueber die
|
|
Umgebungsvariable LOCALAPPDATA auf einen Temp-Ordner umgelenkt.
|
|
"""
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
|
|
|
|
RECHNUNG = r"C:\claude\rechnungstool"
|
|
STEUER = r"C:\claude\beherbergungssteuer"
|
|
|
|
# ---- 1. Beide Programme meinen denselben Ort ----------------------------
|
|
# Geladen wird jede Fassung EINZELN, damit wirklich die Datei des jeweiligen
|
|
# Programms befragt wird und nicht die zufaellig zuerst importierte.
|
|
import importlib.util # noqa: E402
|
|
from pruefhelfer import fehler, pruefe
|
|
|
|
|
|
def modul_aus(ordner, name):
|
|
pfad = os.path.join(ordner, name + ".py")
|
|
kennung = f"{name}_{os.path.basename(ordner)}"
|
|
spec = importlib.util.spec_from_file_location(kennung, pfad)
|
|
m = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(m)
|
|
return m
|
|
|
|
|
|
tmp = tempfile.mkdtemp(prefix="gemeinsam_auto_")
|
|
os.environ["LOCALAPPDATA"] = tmp
|
|
|
|
g_rechnung = modul_aus(RECHNUNG, "gemeinsam")
|
|
g_steuer = modul_aus(STEUER, "gemeinsam")
|
|
|
|
a, b = g_rechnung.standard_ordner(), g_steuer.standard_ordner()
|
|
pruefe(a == b, f"beide Programme nennen denselben Ordner ({a})")
|
|
pruefe(a.startswith(tmp),
|
|
"und er haengt an LOCALAPPDATA, nicht an einem festen Pfad im Quelltext")
|
|
pruefe(os.path.basename(a) == "ravokk",
|
|
f"er heisst 'ravokk', wie er es wollte ({os.path.basename(a)})")
|
|
|
|
# ---- 2. Er wird angelegt, und darin liegt EIN Buch ----------------------
|
|
angelegt = g_rechnung.standard_anlegen()
|
|
pruefe(bool(angelegt) and os.path.isdir(angelegt),
|
|
f"der Ordner wird angelegt ({angelegt})")
|
|
pruefe(g_rechnung.DATEINAME == g_steuer.DATEINAME,
|
|
f"beide erwarten dieselbe Datei ({g_rechnung.DATEINAME})")
|
|
pruefe(g_rechnung.pfad_zur_datei(angelegt) == g_steuer.pfad_zur_datei(angelegt),
|
|
"und damit denselben vollen Pfad")
|
|
|
|
# ---- 3. Was das eine eintraegt, findet das andere -----------------------
|
|
buch_r = g_rechnung.Nummernbuch(angelegt)
|
|
buch_s = g_steuer.Nummernbuch(angelegt)
|
|
|
|
buch_r.eintragen("2026-777", jahr=2026, nachname="Probe", quelle="rechnungstool")
|
|
gefunden = [n for n in buch_s.nummern(2026) if n == "2026-777"]
|
|
pruefe(bool(gefunden),
|
|
"was das Rechnungstool eintraegt, sieht das Steuerjournal sofort")
|
|
|
|
buch_s.eintragen("2026-778", jahr=2026, nachname="Gegenprobe", quelle="journal")
|
|
zurueck = [n for n in buch_r.nummern(2026) if n == "2026-778"]
|
|
pruefe(bool(zurueck), "und umgekehrt genauso")
|
|
|
|
# GENAU eins - nicht "mindestens eins". Vorher stand hier `>= 1` mit einem
|
|
# any(); im Text darueber stand "genau ein Buch". Eine Pruefung, die nicht rot
|
|
# werden kann, prueft nichts: laege dort ein zweites Buch, weil eine der
|
|
# Fassungen einen anderen Dateinamen bildet, waere sie gruen geblieben - und
|
|
# das ist genau der Fall, den dieser Pruefstand abfangen soll.
|
|
#
|
|
# Die Begleitdateien von SQLite (-wal, -shm) zaehlen nicht mit; sie gehoeren
|
|
# zum selben Buch.
|
|
buecher = [n for n in os.listdir(angelegt) if n.endswith(".sqlite3")]
|
|
pruefe(len(buecher) == 1,
|
|
f"es liegt genau EIN Buch dort ({os.listdir(angelegt)})")
|
|
pruefe(buecher == [g_rechnung.DATEINAME],
|
|
f"und es traegt den Namen, den beide erwarten ({buecher})")
|
|
|
|
# ---- 4. Was "in beiden gleich" heisst, ist auch gleich ------------------
|
|
# Neun Dateien tragen im Kopf den Satz, sie laegen in beiden Programmen
|
|
# gleich. Das ist eine Zusage an den Naechsten, der eine davon anfasst - und
|
|
# geprueft hat sie bis zum 07.09.2026 niemand. Bei hinweis.py stimmte sie da
|
|
# schon nicht mehr: gleicher Inhalt, andere Zeilenenden.
|
|
#
|
|
# Die Liste steht bewusst NICHT hier, sondern wird aus dem Satz selbst
|
|
# gelesen. Eine Liste, die jemand von Hand nachfuehren muss, veraltet still
|
|
# (und still veraltete Pruefstaende fuehren in die Irre).
|
|
MARKE = "Diese Datei liegt in BEIDEN Programmen gleich"
|
|
|
|
gemeinsame = sorted(n for n in os.listdir(RECHNUNG) if n.endswith(".py")
|
|
and MARKE in open(os.path.join(RECHNUNG, n),
|
|
encoding="utf-8").read())
|
|
pruefe(len(gemeinsame) >= 5,
|
|
f"{len(gemeinsame)} Dateien sagen von sich, sie seien in beiden gleich")
|
|
|
|
for name in gemeinsame:
|
|
hier = os.path.join(RECHNUNG, name)
|
|
dort = os.path.join(STEUER, name)
|
|
if not os.path.isfile(dort):
|
|
pruefe(False, f"{name} fehlt im Steuerrechnungstool")
|
|
continue
|
|
roh_h, roh_d = open(hier, "rb").read(), open(dort, "rb").read()
|
|
if roh_h == roh_d:
|
|
pruefe(True, f"{name} ist byteweise gleich")
|
|
continue
|
|
# Nicht gleich - dann wenigstens sagen, WORAN es liegt. Das ist der
|
|
# Unterschied zwischen einer Meldung und einer Suche.
|
|
if roh_h.replace(b"\r\n", b"\n") == roh_d.replace(b"\r\n", b"\n"):
|
|
grund = "nur die Zeilenenden weichen ab"
|
|
else:
|
|
grund = "der Inhalt weicht ab"
|
|
pruefe(False, f"{name}: {grund}")
|
|
|
|
shutil.rmtree(tmp, ignore_errors=True)
|
|
|
|
if fehler:
|
|
print(f"ROT | {len(fehler)} Punkt(e) nicht erfuellt")
|
|
sys.exit(1)
|
|
print("GRUEN | Beide Programme finden dasselbe Nummernbuch ohne Nachfrage")
|