Oberflaeche im Pcore-Stil: Tuerkis, Kacheln, Glas-Knoepfe, Roboto

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
This commit is contained in:
TheMockTv 2026-09-05 18:52:42 +02:00
parent 0ad03fd1a4
commit 5ebb02b267
17 changed files with 330 additions and 87 deletions

110
app.py
View file

@ -39,6 +39,7 @@ from bestand import (hoechster_zaehler_im_ordner, naechste_freie_nummer,
vorhandene_rechnungen)
from einzelinstanz import (eigene_pid_merken, einzelinstanz_pruefen, fenster_nach_vorn,
laufende_pid_lesen)
import theme
from theme import ThemeMixin
from korrektur import KorrekturMixin
from einstellungen import EinstellungenMixin
@ -202,12 +203,13 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
# ---------------------------------------------------------------- Kopf
def _build_kopf(self):
top = ttk.Frame(self, padding=(12, 10, 12, 4))
top = ttk.Frame(self, padding=(16, 14, 16, 6))
top.pack(fill="x")
# Kunde (links)
kunde = ttk.LabelFrame(top, text="Rechnung an (Kunde)", padding=8)
kunde.grid(row=0, column=0, sticky="nsew", padx=(0, 10))
kunde = ttk.LabelFrame(top, text="RECHNUNG AN (KUNDE)", padding=14,
style="Karte.TLabelframe")
kunde.grid(row=0, column=0, sticky="nsew", padx=(0, 14))
self.k_anrede = tk.StringVar()
self.k_vorname = tk.StringVar()
self.k_name = tk.StringVar() # Nachname oder Firma – die Zuordnung im Steuerjournal
@ -223,7 +225,8 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
("PLZ / Ort *", self.k_plzort),
]
for i, (lbl, var) in enumerate(felder):
ttk.Label(kunde, text=lbl).grid(row=i, column=0, sticky="w", pady=2)
ttk.Label(kunde, text=lbl, style="Karte.TLabel").grid(
row=i, column=0, sticky="w", pady=4)
if var is self.k_anrede:
# Auswahlliste, aber nicht gesperrt - Sonderfaelle kann man tippen.
w = ttk.Combobox(kunde, textvariable=var, width=36, values=ANREDEN)
@ -232,12 +235,13 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
w.grid(row=i, column=1, sticky="ew", padx=(8, 0), pady=2)
ttk.Label(kunde, text="* Pflicht – Name und Anschrift des Kunden gehören "
"auf die Rechnung (§ 14 Abs. 4 Nr. 1 UStG).",
foreground=self.col_muted, wraplength=330, justify="left").grid(
style="KarteMuted.TLabel", wraplength=330, justify="left").grid(
row=len(felder), column=0, columnspan=2, sticky="w", pady=(6, 0))
kunde.columnconfigure(1, weight=1)
# Rechnungsinfos (rechts)
info = ttk.LabelFrame(top, text="Rechnungsdaten", padding=8)
info = ttk.LabelFrame(top, text="RECHNUNGSDATEN", padding=14,
style="Karte.TLabelframe")
info.grid(row=0, column=1, sticky="nsew")
self.r_nummer = tk.StringVar()
self.r_datum = tk.StringVar(value=datetime.date.today().strftime("%d.%m.%Y"))
@ -253,8 +257,8 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
date_vars = (self.r_datum, self.r_anreise, self.r_abreise)
self.datum_labels = {}
for i, (lbl, var) in enumerate(rows):
beschriftung = ttk.Label(info, text=lbl)
beschriftung.grid(row=i, column=0, sticky="w", pady=2)
beschriftung = ttk.Label(info, text=lbl, style="Karte.TLabel")
beschriftung.grid(row=i, column=0, sticky="w", pady=4)
if var is self.r_anreise:
self.datum_labels["von"] = beschriftung
elif var is self.r_abreise:
@ -267,14 +271,16 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
self.nummer_entry = ent
if var in date_vars: # Datum: Punkte automatisch, Cursor ans Ende
ent.bind("<KeyRelease>", self._auto_datum_evt)
self.naechte_label = ttk.Label(info, text="Nächte")
self.naechte_label.grid(row=4, column=0, sticky="w", pady=2)
ttk.Label(info, textvariable=self.r_naechte, font=("Segoe UI", 10, "bold")).grid(
self.naechte_label = ttk.Label(info, text="Nächte", style="Karte.TLabel")
self.naechte_label.grid(row=4, column=0, sticky="w", pady=4)
ttk.Label(info, textvariable=self.r_naechte, style="Karte.TLabel",
font=(theme.SCHRIFT_NAME, 11, "bold")).grid(
row=4, column=1, sticky="w", padx=(8, 0))
# Storno-Hinweis: steht nur da, solange ein Storno im Zwischenspeicher liegt.
self.storno_hinweis = tk.StringVar(value="")
ttk.Label(info, textvariable=self.storno_hinweis, foreground="#b00020",
font=("Segoe UI", 9, "bold"), wraplength=200, justify="left").grid(
ttk.Label(info, textvariable=self.storno_hinweis, style="Karte.TLabel",
foreground=self.col_gefahr,
font=(theme.SCHRIFT_NAME, 9, "bold"), wraplength=200, justify="left").grid(
row=5, column=0, columnspan=2, sticky="w", pady=(6, 0))
info.columnconfigure(1, weight=1)
self.r_anreise.trace_add("write", lambda *a: self._update_naechte())
@ -369,10 +375,11 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
self.blaetter.add(montage_tab, text=" Montage ")
self._build_montage(montage_tab)
wrap = ttk.LabelFrame(camping, text="Leistungen – Anzahl (und ggf. Personen) eintragen", padding=(6, 4))
wrap = ttk.LabelFrame(camping, text="LEISTUNGEN – ANZAHL (UND GGF. PERSONEN) EINTRAGEN",
padding=(12, 8), style="Karte.TLabelframe")
wrap.pack(side="top", fill="both", expand=True)
canvas = tk.Canvas(wrap, highlightthickness=0, bg=self.col_bg)
canvas = tk.Canvas(wrap, highlightthickness=0, bg=self.col_card)
sb = ttk.Scrollbar(wrap, orient="vertical", command=canvas.yview)
self.pos_frame = ttk.Frame(canvas)
self._pos_win = canvas.create_window((0, 0), window=self.pos_frame, anchor="nw")
@ -399,10 +406,11 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
# Kopfzeile (gleiches Grid wie die Daten)
for i, (title, anchor, _) in enumerate(self.COLS):
ttk.Label(self.pos_frame, text=title, anchor=anchor,
font=("Segoe UI", 9, "bold")).grid(row=0, column=i, padx=3, pady=(0, 3), sticky="ew")
ttk.Label(self.pos_frame, text=title.upper(), anchor=anchor,
style="Abschnitt.TLabel").grid(
row=0, column=i, padx=3, pady=(0, 5), sticky="ew")
ttk.Separator(self.pos_frame, orient="horizontal").grid(
row=1, column=0, columnspan=len(self.COLS), sticky="ew", pady=(0, 2))
row=1, column=0, columnspan=len(self.COLS), sticky="ew", pady=(0, 4))
for idx, pos in enumerate(self.cfg["katalog"]):
r = idx + 2
@ -414,17 +422,26 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
av.trace_add("write", lambda *a: self.recalc())
pv.trace_add("write", lambda *a: self.recalc())
ttk.Label(self.pos_frame, text=str(nr), anchor="center").grid(row=r, column=0, padx=3, pady=2, sticky="ew")
# Jede zweite Zeile bekommt einen anderen Grund: bei 17 Positionen
# verrutscht man beim Eintragen sonst leicht eine Zeile.
zeilenstil = "Zebra.TLabel" if idx % 2 else "Karte.TLabel"
ttk.Label(self.pos_frame, text=str(nr), anchor="center", style=zeilenstil).grid(
row=r, column=0, padx=0, pady=3, sticky="ew", ipady=2)
ttk.Label(self.pos_frame, text=pos["leistung"], anchor="w", wraplength=320,
justify="left").grid(row=r, column=1, padx=3, pady=2, sticky="ew")
ttk.Label(self.pos_frame, text=str(pos["satz"]), anchor="center").grid(row=r, column=2, padx=3, pady=2, sticky="ew")
ttk.Entry(self.pos_frame, textvariable=av, width=8, justify="center").grid(row=r, column=3, padx=3, pady=2)
justify="left", style=zeilenstil).grid(
row=r, column=1, padx=0, pady=3, sticky="ew", ipady=2)
ttk.Label(self.pos_frame, text=str(pos["satz"]), anchor="center",
style=zeilenstil).grid(row=r, column=2, padx=0, pady=3, sticky="ew", ipady=2)
ttk.Entry(self.pos_frame, textvariable=av, width=8, justify="center").grid(
row=r, column=3, padx=4, pady=3)
# Personen-Feld NUR bei "pro Person"-Positionen anzeigen
if pos["pro_person"]:
ttk.Entry(self.pos_frame, textvariable=pv, width=6, justify="center").grid(row=r, column=4, padx=3, pady=2)
ttk.Label(self.pos_frame, text=berechnung.eur_kurz(pos["preis"]), anchor="e").grid(row=r, column=5, padx=3, pady=2, sticky="ew")
g = ttk.Label(self.pos_frame, text="0,00", anchor="e")
g.grid(row=r, column=6, padx=3, pady=2, sticky="ew")
ttk.Entry(self.pos_frame, textvariable=pv, width=6, justify="center").grid(
row=r, column=4, padx=4, pady=3)
ttk.Label(self.pos_frame, text=berechnung.eur_kurz(pos["preis"]), anchor="e",
style=zeilenstil).grid(row=r, column=5, padx=0, pady=3, sticky="ew", ipady=2)
g = ttk.Label(self.pos_frame, text="0,00", anchor="e", style=zeilenstil)
g.grid(row=r, column=6, padx=0, pady=3, sticky="ew", ipady=2)
self.gesamt_lbls[nr] = g
# ------------------------------------------------------------ Montage
@ -440,7 +457,7 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
self.m_monate.trace_add("write", lambda *a: self.recalc())
ttk.Label(eltern, text="Monatsmiete für einen Montagearbeiter",
font=("Segoe UI", 11, "bold")).grid(row=0, column=0, columnspan=4, sticky="w")
font=(theme.SCHRIFT_NAME, 11, "bold")).grid(row=0, column=0, columnspan=4, sticky="w")
ttk.Label(eltern, wraplength=760, justify="left", foreground=self.col_muted,
text="Den Zeitraum trägst du oben rechts ein (dieselben Felder wie Anreise/Abreise). "
"Bezeichnung, Steuersatz und Monatspreis stehen unter "
@ -451,17 +468,17 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
ttk.Entry(eltern, textvariable=self.m_monate, width=8, justify="center").grid(
row=2, column=1, sticky="w", padx=(8, 24), pady=3)
ttk.Label(eltern, text="Monatspreis (brutto):").grid(row=2, column=2, sticky="w", pady=3)
self.m_preis = ttk.Label(eltern, text="", font=("Segoe UI", 10, "bold"))
self.m_preis = ttk.Label(eltern, text="", font=(theme.SCHRIFT_NAME, 10, "bold"))
self.m_preis.grid(row=2, column=3, sticky="w", padx=(8, 0), pady=3)
ttk.Separator(eltern, orient="horizontal").grid(
row=3, column=0, columnspan=4, sticky="ew", pady=12)
ttk.Label(eltern, text="So steht es auf der Rechnung:",
font=("Segoe UI", 9, "bold")).grid(row=4, column=0, columnspan=4, sticky="w")
font=(theme.SCHRIFT_NAME, 9, "bold")).grid(row=4, column=0, columnspan=4, sticky="w")
self.m_text = ttk.Label(eltern, text="", wraplength=760, justify="left")
self.m_text.grid(row=5, column=0, columnspan=4, sticky="w", pady=(4, 10))
self.m_zahlen = ttk.Label(eltern, text="", font=("Segoe UI", 10))
self.m_zahlen = ttk.Label(eltern, text="", font=(theme.SCHRIFT_NAME, 10))
self.m_zahlen.grid(row=6, column=0, columnspan=4, sticky="w")
eltern.columnconfigure(3, weight=1)
@ -522,14 +539,14 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
status = ttk.Frame(self, padding=(12, 2))
status.pack(side="bottom", fill="x")
ttk.Label(status, text="powered by ravokk", foreground=self.col_muted,
font=("Segoe UI", 8)).pack(side="right")
font=(theme.SCHRIFT_NAME, 8)).pack(side="right")
# Steht der gemeinsame Ordner? Das gehoert sichtbar hin - ohne ihn kennt
# das Programm die Nummern des Steuerjournals nicht.
self.buch_lbl = ttk.Label(status, text="", foreground=self.col_muted,
font=("Segoe UI", 8))
font=(theme.SCHRIFT_NAME, 8))
self.buch_lbl.pack(side="left")
f = ttk.Frame(self, padding=(12, 6, 12, 10))
f = ttk.Frame(self, padding=(16, 12, 16, 14))
f.pack(side="bottom", fill="x")
ttk.Separator(self, orient="horizontal").pack(side="bottom", fill="x")
@ -539,13 +556,17 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
links = ttk.Frame(f)
links.pack(side="left")
ttk.Button(links, text="Vorschau", command=self.vorschau_zeigen, width=12).pack(side="left", padx=4)
ttk.Button(links, text="PDF erstellen", command=self.pdf_erstellen, width=18).pack(side="left", padx=4)
# "PDF erstellen" ist der Knopf, um den es geht - der traegt die
# Akzentfarbe. Storno greift in eine ausgestellte Rechnung ein und ist
# deshalb rot markiert, nicht weil er gefaehrlich waere, sondern damit
# niemand ihn versehentlich statt "PDF erstellen" trifft.
ttk.Button(links, text="PDF erstellen", command=self.pdf_erstellen,
style="Haupt.TButton").pack(side="left", padx=(0, 6))
ttk.Button(links, text="Vorschau", command=self.vorschau_zeigen).pack(side="left", padx=6)
ttk.Button(links, text="Storno", command=self.dlg_storno,
width=12).pack(side="left", padx=4)
ttk.Button(links, text="Berichtigen", command=self.dlg_berichtigen,
width=14).pack(side="left", padx=4)
ttk.Button(links, text="Zurücksetzen", command=self.reset_formular, width=14).pack(side="left", padx=4)
style="Gefahr.TButton").pack(side="left", padx=6)
ttk.Button(links, text="Berichtigen", command=self.dlg_berichtigen).pack(side="left", padx=6)
ttk.Button(links, text="Zurücksetzen", command=self.reset_formular).pack(side="left", padx=6)
# --------------------------------------------------- Gemeinsames Buch
def _buch_oeffnen(self):
@ -638,7 +659,7 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
rahmen = ttk.Frame(d, padding=18)
rahmen.pack(fill="both", expand=True)
ttk.Label(rahmen, text="Wo soll die Rechnungsnummer weiterlaufen?",
font=("Segoe UI", 13, "bold")).grid(row=0, column=0, columnspan=2, sticky="w")
font=(theme.SCHRIFT_NAME, 13, "bold")).grid(row=0, column=0, columnspan=2, sticky="w")
ttk.Label(rahmen, wraplength=620, justify="left", text=(
"Zwei Wege - such dir einen aus." + chr(10) + chr(10) +
"Das wird EINMAL eingerichtet. Danach steht der Zähler fest: eine "
@ -649,7 +670,7 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
if gefunden:
tk.Label(rahmen, wraplength=620, justify="left", fg="#1f5c3d",
font=("Segoe UI", 9, "bold"),
font=(theme.SCHRIFT_NAME, 9, "bold"),
text=f"Im Rechnungsordner und im Nummernbuch steht schon "
f"{jahr}-{gefunden:03d} – niedriger geht es nicht.").grid(
row=2, column=0, columnspan=2, sticky="w", pady=(0, 10))
@ -677,7 +698,7 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
eingabe.pack(side="left", padx=(8, 0))
weg_gewaehlt()
hinweis = tk.Label(rahmen, text="", wraplength=620, justify="left", fg="#a00000",
font=("Segoe UI", 9))
font=(theme.SCHRIFT_NAME, 9))
hinweis.grid(row=5, column=0, columnspan=2, sticky="w", pady=(8, 0))
def uebernehmen():
@ -957,7 +978,8 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
rows.append(("Gesamtbetrag:", berechnung.eur(r.gesamt), True))
for i, (lbl, val, fett) in enumerate(rows):
font = ("Segoe UI", 12, "bold") if fett else ("Segoe UI", 10)
font = ((theme.SCHRIFT_NAME, 15, "bold") if fett
else (theme.SCHRIFT_NAME, 10))
pady = (5, 0) if fett else (1, 0)
ttk.Label(self.summen_box, text=lbl, font=font).grid(row=i, column=0, sticky="e", padx=6, pady=pady)
vkw = {"font": font, "width": 14, "anchor": "e"}
@ -1040,7 +1062,7 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
rahmen = ttk.Frame(d, padding=18)
rahmen.pack(fill="both", expand=True)
ttk.Label(rahmen, text=f"{wer or 'ohne Namen'} – {betrag}",
font=("Segoe UI", 12, "bold")).grid(row=0, column=0, sticky="w")
font=(theme.SCHRIFT_NAME, 12, "bold")).grid(row=0, column=0, sticky="w")
ttk.Label(rahmen, wraplength=560, justify="left", text=(
f"RECHNUNG {nummer} – der Beleg. Danach lässt sie sich nicht mehr "