Files
provisionsmanager95/provtool/gui/dialogs.py
T
Kerim AtilganandClaude Opus 5.5 851fd6600a Modernes Design (Standard, Win95 als Legacy), Zeitraum abrechnen, Abrechnung erledigt, Linux-Build
- Design: „modern“ (hell, flach, wie Windows 11, normaler Windows-Rahmen) ist Standard;
  „win95“ bleibt unter Datei > Einstellungen wählbar (Einstellung „design“).
- Export: Zeitraum von–bis (nach Aktivierungsdatum oder Abrechnungszeitraum des Anbieters),
  exportierte Positionen werden als abgerechnet gekennzeichnet (Betrag/Händler eingefroren) und
  per Fingerabdruck auch in späteren Dateien/Abrechnungen nicht noch einmal abgerechnet.
- Datei > Abrechnung erledigt / wieder öffnen: erledigte Abrechnungen sind gegen Änderungen gesperrt.
- DB-Schema v4 (Migration inkl. Fingerabdrücke für alte Positionen).
- Linux-Build (python:3.12-bookworm) als zweiter Pipeline-Job mit Tests und Selbsttest;
  Release-Upload für beide Jobs über build/release_hochladen.sh.
- Behoben: normaler Windows-Rahmen stürzte beim Start ab (Hauptrahmen.nativ fehlte).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:47:00 +01:00

1043 lines
50 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Dialogfenster des Provisions-Managers."""
from __future__ import annotations
import tkinter as tk
from tkinter import filedialog, ttk
from provtool import APP_NAME, APP_ORG, __version__
from provtool import regeln
from provtool import zeitraum as zr
from provtool.db import CAPS_ANBIETER, CAPS_WERT, AbrechnungErledigt, Datenbank, Haendler
from provtool.gui import win95 as w
from provtool.importer import ANBIETER
from provtool.money import fmt, fmt_percent, parse_cent, parse_percent
# Auswahl im Händler-Dialog: Schlüssel -> Anzeige
KENNUNG_ARTEN = {"freenet": "freenet (Unterhändler-Nr.)", "vodafone": "Vodafone (VO-Nr.)"}
CAPS_TEXT = "Kunden-Nachname komplett in GROSSBUCHSTABEN"
CAPS_HAKEN = "Vodafone-Kunden mit Nachnamen in GROSSBUCHSTABEN gehören zu diesem Händler"
# =========================================================================== Händlerverwaltung
class HaendlerDialog(w.Dialog):
def __init__(self, master, db: Datenbank, auswahl_id: int | None = None,
neue_kennung: tuple[str, str] | None = None):
super().__init__(master, "Händler verwalten", resizable=True)
self.db = db
self.aktuell: Haendler | None = None
self.geaendert = False
haupt = tk.Frame(self, bg=w.FACE)
haupt.pack(fill="both", expand=True, padx=8, pady=8)
# --- links: Liste
links = tk.Frame(haupt, bg=w.FACE)
links.pack(side="left", fill="y")
tk.Label(links, text="Händler:", anchor="w").pack(fill="x")
rahmen, self.liste = w.scroll_tree(links, columns=("name",), show="tree", selectmode="browse", height=16)
self.liste.column("#0", width=0, stretch=False)
self.liste.column("name", width=190)
rahmen.pack(fill="y", expand=True)
self.liste.bind("<<TreeviewSelect>>", self._auswahl)
lk = tk.Frame(links, bg=w.FACE)
lk.pack(fill="x", pady=(6, 0))
w.knopf(lk, "Neu", self._neu, breite=8).pack(side="left")
w.knopf(lk, "Löschen", self._loeschen, breite=8).pack(side="left", padx=4)
# --- rechts: Formular
rechts = tk.Frame(haupt, bg=w.FACE)
rechts.pack(side="left", fill="both", expand=True, padx=(10, 0))
g = w.gruppe(rechts, "Stammdaten")
g.pack(fill="x")
self.v_name, self.v_ap, self.v_mail = tk.StringVar(), tk.StringVar(), tk.StringVar()
self.v_proz, self.v_aktiv = tk.StringVar(value="100"), tk.BooleanVar(value=True)
self.v_air = tk.StringVar(value="0")
for r, (lbl, var) in enumerate((("Name / Firma:", self.v_name), ("Ansprechpartner:", self.v_ap),
("E-Mail:", self.v_mail))):
tk.Label(g, text=lbl, anchor="w").grid(row=r, column=0, sticky="w", pady=2)
tk.Entry(g, textvariable=var, width=36).grid(row=r, column=1, columnspan=2, sticky="we", pady=2)
tk.Label(g, text="Standard-Auszahlung:", anchor="w").grid(row=3, column=0, sticky="w", pady=2)
pf = tk.Frame(g, bg=w.FACE)
pf.grid(row=3, column=1, sticky="w")
tk.Entry(pf, textvariable=self.v_proz, width=7, justify="right").pack(side="left")
tk.Label(pf, text="% der Provision").pack(side="left", padx=4)
tk.Checkbutton(g, text="Aktiv", variable=self.v_aktiv).grid(row=3, column=2, sticky="e")
tk.Label(g, text="Airtime:", anchor="w").grid(row=4, column=0, sticky="w", pady=2)
af = tk.Frame(g, bg=w.FACE)
af.grid(row=4, column=1, columnspan=2, sticky="w")
self.e_air = tk.Entry(af, textvariable=self.v_air, width=7, justify="right")
self.e_air.pack(side="left")
tk.Label(af, text="% der Airtime (0 % = keine Airtime)").pack(side="left", padx=4)
tk.Label(g, text="Notiz:", anchor="nw").grid(row=5, column=0, sticky="nw", pady=2)
self.t_notiz = tk.Text(g, width=36, height=2, font=w.FONT)
self.t_notiz.grid(row=5, column=1, columnspan=2, sticky="we", pady=2)
g.columnconfigure(1, weight=1)
g2 = w.gruppe(rechts, "Kennungen (ordnet Positionen diesem Händler zu)")
g2.pack(fill="both", expand=True, pady=(8, 0))
r2, self.kliste = w.scroll_tree(g2, columns=("anbieter", "wert"), show="headings", height=3,
selectmode="browse")
self.kliste.heading("anbieter", text="Anbieter")
self.kliste.heading("wert", text="Unterhändler-Nr. / VO-Nr.")
self.kliste.column("anbieter", width=90)
self.kliste.column("wert", width=260)
r2.pack(fill="both", expand=True)
kf = tk.Frame(g2, bg=w.FACE)
kf.pack(fill="x", pady=(4, 0))
self.v_kanb = tk.StringVar(value=KENNUNG_ARTEN["freenet"])
cb = ttk.Combobox(kf, textvariable=self.v_kanb, values=list(KENNUNG_ARTEN.values()), width=34,
state="readonly")
cb.pack(side="left")
cb.bind("<<ComboboxSelected>>", lambda e: self._art_gewaehlt())
self.v_kwert = tk.StringVar()
ke = tk.Entry(kf, textvariable=self.v_kwert, width=14)
self._ke = ke
ke.pack(side="left", padx=4)
ke.bind("<Return>", lambda e: self._kennung_dazu())
w.knopf(kf, "Hinzufügen", self._kennung_dazu).pack(side="left")
w.knopf(kf, "Entfernen", self._kennung_weg).pack(side="left", padx=4)
self.v_caps = tk.BooleanVar(value=False)
self.cb_caps = tk.Checkbutton(g2, text=CAPS_HAKEN, variable=self.v_caps, anchor="w")
self.cb_caps.pack(fill="x", pady=(4, 0))
g4 = w.gruppe(rechts, "Jahresbonus Neuaktivierung (Betrag je Karte nach Staffel)")
g4.pack(fill="x", pady=(8, 0))
self.v_eigen_staffel = tk.BooleanVar(value=False)
sf = tk.Frame(g4, bg=w.FACE)
sf.pack(fill="x")
tk.Radiobutton(sf, text="Standard-Staffel", variable=self.v_eigen_staffel, value=False,
command=self._staffel_zeigen).pack(side="left")
tk.Radiobutton(sf, text="Eigene Staffel für diesen Händler", variable=self.v_eigen_staffel, value=True,
command=self._staffel_zeigen).pack(side="left", padx=(8, 0))
w.knopf(sf, "Bearbeiten...", self._staffel_bearbeiten).pack(side="right")
self.l_staffel = tk.Label(g4, text="", anchor="w", justify="left", fg=w.NAVY, wraplength=560)
self.l_staffel.pack(fill="x", pady=(2, 0))
self._eigene_staffel: regeln.Staffel | None = None
g3 = w.gruppe(rechts, "Abweichende Auszahlung je Provisionsart")
g3.pack(fill="both", expand=True, pady=(8, 0))
r3, self.rliste = w.scroll_tree(g3, columns=("art", "proz"), show="headings", height=3, selectmode="browse")
self.rliste.heading("art", text="Provisionsart")
self.rliste.heading("proz", text="Auszahlung")
self.rliste.column("art", width=240)
self.rliste.column("proz", width=70, anchor="e")
r3.pack(fill="both", expand=True)
rf = tk.Frame(g3, bg=w.FACE)
rf.pack(fill="x", pady=(4, 0))
tk.Label(rf, text="Regeln entstehen im Hauptfenster über „Prozent...“.", fg=w.SHADOW).pack(side="left")
w.knopf(rf, "Entfernen", self._regel_weg).pack(side="right")
unten = tk.Frame(self, bg=w.FACE)
unten.pack(fill="x", padx=8, pady=(0, 8))
w.knopf(unten, "Schließen", self.abbrechen, breite=10).pack(side="right")
w.knopf(unten, "Speichern", self._speichern, breite=10, default="active").pack(side="right", padx=6)
self._kennungen: list[tuple[str, str]] = []
self._liste_laden(auswahl_id)
if neue_kennung:
self._neu()
if neue_kennung[0] == CAPS_ANBIETER:
self.v_caps.set(True)
else:
self._kennungen = [neue_kennung]
self._kennungen_zeigen()
elif self.liste.selection():
self._auswahl() # sofort füllen – sonst wächst der Dialog erst nach der Größenberechnung
self.minsize(660, 560)
# ---- intern
@staticmethod
def _anbieter_key(anzeige: str) -> str:
return next((k for k, v in KENNUNG_ARTEN.items() if v == anzeige), anzeige)
def _art_gewaehlt(self):
self._ke.focus_set()
def _caps_einstellen(self, hid: int, an: bool):
"""GROSSBUCHSTABEN-Häkchen: gesperrt, wenn die Regel schon einem anderen Händler gehört."""
self.v_caps.set(an)
besitzer = self.db.kennung_map().get((CAPS_ANBIETER, CAPS_WERT))
if besitzer and besitzer != hid:
h = self.db.haendler(besitzer)
self.cb_caps.configure(state="disabled", text=f"GROSSBUCHSTABEN-Regel: gehört zu „{h.name if h else '?'}“")
else:
self.cb_caps.configure(state="normal", text=CAPS_HAKEN)
def _staffel_zeigen(self):
eigen = self.v_eigen_staffel.get()
if eigen and self._eigene_staffel is None:
self._eigene_staffel = list(self.db.standard_staffel())
st = self._eigene_staffel if eigen else self.db.standard_staffel()
self.l_staffel.configure(text=("Eigene Staffel: " if eigen else "Standard: ")
+ regeln.staffel_beschreibung(st, kurz=True))
def _staffel_bearbeiten(self):
if not self.v_eigen_staffel.get():
if w.frage(self, "Jahresbonus-Staffel",
"Die Standard-Staffel gilt für alle Händler ohne eigene Staffel.\n\n"
"Soll dieser Händler eine eigene Staffel bekommen?\n"
"(Die Standard-Staffel ändern Sie unter Händler > Standard-Staffel Jahresbonus...)",
ja="Eigene Staffel", nein="Abbrechen"):
self.v_eigen_staffel.set(True)
else:
return
neu = StaffelDialog(self, self._eigene_staffel or self.db.standard_staffel(),
f"Jahresbonus-Staffel – {self.v_name.get().strip() or 'neuer Händler'}",
standard=self.db.standard_staffel()).zeigen()
if neu is not None:
self._eigene_staffel = neu
self._staffel_zeigen()
def _liste_laden(self, auswahl_id=None):
self.liste.delete(*self.liste.get_children())
for h in self.db.haendler_liste():
self.liste.insert("", "end", iid=str(h.id), values=(h.name + ("" if h.aktiv else " (inaktiv)"),))
if auswahl_id and self.liste.exists(str(auswahl_id)):
self.liste.selection_set(str(auswahl_id))
self.liste.see(str(auswahl_id))
elif self.liste.get_children():
self.liste.selection_set(self.liste.get_children()[0])
else:
self._neu()
def _auswahl(self, _e=None):
sel = self.liste.selection()
if not sel:
return
h = self.db.haendler(int(sel[0]))
if not h:
return
self.aktuell = h
self.v_name.set(h.name)
self.v_ap.set(h.ansprechpartner)
self.v_mail.set(h.email)
self.v_proz.set(fmt_percent(h.standard_prozent).replace(" %", ""))
self.v_air.set(fmt_percent(h.airtime_prozent).replace(" %", ""))
self.v_aktiv.set(h.aktiv)
self.t_notiz.delete("1.0", "end")
self.t_notiz.insert("1.0", h.notiz or "")
kenn = self.db.kennungen(h.id)
self._kennungen = [k for k in kenn if k[0] != CAPS_ANBIETER]
self._caps_einstellen(h.id, (CAPS_ANBIETER, CAPS_WERT) in kenn)
self._eigene_staffel = list(h.staffel) if h.staffel is not None else None
self.v_eigen_staffel.set(h.staffel is not None)
self._staffel_zeigen()
self._kennungen_zeigen()
self._regeln_zeigen()
def _kennungen_zeigen(self):
self.kliste.delete(*self.kliste.get_children())
for i, (a, v) in enumerate(self._kennungen):
self.kliste.insert("", "end", iid=str(i), values=(ANBIETER.get(a, a), v))
def _regeln_zeigen(self):
self.rliste.delete(*self.rliste.get_children())
if self.aktuell and self.aktuell.id:
for art, p in sorted(self.db.regeln(self.aktuell.id).items()):
self.rliste.insert("", "end", iid=art, values=(art, fmt_percent(p)))
def _neu(self):
self.liste.selection_remove(self.liste.selection())
self.aktuell = Haendler(0, "")
for v in (self.v_name, self.v_ap, self.v_mail):
v.set("")
self.v_proz.set("100")
self.v_air.set("0")
self.v_aktiv.set(True)
self.t_notiz.delete("1.0", "end")
self._kennungen = []
self._caps_einstellen(0, False)
self._eigene_staffel = None
self.v_eigen_staffel.set(False)
self._staffel_zeigen()
self._kennungen_zeigen()
self._regeln_zeigen()
def _kennung_dazu(self):
anb = self._anbieter_key(self.v_kanb.get())
wert = self.v_kwert.get().strip()
if not wert:
return
if (anb, wert) not in self._kennungen:
self._kennungen.append((anb, wert))
self.v_kwert.set("")
self._kennungen_zeigen()
def _kennung_weg(self):
sel = self.kliste.selection()
if sel:
del self._kennungen[int(sel[0])]
self._kennungen_zeigen()
def _regel_weg(self):
sel = self.rliste.selection()
if sel and self.aktuell and self.aktuell.id:
self.db.regel_setzen(self.aktuell.id, sel[0], None)
self.geaendert = True
self._regeln_zeigen()
def _speichern(self):
name = self.v_name.get().strip()
if not name:
w.warnung(self, "Händler", "Bitte einen Namen eingeben.")
return
try:
proz = parse_percent(self.v_proz.get())
except ValueError:
w.warnung(self, "Händler", "Die Standard-Auszahlung muss eine Zahl sein (z. B. 100 oder 85,5).")
return
try:
air = parse_percent(self.v_air.get() or "0")
except ValueError:
w.warnung(self, "Händler", "Die Airtime-Auszahlung muss eine Zahl sein (z. B. 0, 50 oder 100).")
return
if self.v_kwert.get().strip():
self._kennung_dazu()
h = Haendler(self.aktuell.id if self.aktuell else 0, name, self.v_ap.get().strip(), self.v_mail.get().strip(),
self.t_notiz.get("1.0", "end").strip(), proz, self.v_aktiv.get(), air,
self._eigene_staffel if self.v_eigen_staffel.get() else None)
kennungen = list(self._kennungen)
if self.v_caps.get() and str(self.cb_caps.cget("state")) != "disabled":
kennungen.append((CAPS_ANBIETER, CAPS_WERT))
try:
hid = self.db.haendler_speichern(h, kennungen)
except Exception as exc: # sqlite3.IntegrityError / ValueError
msg = str(exc)
if "UNIQUE" in msg and "haendler.name" in msg:
msg = f"Es gibt bereits einen Händler „{name}“."
w.fehler(self, "Händler speichern", msg)
return
self.geaendert = True
self._liste_laden(hid)
def _loeschen(self):
if not self.aktuell or not self.aktuell.id:
return
if w.frage(self, "Händler löschen",
f"Händler „{self.aktuell.name}“ mit allen Kennungen und Regeln löschen?\n\n"
"Positionen in Abrechnungen bleiben erhalten, gelten dann aber als „nicht zugeordnet“.",
default_ja=False):
self.db.haendler_loeschen(self.aktuell.id)
self.geaendert = True
self.aktuell = None
self._liste_laden()
def abbrechen(self):
self.result = self.geaendert
self.destroy()
# =========================================================================== Prozent anwenden
class ProzentDialog(w.Dialog):
def __init__(self, master, haendler_name: str, provisionsart: str | None, anzahl: int, summe_orig: int,
vorschlag: float, regel_moeglich: bool):
super().__init__(master, "Auszahlung festlegen")
self.summe_orig = summe_orig
f = tk.Frame(self, bg=w.FACE)
f.pack(fill="both", padx=12, pady=12)
ic = w.make_icon(self, "prozent", 2)
self._ic = ic
tk.Label(f, image=ic).grid(row=0, column=0, rowspan=4, sticky="n", padx=(0, 12))
was = f"Provisionsart „{provisionsart}“" if provisionsart else f"{anzahl} ausgewählte Position(en)"
tk.Label(f, text=f"Händler: {haendler_name}\n{was}\nProvision laut Abrechnung: {fmt(summe_orig)}",
justify="left", anchor="w").grid(row=0, column=1, columnspan=3, sticky="w")
tk.Label(f, text="Auszahlung:").grid(row=1, column=1, sticky="w", pady=(10, 2))
self.v = tk.StringVar(value=fmt_percent(vorschlag).replace(" %", ""))
ef = tk.Frame(f, bg=w.FACE)
ef.grid(row=1, column=2, columnspan=2, sticky="w", pady=(10, 2))
e = tk.Entry(ef, textvariable=self.v, width=8, justify="right")
e.pack(side="left")
tk.Label(ef, text="% der Provision").pack(side="left", padx=4)
self.vorschau = tk.Label(f, text="", fg=w.NAVY, anchor="w")
self.vorschau.grid(row=2, column=1, columnspan=3, sticky="w")
self.v_regel = tk.BooleanVar(value=bool(provisionsart and regel_moeglich))
self.v_manuell = tk.BooleanVar(value=False)
cbf = tk.Frame(f, bg=w.FACE)
cbf.grid(row=3, column=1, columnspan=3, sticky="w", pady=(8, 0))
if provisionsart and regel_moeglich:
tk.Checkbutton(cbf, text="Als Standard für diesen Händler und diese Provisionsart merken",
variable=self.v_regel).pack(anchor="w")
tk.Checkbutton(cbf, text="Auch von Hand geänderte Beträge überschreiben",
variable=self.v_manuell).pack(anchor="w")
self.v.trace_add("write", lambda *_: self._vorschau())
self._vorschau()
self.bind("<Return>", lambda _e: self._ok())
self.knopfleiste([("OK", self._ok), ("Abbrechen", self.abbrechen)]).pack(pady=(0, 12))
e.select_range(0, "end")
self._e = e
def _vorschau(self):
from provtool.money import apply_percent
try:
p = parse_percent(self.v.get())
self.vorschau.configure(text=f"-> Auszahlung ca. {fmt(apply_percent(self.summe_orig, p))}")
except ValueError:
self.vorschau.configure(text="(ungültiger Wert)")
def _ok(self):
try:
p = parse_percent(self.v.get())
except ValueError:
w.warnung(self, "Auszahlung", "Bitte einen Prozentwert eingeben, z. B. 100, 80 oder 72,5.")
return
self.result = (p, self.v_regel.get(), self.v_manuell.get())
self.destroy()
def zeigen(self, fokus=None):
return super().zeigen(self._e)
# =========================================================================== Jahresbonus-Staffel
class StaffelDialog(w.Dialog):
"""Staffel bearbeiten: je Stufe „ab N Karten“ und Betrag je Karte (oder „voll“)."""
def __init__(self, master, staffel: regeln.Staffel, titel: str = "Jahresbonus-Staffel",
standard: regeln.Staffel | None = None):
super().__init__(master, titel)
self.standard = standard
f = tk.Frame(self, bg=w.FACE)
f.pack(fill="both", expand=True, padx=12, pady=(10, 4))
tk.Label(f, justify="left", anchor="w", wraplength=380, text=(
"Jahresbonus Neuaktivierung: Der Händler bekommt je Karte den Betrag der Stufe, die zu seiner "
"Anzahl Karten in der Abrechnung passt. „voll“ = ganzer Betrag laut Abrechnung. Nie mehr als "
"die Provision laut Abrechnung; Stornos zählen ab.")).pack(fill="x")
kopf = tk.Frame(f, bg=w.FACE)
kopf.pack(fill="x", pady=(8, 2))
tk.Label(kopf, text="ab Karten", width=9, anchor="w").pack(side="left")
tk.Label(kopf, text="Betrag je Karte (oder „voll“)", anchor="w").pack(side="left", padx=(8, 0))
self.tabelle = tk.Frame(f, bg=w.FACE)
self.tabelle.pack(fill="x")
self.zeilen: list[tuple[tk.Frame, tk.StringVar, tk.StringVar]] = []
for ab, cent in staffel:
self._zeile(ab, cent)
kk = tk.Frame(f, bg=w.FACE)
kk.pack(fill="x", pady=(6, 0))
w.knopf(kk, "Stufe hinzufügen", self._dazu).pack(side="left")
w.knopf(kk, "Letzte entfernen", self._weg).pack(side="left", padx=4)
if standard is not None:
w.knopf(kk, "Wie Standard", lambda: self._setzen(standard)).pack(side="right")
self.knopfleiste([("OK", self._ok), ("Abbrechen", self.abbrechen)]).pack(pady=(6, 10))
def _zeile(self, ab: int | str = "", cent: int | None | str = ""):
z = tk.Frame(self.tabelle, bg=w.FACE)
z.pack(fill="x", pady=1)
v_ab = tk.StringVar(value=str(ab))
v_bt = tk.StringVar(value="voll" if cent is None else (fmt(cent, symbol=False) if isinstance(cent, int)
else str(cent)))
tk.Entry(z, textvariable=v_ab, width=8, justify="right").pack(side="left")
tk.Entry(z, textvariable=v_bt, width=10, justify="right").pack(side="left", padx=(12, 0))
tk.Label(z, text="€ je Karte").pack(side="left", padx=4)
self.zeilen.append((z, v_ab, v_bt))
def _dazu(self):
letzte = self.zeilen[-1][1].get() if self.zeilen else "0"
try:
ab = int(letzte) + 5
except ValueError:
ab = ""
self._zeile(ab, "")
def _weg(self):
if len(self.zeilen) > 1:
self.zeilen.pop()[0].destroy()
def _setzen(self, staffel):
for z, _, _ in self.zeilen:
z.destroy()
self.zeilen = []
for ab, cent in staffel:
self._zeile(ab, cent)
def lesen(self) -> regeln.Staffel:
out = []
for i, (_, v_ab, v_bt) in enumerate(self.zeilen, start=1):
try:
ab = int(v_ab.get().strip())
except ValueError:
raise ValueError(f"Stufe {i}: „ab Karten“ muss eine ganze Zahl sein.") from None
text = v_bt.get().strip()
if text.lower() == "voll":
cent = None
else:
try:
cent = parse_cent(text)
except ValueError:
raise ValueError(f"Stufe {i}: Bitte einen Betrag (z. B. 5 oder 7,50) oder „voll“ eingeben.") \
from None
out.append((ab, cent))
return regeln.staffel_pruefen(out)
def _ok(self):
try:
self.result = self.lesen()
except ValueError as exc:
w.warnung(self, "Staffel", str(exc))
return
self.destroy()
# =========================================================================== Provisionsarten
TYP_AUTO = "Automatisch"
class ProvisionsartenDialog(w.Dialog):
"""Welche Provisionsart ist Airtime bzw. Jahresbonus? Erkannt wird automatisch, umstellbar je Art."""
def __init__(self, master, db: Datenbank):
super().__init__(master, "Provisionsarten", resizable=True)
self.db = db
self.geaendert = False
f = tk.Frame(self, bg=w.FACE)
f.pack(fill="both", expand=True, padx=10, pady=10)
tk.Label(f, anchor="w", justify="left", text=(
"Airtime wird pro Händler mit dem Airtime-Prozentsatz ausgezahlt und im Export als eigene Liste\n"
"geführt. Der Jahresbonus Neuaktivierung wird nach der Staffel berechnet.")).pack(fill="x")
r, self.tree = w.scroll_tree(f, columns=("art", "anzahl", "typ"), show="headings", height=12,
selectmode="browse")
for c, t, b, a in (("art", "Provisionsart", 300, "w"), ("anzahl", "Pos.", 50, "e"),
("typ", "Behandlung", 230, "w")):
self.tree.heading(c, text=t)
self.tree.column(c, width=b, anchor=a)
r.pack(fill="both", expand=True, pady=(6, 4))
self.tree.bind("<<TreeviewSelect>>", lambda e: self._auswahl())
u = tk.Frame(f, bg=w.FACE)
u.pack(fill="x")
tk.Label(u, text="Behandlung:").pack(side="left")
self.v_typ = tk.StringVar(value=TYP_AUTO)
self.cb = ttk.Combobox(u, textvariable=self.v_typ, state="readonly", width=36,
values=[TYP_AUTO] + list(regeln.TYPEN.values()))
self.cb.pack(side="left", padx=4)
w.knopf(u, "Übernehmen", self._setzen).pack(side="left")
self.knopfleiste([("Schließen", self.abbrechen)]).pack(pady=(0, 10))
self._laden()
self.minsize(620, 380)
def _laden(self, auswahl: str | None = None):
self.tree.delete(*self.tree.get_children())
fest = self.db.art_typen()
self._arten = {}
for i, (art, erkannt, n) in enumerate(self.db.provisionsarten()):
typ = fest.get(art)
text = regeln.TYPEN[typ] if typ else f"{TYP_AUTO}: {regeln.TYPEN[erkannt]}"
iid = str(i)
self._arten[iid] = art
self.tree.insert("", "end", iid=iid, values=(art or "(ohne)", n, text))
if art == auswahl:
self.tree.selection_set(iid)
self.tree.see(iid)
def _auswahl(self):
sel = self.tree.selection()
if not sel:
return
typ = self.db.art_typen().get(self._arten[sel[0]])
self.v_typ.set(regeln.TYPEN[typ] if typ else TYP_AUTO)
def _setzen(self):
sel = self.tree.selection()
if not sel:
w.info(self, "Provisionsarten", "Bitte zuerst eine Provisionsart in der Liste auswählen.")
return
art = self._arten[sel[0]]
wahl = self.v_typ.get()
typ = next((k for k, v in regeln.TYPEN.items() if v == wahl), None)
self.db.art_typ_setzen(art, typ)
self.geaendert = True
self._laden(art)
def abbrechen(self):
self.result = self.geaendert
self.destroy()
# =========================================================================== Abrechnungen
class AbrechnungenDialog(w.Dialog):
def __init__(self, master, db: Datenbank, aktuelle_id: int | None):
super().__init__(master, "Abrechnung öffnen", resizable=True)
self.db = db
tk.Label(self, text="Gespeicherte Abrechnungen:", anchor="w").pack(fill="x", padx=10, pady=(10, 2))
r, self.tree = w.scroll_tree(self, columns=("titel", "status", "geaendert", "anzahl", "dateien"),
show="headings", height=12, selectmode="browse")
for c, t, b in (("titel", "Bezeichnung", 180), ("status", "Status", 110), ("geaendert", "Zuletzt geändert", 120),
("anzahl", "Positionen", 70), ("dateien", "Dateien", 260)):
self.tree.heading(c, text=t)
self.tree.column(c, width=b, anchor="e" if c == "anzahl" else "w")
r.pack(fill="both", expand=True, padx=10)
self.tree.bind("<Double-1>", lambda e: self._oeffnen())
k = tk.Frame(self, bg=w.FACE)
k.pack(fill="x", padx=10, pady=10)
w.knopf(k, "Öffnen", self._oeffnen, breite=10, default="active").pack(side="left")
w.knopf(k, "Umbenennen...", self._umbenennen, breite=12).pack(side="left", padx=6)
w.knopf(k, "Löschen", self._loeschen, breite=10).pack(side="left")
w.knopf(k, "Abbrechen", self.abbrechen, breite=10).pack(side="right")
self.bind("<Return>", lambda e: self._oeffnen())
self._laden(aktuelle_id)
def _laden(self, sel=None):
self.tree.delete(*self.tree.get_children())
for a in self.db.abrechnungen():
status = "erledigt" if a["erledigt"] else (f"{a['abgerechnet']} abgerechnet" if a["abgerechnet"] else "offen")
self.tree.insert("", "end", iid=str(a["id"]),
values=(a["titel"], status, a["geaendert"][:16], a["anzahl"], a["dateien"] or ""))
kids = self.tree.get_children()
if sel and self.tree.exists(str(sel)):
self.tree.selection_set(str(sel))
elif kids:
self.tree.selection_set(kids[0])
def _sel(self) -> int | None:
s = self.tree.selection()
return int(s[0]) if s else None
def _oeffnen(self):
if self._sel():
self.result = ("oeffnen", self._sel())
self.destroy()
def _umbenennen(self):
aid = self._sel()
if not aid:
return
t = w.eingabe(self, "Umbenennen", "Neue Bezeichnung:", self.db.abrechnung(aid)["titel"])
if t and t.strip():
self.db.abrechnung_umbenennen(aid, t.strip())
self._laden(aid)
self.result = ("geaendert", aid)
def _loeschen(self):
aid = self._sel()
if not aid:
return
titel = self.db.abrechnung(aid)["titel"]
if w.frage(self, "Löschen", f"Abrechnung „{titel}“ mit allen Positionen und Änderungen endgültig löschen?",
default_ja=False):
self.db.abrechnung_loeschen(aid)
self._laden()
self.result = ("geloescht", aid)
def abbrechen(self):
self.destroy()
class DateienDialog(w.Dialog):
"""Zeigt die importierten Dateien einer Abrechnung; einzelne Dateien können wieder entfernt werden."""
def __init__(self, master, db: Datenbank, aid: int):
super().__init__(master, "Importierte Dateien", resizable=True)
self.db, self.aid, self.geaendert = db, aid, False
r, self.tree = w.scroll_tree(self, columns=("datei", "format", "anzahl", "zeit"), show="headings", height=8,
selectmode="browse")
for c, t, b in (("datei", "Datei", 260), ("format", "Format", 80), ("anzahl", "Positionen", 70),
("zeit", "Importiert", 120)):
self.tree.heading(c, text=t)
self.tree.column(c, width=b)
r.pack(fill="both", expand=True, padx=10, pady=(10, 0))
k = tk.Frame(self, bg=w.FACE)
k.pack(fill="x", padx=10, pady=10)
w.knopf(k, "Datei entfernen", self._weg).pack(side="left")
w.knopf(k, "Schließen", self.abbrechen, breite=10).pack(side="right")
self._laden()
def _laden(self):
self.tree.delete(*self.tree.get_children())
for d in self.db.dateien(self.aid):
self.tree.insert("", "end", iid=str(d["id"]),
values=(d["dateiname"], d["format"], d["anzahl"], d["importiert"][:16]))
def _weg(self):
s = self.tree.selection()
if not s:
return
name = self.tree.item(s[0], "values")[0]
if w.frage(self, "Datei entfernen", f"Alle Positionen aus „{name}“ aus dieser Abrechnung entfernen?\n"
"Von Hand geänderte Beträge dieser Datei gehen verloren.", default_ja=False):
try:
self.db.datei_entfernen(int(s[0]))
except AbrechnungErledigt as exc:
w.info(self, "Datei entfernen", str(exc))
return
self.geaendert = True
self._laden()
def abbrechen(self):
self.result = self.geaendert
self.destroy()
# =========================================================================== Import-Bericht
class ImportBericht(w.Dialog):
def __init__(self, master, zeilen: list[tuple[str, str, int, int, str]], unbekannt: list[tuple[str, str, int, int]]):
"""zeilen: (datei, format, anzahl, summe_cent, hinweis); unbekannt: (anbieter, kennung, anzahl, summe)."""
super().__init__(master, "Import abgeschlossen", resizable=True)
tk.Label(self, text="Folgende Dateien wurden eingelesen:", anchor="w").pack(fill="x", padx=10, pady=(10, 2))
r, t = w.scroll_tree(self, columns=("datei", "format", "anzahl", "summe", "hinweis"), show="headings",
height=min(6, max(2, len(zeilen))))
for c, h, b, a in (("datei", "Datei", 220, "w"), ("format", "Format", 70, "w"), ("anzahl", "Pos.", 50, "e"),
("summe", "Summe", 90, "e"), ("hinweis", "Hinweis", 260, "w")):
t.heading(c, text=h)
t.column(c, width=b, anchor=a)
for d, f, n, s, hw in zeilen:
t.insert("", "end", values=(d, f, n, fmt(s) if n else "", hw))
r.pack(fill="both", expand=True, padx=10)
self.zuordnen = None
if unbekannt:
tk.Label(self, text="Nicht zugeordnete Kennungen (kein Händler hinterlegt – z. B. eigene Filiale):",
anchor="w").pack(fill="x", padx=10, pady=(10, 2))
r2, self.ut = w.scroll_tree(self, columns=("anbieter", "kennung", "anzahl", "summe"), show="headings",
height=min(6, len(unbekannt)), selectmode="extended")
for c, h, b, a in (("anbieter", "Anbieter", 90, "w"), ("kennung", "Kennung", 140, "w"),
("anzahl", "Pos.", 50, "e"), ("summe", "Summe", 90, "e")):
self.ut.heading(c, text=h)
self.ut.column(c, width=b, anchor=a)
for a, k, n, s in unbekannt:
self.ut.insert("", "end", iid=f"{a}\t{k}", values=(ANBIETER.get(a, a), k or "(leer)", n, fmt(s)))
r2.pack(fill="both", expand=True, padx=10)
k = tk.Frame(self, bg=w.FACE)
k.pack(fill="x", padx=10, pady=10)
if unbekannt:
w.knopf(k, "Kennung einem Händler zuordnen...", self._zuordnen).pack(side="left")
w.knopf(k, "Als eigene ausblenden...", self._eigen).pack(side="left", padx=6)
w.knopf(k, "OK", self.abbrechen, breite=10, default="active").pack(side="right")
self.bind("<Return>", lambda e: self.abbrechen())
def _zuordnen(self):
s = self.ut.selection()
if s:
a, k = s[0].split("\t", 1)
self.result = ("zuordnen", a, k)
self.destroy()
def _eigen(self):
s = self.ut.selection()
if s:
self.result = ("eigen", [tuple(x.split("\t", 1)) for x in s])
self.destroy()
class EigeneKennungenDialog(w.Dialog):
"""Kennungen der eigenen Filiale: Positionen damit werden ausgeblendet und nicht exportiert."""
def __init__(self, master, db: Datenbank):
super().__init__(master, "Eigene Kennungen")
self.db, self.geaendert = db, False
tk.Label(self, text="Positionen mit diesen Kennungen gehören zur eigenen Filiale. Sie werden\n"
"ausgeblendet, nicht mitgerechnet und nicht exportiert.\n"
"(Ist eine Kennung einem Händler zugeordnet, hat der Händler Vorrang.)",
justify="left", anchor="w").pack(fill="x", padx=10, pady=(10, 4))
r, self.tree = w.scroll_tree(self, columns=("anbieter", "wert"), show="headings", height=7,
selectmode="extended")
self.tree.heading("anbieter", text="Anbieter")
self.tree.heading("wert", text="Unterhändler-Nr. / VO-Nr.")
self.tree.column("anbieter", width=100)
self.tree.column("wert", width=200)
r.pack(fill="both", expand=True, padx=10)
z = tk.Frame(self, bg=w.FACE)
z.pack(fill="x", padx=10, pady=6)
self.v_anb = tk.StringVar(value="freenet")
ttk.Combobox(z, textvariable=self.v_anb, values=list(ANBIETER.values()), width=10,
state="readonly").pack(side="left")
self.v_wert = tk.StringVar()
e = tk.Entry(z, textvariable=self.v_wert, width=18)
e.pack(side="left", padx=4)
e.bind("<Return>", lambda ev: self._dazu())
w.knopf(z, "Hinzufügen", self._dazu).pack(side="left")
w.knopf(z, "Entfernen", self._weg).pack(side="left", padx=4)
self.knopfleiste([("Schließen", self.abbrechen)]).pack(pady=(0, 10))
self._laden()
def _laden(self):
self.tree.delete(*self.tree.get_children())
for a, k in self.db.eigene_kennungen():
self.tree.insert("", "end", iid=f"{a}\t{k}", values=(ANBIETER.get(a, a), k))
def _dazu(self):
wert = self.v_wert.get().strip()
if not wert:
return
anb = next((k for k, v in ANBIETER.items() if v == self.v_anb.get()), self.v_anb.get())
self.db.eigene_setzen(anb, wert, True)
self.v_wert.set("")
self.geaendert = True
self._laden()
def _weg(self):
for iid in self.tree.selection():
a, k = iid.split("\t", 1)
self.db.eigene_setzen(a, k, False)
self.geaendert = True
self._laden()
def abbrechen(self):
self.result = self.geaendert
self.destroy()
class KennungZuordnen(w.Dialog):
"""Fragt, ob eine Kennung einem bestehenden oder neuen Händler zugeordnet werden soll."""
def __init__(self, master, db: Datenbank, anbieter: str, kennung: str):
super().__init__(master, "Kennung zuordnen")
self.db, self.anbieter, self.kennung = db, anbieter, kennung
tk.Label(self, text=f"{ANBIETER.get(anbieter, anbieter)}-Kennung {kennung} zuordnen zu:",
anchor="w").pack(fill="x", padx=12, pady=(12, 6))
self.v = tk.StringVar(value="__neu__")
f = tk.Frame(self, bg=w.FACE)
f.pack(fill="x", padx=20)
tk.Radiobutton(f, text="Neuem Händler ...", variable=self.v, value="__neu__").pack(anchor="w")
haendler = db.haendler_liste()
if haendler:
tk.Radiobutton(f, text="Bestehendem Händler:", variable=self.v, value="__alt__").pack(anchor="w")
self.cb = ttk.Combobox(f, values=[h.name for h in haendler], state="readonly", width=34)
self.cb.pack(anchor="w", padx=22, pady=(0, 4))
self.cb.bind("<<ComboboxSelected>>", lambda e: self.v.set("__alt__"))
self._hmap = {h.name: h.id for h in haendler}
self.knopfleiste([("OK", self._ok), ("Abbrechen", self.abbrechen)]).pack(pady=12)
def _ok(self):
if self.v.get() == "__neu__":
self.result = ("neu", None)
else:
name = self.cb.get()
if not name:
w.warnung(self, "Kennung zuordnen", "Bitte einen Händler auswählen.")
return
hid = self._hmap[name]
kenn = self.db.kennungen(hid) + [(self.anbieter, self.kennung)]
try:
self.db.haendler_speichern(self.db.haendler(hid), kenn)
except ValueError as exc:
w.fehler(self, "Kennung zuordnen", str(exc))
return
self.result = ("alt", hid)
self.destroy()
class HaendlerWaehlen(w.Dialog):
"""Feste Zuweisung ausgewählter Positionen zu einem Händler."""
KEINER = "(keinem Händler – nicht auszahlen)"
def __init__(self, master, db: Datenbank, anzahl: int):
super().__init__(master, "Händler zuweisen")
self._hmap = {h.name: h.id for h in db.haendler_liste(nur_aktive=True)}
tk.Label(self, text=f"{anzahl} Position(en) fest zuweisen an:", anchor="w").pack(fill="x", padx=12,
pady=(12, 4))
self.cb = ttk.Combobox(self, values=list(self._hmap) + [self.KEINER], state="readonly", width=40)
self.cb.pack(padx=12, pady=4)
tk.Label(self, text="Die feste Zuweisung hat Vorrang vor Kennungen und der GROSSBUCHSTABEN-Regel.\n"
"Rückgängig: Rechtsklick > „Automatisch zuordnen“.", justify="left",
fg=w.SHADOW).pack(fill="x", padx=12)
self.bind("<Return>", lambda e: self._ok())
self.knopfleiste([("OK", self._ok), ("Abbrechen", self.abbrechen)]).pack(pady=12)
def _ok(self):
name = self.cb.get()
if not name:
w.warnung(self, "Händler zuweisen", "Bitte einen Händler auswählen.")
return
self.result = 0 if name == self.KEINER else self._hmap[name]
self.destroy()
def zeigen(self, fokus=None):
return super().zeigen(self.cb)
# =========================================================================== Export
class ExportDialog(w.Dialog):
def __init__(self, master, haendler: list[tuple[int, str, int, int]], vorauswahl: set[int], settings: dict,
titel: str):
"""haendler: (id, name, anzahl, summe_cent)"""
super().__init__(master, "Abrechnungen exportieren")
self.settings = settings
f = tk.Frame(self, bg=w.FACE)
f.pack(fill="both", expand=True, padx=10, pady=10)
tk.Label(f, text="Für welche Händler soll eine Excel-Datei erstellt werden?", anchor="w").pack(fill="x")
r, self.tree = w.scroll_tree(f, columns=("name", "anzahl", "summe"), show="tree headings", height=8,
selectmode="extended")
self.tree.heading("#0", text="")
self.tree.column("#0", width=40, stretch=False)
for c, t, b, a in (("name", "Händler", 220, "w"), ("anzahl", "Pos.", 50, "e"), ("summe", "Auszahlung", 100, "e")):
self.tree.heading(c, text=t)
self.tree.column(c, width=b, anchor=a)
self.haken = {}
for hid, name, n, s in haendler:
self.haken[hid] = hid in vorauswahl
self.tree.insert("", "end", iid=str(hid), text="[x]" if self.haken[hid] else "[ ]",
values=(name, n, fmt(s)))
self.tree.bind("<Button-1>", self._klick)
self.tree.bind("<space>", lambda e: [self._toggle(int(i)) for i in self.tree.selection()])
r.pack(fill="both", expand=True, pady=(2, 4))
kk = tk.Frame(f, bg=w.FACE)
kk.pack(fill="x")
w.knopf(kk, "Alle", lambda: self._alle(True)).pack(side="left")
w.knopf(kk, "Keine", lambda: self._alle(False)).pack(side="left", padx=4)
g = w.gruppe(f, "Optionen")
g.pack(fill="x", pady=(8, 0))
tk.Label(g, text="Bezeichnung im Dateinamen:").grid(row=0, column=0, sticky="w")
self.v_titel = tk.StringVar(value=titel)
tk.Entry(g, textvariable=self.v_titel, width=30).grid(row=0, column=1, columnspan=2, sticky="we", pady=2)
tk.Label(g, text="Zielordner:").grid(row=1, column=0, sticky="w")
self.v_ordner = tk.StringVar(value=settings.get("export_ordner") or "")
tk.Entry(g, textvariable=self.v_ordner, width=40).grid(row=1, column=1, sticky="we", pady=2)
w.knopf(g, "Durchsuchen...", self._ordner).grid(row=1, column=2, padx=(4, 0))
# Neuer Schlüssel: freenet und Vodafone stehen jetzt standardmäßig in EINER Datei
self.v_anb = tk.BooleanVar(value=settings.get("export_anbieter_getrennt", False))
self.v_null = tk.BooleanVar(value=settings.get("export_null_weglassen", True))
self.v_kopf = tk.BooleanVar(value=settings.get("export_kopfzeile", False))
self.v_sum = tk.BooleanVar(value=settings.get("export_summe", True))
self.v_auf = tk.BooleanVar(value=True)
for zeile, (text, var) in enumerate((
("freenet und Vodafone in getrennte Dateien (sonst beide in einer)", self.v_anb),
("Positionen mit 0,00 € weglassen", self.v_null),
("Kopfzeile mit Spaltennamen", self.v_kopf),
("Summenzeile unter der Tabelle", self.v_sum),
("Ordner nach dem Export öffnen", self.v_auf)), start=2):
tk.Checkbutton(g, text=text, variable=var).grid(row=zeile, column=0, columnspan=3, sticky="w")
tk.Label(g, text="Airtime steht in der Abrechnung als eine Summenzeile, die Einzelposten kommen in eine\n"
"eigene Datei „... - Airtime - ...xlsx“.", fg=w.SHADOW, justify="left", anchor="w").grid(
row=7, column=0, columnspan=3, sticky="w", pady=(4, 0))
g.columnconfigure(1, weight=1)
z = w.gruppe(f, "Zeitraum und Abrechnung")
z.pack(fill="x", pady=(8, 0))
zf = tk.Frame(z, bg=w.FACE)
zf.pack(fill="x")
tk.Label(zf, text="Nur von:").pack(side="left")
self.v_von, self.v_bis = tk.StringVar(), tk.StringVar()
tk.Entry(zf, textvariable=self.v_von, width=11).pack(side="left", padx=4)
tk.Label(zf, text="bis:").pack(side="left")
tk.Entry(zf, textvariable=self.v_bis, width=11).pack(side="left", padx=4)
w.knopf(zf, "Vormonat", self._vormonat).pack(side="left", padx=(4, 0))
self.v_basis = tk.StringVar(value=zr.BASEN[settings.get("export_basis", zr.AKTIVIERUNG)])
ttk.Combobox(z, textvariable=self.v_basis, values=list(zr.BASEN.values()), state="readonly",
width=40).pack(anchor="w", pady=(4, 0))
tk.Label(z, text="(leer = alle offenen Positionen; Datum als TT.MM.JJJJ)", fg=w.SHADOW, anchor="w").pack(
fill="x")
self.v_kennz = tk.BooleanVar(value=True)
tk.Checkbutton(z, text="Exportierte Positionen als abgerechnet kennzeichnen (werden dann nicht noch "
"einmal abgerechnet)", variable=self.v_kennz, anchor="w").pack(fill="x", pady=(4, 0))
self.knopfleiste([("Exportieren", self._ok), ("Abbrechen", self.abbrechen)]).pack(pady=(0, 10))
def _vormonat(self):
von, bis = zr.vormonat()
self.v_von.set(von.strftime("%d.%m.%Y"))
self.v_bis.set(bis.strftime("%d.%m.%Y"))
def _klick(self, e):
if self.tree.identify_column(e.x) == "#0":
iid = self.tree.identify_row(e.y)
if iid:
self._toggle(int(iid))
def _toggle(self, hid):
self.haken[hid] = not self.haken[hid]
self.tree.item(str(hid), text="[x]" if self.haken[hid] else "[ ]")
def _alle(self, an):
for hid in self.haken:
self.haken[hid] = an
self.tree.item(str(hid), text="[x]" if an else "[ ]")
def _ordner(self):
d = filedialog.askdirectory(parent=self, title="Zielordner wählen",
initialdir=self.v_ordner.get() or None)
if d:
self.v_ordner.set(d)
def _ok(self):
ids = [h for h, an in self.haken.items() if an]
if not ids:
w.warnung(self, "Export", "Bitte mindestens einen Händler anhaken.")
return
try:
von, bis = zr.datum_lesen(self.v_von.get()), zr.datum_lesen(self.v_bis.get())
except ValueError as exc:
w.warnung(self, "Zeitraum", str(exc))
return
basis = next(k for k, v in zr.BASEN.items() if v == self.v_basis.get())
if not self.v_ordner.get().strip():
self._ordner()
if not self.v_ordner.get().strip():
return
self.settings.update(export_ordner=self.v_ordner.get().strip(), export_anbieter_getrennt=self.v_anb.get(),
export_null_weglassen=self.v_null.get(), export_kopfzeile=self.v_kopf.get(),
export_summe=self.v_sum.get())
self.settings.pop("export_pro_anbieter", None)
self.result = dict(ids=ids, ordner=self.v_ordner.get().strip(), titel=self.v_titel.get().strip(),
pro_anbieter=self.v_anb.get(), null_weglassen=self.v_null.get(),
kopfzeile=self.v_kopf.get(), summe=self.v_sum.get(), oeffnen=self.v_auf.get(),
zeitraum=zr.Zeitraum(von, bis, basis) if (von or bis) else None,
kennzeichnen=self.v_kennz.get())
self.settings["export_basis"] = basis
self.destroy()
# =========================================================================== Einstellungen
class EinstellungenDialog(w.Dialog):
def __init__(self, master, settings: dict, db_pfad: str):
super().__init__(master, "Einstellungen")
self.settings = settings
g = w.gruppe(self, "Datenbank")
g.pack(fill="x", padx=10, pady=10)
tk.Label(g, text="Speicherort der Datenbank (Händler, Regeln, Abrechnungen):", anchor="w").grid(
row=0, column=0, columnspan=2, sticky="w")
self.v = tk.StringVar(value=db_pfad)
tk.Entry(g, textvariable=self.v, width=56).grid(row=1, column=0, sticky="we", pady=4)
w.knopf(g, "Ändern...", self._waehlen).grid(row=1, column=1, padx=(4, 0))
tk.Label(g, text="Tipp: Liegt die Datei auf einem Netzlaufwerk, können mehrere PCs dieselben\n"
"Händler nutzen – aber bitte nicht gleichzeitig damit arbeiten.",
justify="left", fg=w.SHADOW).grid(row=2, column=0, columnspan=2, sticky="w")
g2 = w.gruppe(self, "Darstellung")
g2.pack(fill="x", padx=10, pady=(0, 10))
self.v_design = tk.StringVar(value=settings.get("design", "modern"))
tk.Radiobutton(g2, text="Modern (wie Windows 11)", variable=self.v_design, value="modern").pack(anchor="w")
tk.Radiobutton(g2, text="Klassisch (Windows 95, Legacy)", variable=self.v_design, value="win95").pack(
anchor="w")
tk.Label(g2, text="Wirkt beim nächsten Programmstart.", fg=w.SHADOW).pack(anchor="w")
self.knopfleiste([("OK", self._ok), ("Abbrechen", self.abbrechen)]).pack(pady=(0, 10))
def _waehlen(self):
p = filedialog.asksaveasfilename(parent=self, title="Datenbank wählen oder neu anlegen",
defaultextension=".db", confirmoverwrite=False,
filetypes=[("Datenbank", "*.db"), ("Alle Dateien", "*.*")])
if p:
self.v.set(p)
def _ok(self):
self.result = dict(pfad=self.v.get().strip(), design=self.v_design.get())
self.destroy()
# =========================================================================== Info
class InfoDialog(w.Dialog):
def __init__(self, master):
super().__init__(master, f"Info über {APP_NAME}")
top = tk.Frame(self, bg=w.FACE)
top.pack(padx=14, pady=14)
c = tk.Canvas(top, width=110, height=110, bg=w.TEAL, bd=2, relief="sunken", highlightthickness=0)
c.pack(side="left", anchor="n")
self._img = w.make_icon(self, "app", 6)
c.create_image(57, 57, image=self._img)
rechts = tk.Frame(top, bg=w.FACE)
rechts.pack(side="left", padx=(14, 0), anchor="n")
tk.Label(rechts, text=APP_NAME, font=w.FONT_BIG).pack(anchor="w")
tk.Label(rechts, text=f"Version {__version__}", anchor="w").pack(anchor="w")
w.trenner(rechts, vertikal=False).pack(fill="x", pady=8)
tk.Label(rechts, text=f"Lizenziert für:\n{APP_ORG}\n\nProvisionsabrechnung für Unterhändler\n"
"freenet · Vodafone", justify="left").pack(anchor="w")
w.trenner(rechts, vertikal=False).pack(fill="x", pady=8)
tk.Label(rechts, text="Arbeitsspeicher für Provisionen: reichlich", fg=w.SHADOW).pack(anchor="w")
self.knopfleiste([("OK", self.abbrechen)]).pack(pady=(0, 12))
self.bind("<Return>", lambda e: self.abbrechen())