Files
provisionsmanager95/provtool/gui/dialogs.py
T
2026-11-02 09:32:04 +01:00

680 lines
33 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.db import CAPS_ANBIETER, CAPS_WERT, Datenbank, Haendler
from provtool.gui import win95 as w
from provtool.importer import ANBIETER
# Auswahl im Händler-Dialog: Schlüssel → Anzeige
KENNUNG_ARTEN = {"freenet": "freenet (Unterhändler-Nr.)", "vodafone": "Vodafone (VO-Nr.)",
CAPS_ANBIETER: "Vodafone (Name in GROSSBUCHSTABEN)"}
CAPS_TEXT = "Kunden-Nachname komplett in GROSSBUCHSTABEN"
from provtool.money import fmt, fmt_percent, parse_percent
# =========================================================================== 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)
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="Notiz:", anchor="nw").grid(row=4, column=0, sticky="nw", pady=2)
self.t_notiz = tk.Text(g, width=36, height=3, font=w.FONT)
self.t_notiz.grid(row=4, 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=4,
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)
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=4, 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()
self._kennungen = [neue_kennung]
self._kennungen_zeigen()
self.minsize(640, 460)
# ---- 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):
caps = self._anbieter_key(self.v_kanb.get()) == CAPS_ANBIETER
self.v_kwert.set("")
self._ke.configure(state="disabled" if caps else "normal")
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_aktiv.set(h.aktiv)
self.t_notiz.delete("1.0", "end")
self.t_notiz.insert("1.0", h.notiz or "")
self._kennungen = self.db.kennungen(h.id)
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):
if a == CAPS_ANBIETER:
self.kliste.insert("", "end", iid=str(i), values=("Vodafone", CAPS_TEXT))
else:
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_aktiv.set(True)
self.t_notiz.delete("1.0", "end")
self._kennungen = []
self._kennungen_zeigen()
self._regeln_zeigen()
def _kennung_dazu(self):
anb = self._anbieter_key(self.v_kanb.get())
wert = CAPS_WERT if anb == CAPS_ANBIETER else 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
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())
try:
hid = self.db.haendler_speichern(h, self._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)
# =========================================================================== 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", "geaendert", "anzahl", "dateien"), show="headings",
height=12, selectmode="browse")
for c, t, b in (("titel", "Bezeichnung", 180), ("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():
self.tree.insert("", "end", iid=str(a["id"]),
values=(a["titel"], 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):
self.db.datei_entfernen(int(s[0]))
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="browse")
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, "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:
self.result = tuple(s[0].split("\t", 1))
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=30, 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))
self.v_anb = tk.BooleanVar(value=settings.get("export_pro_anbieter", 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)
tk.Checkbutton(g, text="Pro Anbieter (freenet / Vodafone) eine eigene Datei",
variable=self.v_anb).grid(row=2, column=0, columnspan=3, sticky="w")
tk.Checkbutton(g, text="Kopfzeile mit Spaltennamen", variable=self.v_kopf).grid(row=3, column=0, columnspan=3,
sticky="w")
tk.Checkbutton(g, text="Summenzeile unter der Tabelle", variable=self.v_sum).grid(row=4, column=0,
columnspan=3, sticky="w")
tk.Checkbutton(g, text="Ordner nach dem Export öffnen", variable=self.v_auf).grid(row=5, column=0,
columnspan=3, sticky="w")
g.columnconfigure(1, weight=1)
self.knopfleiste([("Exportieren", self._ok), ("Abbrechen", self.abbrechen)]).pack(pady=(0, 10))
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
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_pro_anbieter=self.v_anb.get(),
export_kopfzeile=self.v_kopf.get(), export_summe=self.v_sum.get())
self.result = dict(ids=ids, ordner=self.v_ordner.get().strip(), titel=self.v_titel.get().strip(),
pro_anbieter=self.v_anb.get(), kopfzeile=self.v_kopf.get(), summe=self.v_sum.get(),
oeffnen=self.v_auf.get())
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")
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 = self.v.get().strip()
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())