"""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("<>", 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("<>", 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("", 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("", 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("", 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("", 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="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("", 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("", 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("<>", 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("", 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("", self._klick) self.tree.bind("", 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") g2 = w.gruppe(self, "Darstellung") g2.pack(fill="x", padx=10, pady=(0, 10)) self.v_klassisch = tk.BooleanVar(value=bool(settings.get("klassischer_rahmen", True))) tk.Checkbutton(g2, text="Windows-95-Titelleiste verwenden (sonst die normale von Windows)", variable=self.v_klassisch).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(), klassisch=self.v_klassisch.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("", lambda e: self.abbrechen())