Provisions-Manager 95: Import, Händler-DB, Auszahlungsregeln, Excel-Export, Win95-Oberfläche, Wine-Build

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-11-02 09:24:39 +01:00
commit da2e3c9ba1
25 files changed
+3366

No files matched your search

+633
View File
@@ -0,0 +1,633 @@
"""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 Datenbank, Haendler
from provtool.gui import win95 as w
from provtool.importer import ANBIETER
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=200)
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="freenet")
cb = ttk.Combobox(kf, textvariable=self.v_kanb, values=list(ANBIETER.values()), width=10, state="readonly")
cb.pack(side="left")
self.v_kwert = tk.StringVar()
ke = tk.Entry(kf, textvariable=self.v_kwert, width=18)
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 ANBIETER.items() if v == anzeige), anzeige)
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):
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):
wert = self.v_kwert.get().strip()
if not wert:
return
anb = self._anbieter_key(self.v_kanb.get())
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()
# =========================================================================== 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())