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
128 lines
4.4 KiB
Python
128 lines
4.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Pruefstand: passt die Leistungstabelle in jedes erlaubte Fenster?
|
|
|
|
DER FALL, DER DAZU GEFUEHRT HAT (06.09.2026)
|
|
--------------------------------------------
|
|
Sein Befund: *"die namen und datums bloecke koennen schmaler werden sind zu
|
|
breit und verdrengen die posten block"*. Gemessen stimmte das genau: die
|
|
sieben Spalten der Leistungstabelle brauchten 743 Pixel, sichtbar waren 618 -
|
|
"E.-Preis" und "Gesamt" standen ausserhalb des Bildes.
|
|
|
|
WARUM DAS KEIN ANDERER PRUEFSTAND FINDET
|
|
----------------------------------------
|
|
Es stuerzt nichts ab, es rechnet nichts falsch, kein Wert fehlt. Die Rechnung
|
|
wird korrekt erstellt - man sieht die Preise nur nicht. Ein Fehler, den man
|
|
ansehen muss, um ihn zu bemerken. Genau deshalb steht er hier als Messung.
|
|
|
|
Und die Ursache lag nicht dort, wo man sie vermutet: an den Feldbreiten links
|
|
zu drehen half NICHTS. Die Arbeitsflaeche verteilte die Breite ueber eine
|
|
uniform-Gruppe streng im Verhaeltnis 4:3 - die Spalte war so breit, weil das
|
|
Gewicht es vorschrieb, nicht weil der Inhalt es brauchte.
|
|
|
|
WAS HIER GEPRUEFT WIRD
|
|
----------------------
|
|
Ueber die ganze erlaubte Spanne von der Mindestbreite bis zu einem grossen
|
|
Fenster:
|
|
|
|
1. Alle Spalten der Tabelle passen nebeneinander in die sichtbare Flaeche.
|
|
2. Die Formularseite bleibt lesbar - sie darf nicht unter ihre gemessene
|
|
Mindestbreite fallen.
|
|
3. Die Formularseite waechst NICHT mit. Zusaetzliche Breite gehoert der
|
|
Tabelle; ein 600 Pixel breites Feld fuer eine Postleitzahl macht nichts
|
|
besser.
|
|
4. Der Textumbruch der Leistungsspalte folgt ihrer echten Breite. Ist er
|
|
groesser, wird der Text abgeschnitten; ist er viel kleiner, bricht er
|
|
unnoetig um.
|
|
"""
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
QUELLE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, QUELLE)
|
|
|
|
|
|
os.chdir(QUELLE)
|
|
os.environ["CAMPINGHOF_TEST"] = "1"
|
|
|
|
from tkinter import messagebox # noqa: E402
|
|
|
|
antworten = {"Erprobungsfassung": True, "Fertig": False, "Hausnummer": 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}")
|
|
|
|
|
|
messagebox.askyesno = askyesno
|
|
for _n in ("showinfo", "showwarning", "showerror"):
|
|
setattr(messagebox, _n, lambda *a, **k: None)
|
|
|
|
import app as A # noqa: E402
|
|
from pruefhelfer import fehler, pruefe
|
|
|
|
f = A.RechnungsApp()
|
|
|
|
|
|
def einpendeln(sekunden=1.2):
|
|
"""Ereignisschleife drehen - Tk verteilt die Breite erst danach."""
|
|
ende = time.time() + sekunden
|
|
while time.time() < ende:
|
|
f.update()
|
|
time.sleep(0.02)
|
|
f.update_idletasks()
|
|
|
|
|
|
einpendeln(2.0)
|
|
|
|
# Die Mindestbreite ist die untere Grenze dessen, was der Benutzer einstellen
|
|
# kann - genau dort muss es noch passen.
|
|
MINDEST = f.minsize()[0]
|
|
print(f" (Mindestbreite laut minsize: {MINDEST})")
|
|
|
|
formular_breiten = []
|
|
for breite in (MINDEST, MINDEST + 40, 1180, 1400, 1600):
|
|
f.geometry(f"{breite}x680")
|
|
einpendeln()
|
|
pf = f.pos_frame
|
|
sichtbar = pf.master.winfo_width() # der Canvas darunter
|
|
spalten = [(pf.grid_bbox(column=i, row=0) or (0, 0, 0, 0))[2]
|
|
for i in range(len(A.RechnungsApp.COLS))]
|
|
gesamt = sum(spalten)
|
|
formular = f.flaeche.kaesten["kunde"]["aussen"].winfo_width()
|
|
formular_breiten.append(formular)
|
|
|
|
pruefe(gesamt <= sichtbar,
|
|
f"bei {breite:4d} px passen alle {len(spalten)} Spalten "
|
|
f"({gesamt} von {sichtbar} px)")
|
|
pruefe(formular >= 300,
|
|
f"bei {breite:4d} px bleibt die Formularseite lesbar ({formular} px)")
|
|
|
|
# Der Umbruch der Textspalte muss zur Spalte passen.
|
|
zellen = getattr(f, "_text_zellen", [])
|
|
if zellen:
|
|
umbruch = int(zellen[0].cget("wraplength"))
|
|
text_spalte = spalten[1]
|
|
pruefe(umbruch <= text_spalte,
|
|
f"bei {breite:4d} px bricht der Leistungstext INNERHALB der "
|
|
f"Spalte um ({umbruch} <= {text_spalte})")
|
|
pruefe(umbruch >= text_spalte - 30,
|
|
f"bei {breite:4d} px nutzt er die Spalte auch aus "
|
|
f"({umbruch} >= {text_spalte - 30})")
|
|
|
|
# Mehr Platz gehoert der Tabelle, nicht den Adressfeldern.
|
|
pruefe(len(set(formular_breiten)) == 1,
|
|
f"die Formularseite waechst nicht mit dem Fenster {formular_breiten}")
|
|
|
|
try:
|
|
f.destroy()
|
|
except Exception: # noqa: BLE001
|
|
pass
|
|
|
|
if fehler:
|
|
print(f"ROT | {len(fehler)} Punkt(e) nicht erfuellt")
|
|
sys.exit(1)
|
|
print("GRUEN | Die Leistungstabelle passt in jedes erlaubte Fenster")
|