Seine Ansage: "schau dir unsern fivem server an und unsere webseiten, dann
weisst du wie mein design ist" - also die Palette aus _ui_www und der
Adminpage: Tuerkis #06C6A4 auf gestuften dunklen Flaechen (#0d0e10 Grund,
#15171b Kachel), Raender #25282e, Text #e8e8ea / #8a8e96, Rot #e85a5a.
Das PROGRAMM traegt diese Handschrift, die RECHNUNG die Farbe der jeweiligen
Firma - das Blatt gehoert dem Kunden, nicht uns.
- Roboto wird mitgeliefert und nur fuer den Prozess geladen
(AddFontResourceEx mit FR_PRIVATE): keine Installation, keine Adminrechte,
nach dem Schliessen ist der Rechner wie vorher. Fehlt sie, bleibt es bei
Segoe UI. Die Schriftdateien gehen mit in die exe (spec).
- Abschnitte sind Kacheln mit Ueberschrift statt grauer Rahmen.
- Knoepfe im Glas-Stil wie in seinen Oberflaechen: getoente Flaeche, Rand in
Akzent-Transparenz. Tk kann kein backdrop-filter - die Toenung wird
ausgerechnet (theme.mischen), das Ergebnis ist auf einfarbigem Grund
identisch. "PDF erstellen" fuehrt, "Storno" ist rot.
- Zebrastreifen in der Leistungsliste: bei 17 Zeilen verrutscht man sonst.
- Kein expand am offenen Reiter mehr - dadurch sprang beim Umschalten die
ganze Leiste ("das taben macht den aktiven kleiner").
- Das Fenster fuer den gemeinsamen Ordner stand als einziges noch in Weiss da:
es kam nie durch _style_dialog.
Dazu ein Fund, der nichts mit dem Design zu tun hat: die Pruefstaende erbten
"nummer_eingerichtet" aus der config DIESES Rechners. Sobald hier jemand die
Nummer einmal einrichtet, liefen sie in einen anderen Zweig und wurden rot -
ohne dass sich am Programm etwas geaendert haette. Sie legen den Zustand jetzt
selbst fest. Alle 14 gruen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
122 lines
4.2 KiB
Python
122 lines
4.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Pruefstand: der Vorschau-Knopf.
|
|
|
|
Die Vorschau soll zeigen, wie das Blatt aussehen WIRD - und dabei nichts
|
|
anfassen. Drei Zusicherungen, die alle drei still brechen koennten:
|
|
|
|
1. Sie verbraucht KEINE Rechnungsnummer und traegt nichts ins Nummernbuch.
|
|
Sonst fehlt in der Buchhaltung eine Nummer, fuer die es kein Blatt gibt.
|
|
2. Sie legt nichts im Rechnungsordner ab - dort liegt die Buchhaltung.
|
|
3. Auf jeder Seite steht "VORSCHAU - keine Rechnung". Ein Blatt ohne diesen
|
|
Hinweis koennte ausgedruckt beim Gast landen und als Rechnung gelten.
|
|
"""
|
|
import json
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import threading as _th
|
|
|
|
QUELLE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, QUELLE)
|
|
|
|
tmp = tempfile.mkdtemp(prefix="vorschau_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["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"] = ordner
|
|
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False)
|
|
|
|
os.environ["CAMPINGHOF_TEST"] = "1"
|
|
import app as A # noqa: E402
|
|
from tkinter import messagebox # noqa: E402
|
|
|
|
|
|
def _reissleine():
|
|
print("ABBRUCH | Pruefstand haengt (Zeitgrenze) - vermutlich ein Dialog")
|
|
os._exit(2)
|
|
|
|
|
|
_wecker = _th.Timer(120, _reissleine)
|
|
_wecker.daemon = True
|
|
_wecker.start()
|
|
|
|
fehler = []
|
|
|
|
|
|
def pruefe(bedingung, text):
|
|
zeile = (" OK | " if bedingung else "FEHLER | ") + text
|
|
k = sys.stdout.encoding or "utf-8"
|
|
print(zeile.encode(k, "replace").decode(k))
|
|
if not bedingung:
|
|
fehler.append(text)
|
|
|
|
|
|
messagebox.showinfo = messagebox.showwarning = messagebox.showerror = lambda *a, **k: None
|
|
messagebox.askyesno = lambda *a, **k: True
|
|
|
|
# Im Pruflauf oeffnet das Programm nichts mehr (sonst stehen nach einem Lauf
|
|
# dreissig PDF-Fenster offen). Geprueft wird der Pfad, den die Vorschau
|
|
# zurueckgibt - und dass wirklich NICHTS aufgemacht wurde.
|
|
geoeffnet = []
|
|
A.os.startfile = lambda p, *a, **k: geoeffnet.append(p)
|
|
|
|
f = A.RechnungsApp()
|
|
f.update()
|
|
f.cfg["output_dir"] = ordner
|
|
erste = sorted(f.anzahl_vars)[0]
|
|
|
|
f.k_vorname.set("Anna")
|
|
f.k_name.set("Beispiel")
|
|
f.k_strasse.set("Musterweg 1")
|
|
f.k_plzort.set("12345 Musterstadt")
|
|
f.anzahl_vars[erste].set("3")
|
|
f.update()
|
|
|
|
nummer_vorher = f.r_nummer.get()
|
|
zaehler_vorher = f.cfg.get("rechnung_zaehler")
|
|
|
|
pfad = f.vorschau_zeigen()
|
|
|
|
pruefe(bool(pfad), f"die Vorschau nennt ihr Blatt ({os.path.basename(str(pfad))})")
|
|
pruefe(not geoeffnet, f"im Pruflauf wird KEIN Betrachter aufgemacht ({geoeffnet})")
|
|
pruefe(os.path.exists(pfad), "Vorschau-PDF wurde geschrieben")
|
|
pruefe(os.path.abspath(ordner) not in os.path.abspath(pfad),
|
|
"Vorschau liegt NICHT im Rechnungsordner")
|
|
pruefe(not os.listdir(ordner), f"Rechnungsordner bleibt leer ({os.listdir(ordner)})")
|
|
pruefe(f.r_nummer.get() == nummer_vorher,
|
|
f"Rechnungsnummer unveraendert ({nummer_vorher} -> {f.r_nummer.get()})")
|
|
pruefe(f.cfg.get("rechnung_zaehler") == zaehler_vorher, "Zaehler unveraendert")
|
|
|
|
from pypdf import PdfReader # noqa: E402
|
|
seiten = [s.extract_text() or "" for s in PdfReader(pfad).pages]
|
|
pruefe(all("VORSCHAU" in t for t in seiten),
|
|
f"'VORSCHAU' steht auf jeder Seite ({len(seiten)} Seiten)")
|
|
pruefe(all("keine Rechnung" in t for t in seiten), "'keine Rechnung' steht auf jeder Seite")
|
|
|
|
# Und die echte Rechnung danach traegt die Marke NICHT.
|
|
f.pdf_erstellen()
|
|
echte = [os.path.join(ordner, n) for n in os.listdir(ordner) if n.lower().endswith(".pdf")]
|
|
pruefe(len(echte) == 1, f"danach entsteht genau eine echte Rechnung ({len(echte)})")
|
|
if echte:
|
|
text = "".join((s.extract_text() or "") for s in PdfReader(echte[0]).pages)
|
|
pruefe("VORSCHAU" not in text, "die echte Rechnung traegt KEINE Vorschau-Marke")
|
|
|
|
f.destroy()
|
|
print()
|
|
if fehler:
|
|
print(f"FEHLGESCHLAGEN: {len(fehler)}")
|
|
for x in fehler:
|
|
print(" -", x)
|
|
sys.exit(1)
|
|
print("ALLES GRUEN")
|