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
137 lines
5.2 KiB
Python
137 lines
5.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Pruefstand: die verschiebbaren Kaesten (Arbeitsflaeche).
|
|
|
|
Seine Ansage: *"wie bei opnsense hast so einen kleinen knopf, dann drueckst
|
|
drauf, ab da kannst du schieben, dann hast du einen save button - und dann ist
|
|
die fest"*.
|
|
|
|
Geprueft wird genau diese Reihenfolge:
|
|
|
|
1. Solange nicht "Anordnen" gedrueckt ist, laesst sich NICHTS verschieben -
|
|
beim taeglichen Arbeiten darf nichts verrutschen.
|
|
2. Im Anordnen-Modus haengt ein Etikett am Zeiger (man soll sehen, dass man
|
|
etwas in der Hand hat) und zwei Kaesten tauschen ihre Plaetze.
|
|
3. "Verwerfen" holt die Anordnung von vorher zurueck.
|
|
4. "Speichern" schreibt sie in die config - erst dann steht sie fest.
|
|
5. Der freie Platz gehoert immer dem Kasten mit der langen Liste, egal wo er
|
|
gerade sitzt. Sonst wird der Kundenkasten unten abgeschnitten.
|
|
"""
|
|
import json
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import threading
|
|
|
|
QUELLE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, QUELLE)
|
|
|
|
os.environ["CAMPINGHOF_TEST"] = "1"
|
|
tmp = tempfile.mkdtemp(prefix="flaeche_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.BASE_DIR = tmp
|
|
config.CONFIG_PATH = os.path.join(tmp, "config.json")
|
|
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
|
roh["output_dir"] = ordner
|
|
roh["nummer_eingerichtet"] = True
|
|
roh.pop("fenster_layout", None) # ohne gespeicherte Anordnung starten
|
|
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False)
|
|
|
|
import app as A # noqa: E402
|
|
import flaeche as FL # noqa: E402
|
|
from tkinter import messagebox # noqa: E402
|
|
from pruefhelfer import fehler, pruefe
|
|
|
|
|
|
def _reissleine():
|
|
print("ABBRUCH | Pruefstand haengt (Zeitgrenze)")
|
|
os._exit(2)
|
|
|
|
|
|
_wecker = threading.Timer(120, _reissleine)
|
|
_wecker.daemon = True
|
|
_wecker.start()
|
|
|
|
|
|
messagebox.showinfo = messagebox.showwarning = messagebox.showerror = lambda *a, **k: None
|
|
# Der Erprobungshinweis wird von der pauschalen Antwort unten mit erfasst.
|
|
# Geprueft wird er eigens in pruef_hinweis.py.
|
|
messagebox.askyesno = lambda *a, **k: True
|
|
|
|
f = A.RechnungsApp()
|
|
f.update()
|
|
fl = f.flaeche
|
|
|
|
# ---- 1. Die Vorgabe ist seine Anordnung ----------------------------------
|
|
pruefe(fl.layout == FL.STANDARD,
|
|
f"Vorgabe: Rechnungsdaten oben links, Kunde darunter, Leistungen rechts ({fl.layout})")
|
|
|
|
# ---- 2. Ohne den Knopf laesst sich nichts schieben -----------------------
|
|
pruefe(not fl.anordnen_an, "beim Start ist das Verschieben AUS")
|
|
fl._greifen("kunde")
|
|
pruefe(fl._zieht is None, "ohne 'Anordnen' greift ein Zug ins Leere")
|
|
vorgabe = dict(fl.layout)
|
|
|
|
# ---- 3. Anordnen an: Etikett und Tausch ----------------------------------
|
|
f._anordnen_umschalten()
|
|
pruefe(fl.anordnen_an, "nach dem Knopf ist das Verschieben AN")
|
|
fl._greifen("kunde")
|
|
pruefe(fl._zieht == "kunde", "jetzt haelt man den Kasten")
|
|
pruefe(getattr(fl, "_etikett", None) is not None,
|
|
"und am Zeiger haengt ein Etikett - man SIEHT, dass man zieht")
|
|
fl._ziel = "daten"
|
|
fl._loslassen("kunde")
|
|
pruefe(getattr(fl, "_etikett", None) is None, "nach dem Loslassen ist das Etikett weg")
|
|
pruefe(fl.layout["kunde"] == vorgabe["daten"] and fl.layout["daten"] == vorgabe["kunde"],
|
|
f"die beiden haben die Plaetze getauscht ({fl.layout})")
|
|
|
|
# Gespeichert ist noch NICHTS - erst der Knopf schreibt.
|
|
in_datei = json.load(open(config.CONFIG_PATH, encoding="utf-8")).get("fenster_layout")
|
|
pruefe(in_datei is None, f"vor dem Speichern steht nichts in der config ({in_datei})")
|
|
|
|
# ---- 4. Verwerfen holt die alte Anordnung zurueck ------------------------
|
|
f._anordnen_verwerfen()
|
|
pruefe(fl.layout == vorgabe, f"Verwerfen stellt die Anordnung wieder her ({fl.layout})")
|
|
pruefe(not fl.anordnen_an, "und schaltet das Verschieben wieder aus")
|
|
|
|
# ---- 5. Speichern macht es fest ------------------------------------------
|
|
f._anordnen_umschalten()
|
|
fl._greifen("daten")
|
|
fl._ziel = "leistungen"
|
|
fl._loslassen("daten")
|
|
neu = dict(fl.layout)
|
|
f._anordnen_speichern()
|
|
in_datei = json.load(open(config.CONFIG_PATH, encoding="utf-8")).get("fenster_layout")
|
|
pruefe(in_datei == {n: list(v) for n, v in neu.items()},
|
|
f"Speichern schreibt die Anordnung in die config ({in_datei})")
|
|
pruefe(not fl.anordnen_an, "und schaltet das Verschieben aus")
|
|
|
|
# ---- 6. Der Platz folgt der langen Liste ---------------------------------
|
|
spalte, zeile, hoch = fl.layout["leistungen"]
|
|
gewicht_liste = fl.grid_rowconfigure(zeile)["weight"]
|
|
pruefe(gewicht_liste > 0,
|
|
f"die Zeile mit den Leistungen darf wachsen (Gewicht {gewicht_liste})")
|
|
breite_liste = fl.grid_columnconfigure(spalte)["weight"]
|
|
breite_andere = fl.grid_columnconfigure(1 - spalte)["weight"]
|
|
pruefe(breite_liste > breite_andere,
|
|
f"und ihre Spalte ist breiter ({breite_liste} zu {breite_andere})")
|
|
|
|
# Eine kaputte Anordnung in der config darf das Fenster nicht zerlegen.
|
|
f.cfg["fenster_layout"] = {"kunde": "kaputt", "daten": [0], "leistungen": None}
|
|
fl.anordnen()
|
|
pruefe(fl.layout == FL.STANDARD,
|
|
f"eine unbrauchbare Anordnung faellt auf die Vorgabe zurueck ({fl.layout})")
|
|
|
|
f.destroy()
|
|
print()
|
|
if fehler:
|
|
print(f"FEHLGESCHLAGEN: {len(fehler)}")
|
|
for x in fehler:
|
|
print(" -", x)
|
|
sys.exit(1)
|
|
shutil.rmtree(tmp, ignore_errors=True)
|
|
print("ALLES GRUEN")
|