Neues Design im Stil der Handy-Insel-Webtools: Kopfzeile, Seitenleiste, Kennzahlen, Filter, hell/dunkel, IBM Plex
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2600eea574
commit
f296483fdc
12 files changed
+654
-160
No files matched your search
+257
-103
@@ -82,9 +82,13 @@ class App:
|
||||
self.rahmen = rh.Hauptrahmen(root, w.APP_NAME, self.icons("app"), self.beenden, klassisch=klassisch)
|
||||
self.rahmen.set_titel(w.APP_NAME)
|
||||
self.inhalt = self.rahmen.inhalt
|
||||
if w.DUNKEL:
|
||||
from provtool.gui import win32
|
||||
win32.dunkle_titelleiste(root)
|
||||
|
||||
self._menue()
|
||||
self._toolbar()
|
||||
self.neustart = False # Design umgeschaltet: main() baut das Fenster neu auf
|
||||
self.filter = "alle"
|
||||
self._kopf()
|
||||
self._body()
|
||||
self.status = w.Statusleiste(self.inhalt, [("info", 0), ("pos", 16), ("orig", 22), ("ausz", 22)])
|
||||
griff = self.rahmen.griff(self.status)
|
||||
@@ -107,9 +111,52 @@ class App:
|
||||
root.after(500, self.tipp_des_tages)
|
||||
|
||||
# ================================================================= Aufbau
|
||||
def _menue(self):
|
||||
self.menueleiste = mn.Menueleiste(self.inhalt, self.root)
|
||||
self.menueleiste.pack(side="top", fill="x")
|
||||
def _kopf(self):
|
||||
"""Kopfzeile wie in den Webtools: Logo, Menüs, rechts Suche (modern auch Hell/Dunkel)."""
|
||||
kopf = tk.Frame(self.inhalt, bg=w.FACE)
|
||||
kopf.pack(side="top", fill="x")
|
||||
if w.MODERN:
|
||||
w.trenner(self.inhalt, vertikal=False).pack(side="top", fill="x")
|
||||
innen = tk.Frame(kopf, bg=w.FACE)
|
||||
innen.pack(fill="x", padx=(18, 14) if w.MODERN else 0, pady=10 if w.MODERN else 0)
|
||||
if w.MODERN:
|
||||
w.logo(innen).pack(side="left")
|
||||
namen = tk.Frame(innen, bg=w.FACE)
|
||||
namen.pack(side="left", padx=(10, 22))
|
||||
tk.Label(namen, text=w.APP_NAME, font=(w.FONT_FAMILY, w.FONT_SIZE + 1, "bold")).pack(anchor="w")
|
||||
tk.Label(namen, text="Handy-Insel", fg=w.SHADOW, font=(w.MONO_FAMILY, w.FONT_SIZE - 2)).pack(anchor="w")
|
||||
self._menue(innen)
|
||||
|
||||
if w.MODERN:
|
||||
self.b_design = w.knopf(innen, "Hell" if w.DUNKEL else "Dunkel", self.design_umschalten)
|
||||
self.b_design.pack(side="right", padx=(8, 0))
|
||||
w.Tooltip(self.b_design, "Zwischen hellem und dunklem Design wechseln")
|
||||
self.v_suche = tk.StringVar()
|
||||
if w.MODERN: # Suchfeld mit Rahmen, Hinweis und Tastenkürzel
|
||||
sf = tk.Frame(innen, bg=w.WHITE, highlightthickness=1, highlightbackground=w.RAND2,
|
||||
highlightcolor=w.NAVY)
|
||||
sf.pack(side="right")
|
||||
tk.Label(sf, text="Suchen", bg=w.WHITE, fg=w.SHADOW).pack(side="left", padx=(10, 2))
|
||||
e = tk.Entry(sf, textvariable=self.v_suche, width=28, highlightthickness=0, bd=4)
|
||||
e.pack(side="left")
|
||||
tk.Label(sf, text="Strg F", bg=w.WHITE, fg=w.SHADOW, font=(w.MONO_FAMILY, w.FONT_SIZE - 2)).pack(
|
||||
side="left", padx=(2, 10))
|
||||
e.bind("<FocusIn>", lambda _e: sf.configure(highlightbackground=w.NAVY), add="+")
|
||||
e.bind("<FocusOut>", lambda _e: sf.configure(highlightbackground=w.RAND2), add="+")
|
||||
else:
|
||||
sf = tk.Frame(innen, bg=w.FACE)
|
||||
sf.pack(side="right", padx=8)
|
||||
tk.Label(sf, text="Suchen:").pack(side="left")
|
||||
e = tk.Entry(sf, textvariable=self.v_suche, width=22)
|
||||
e.pack(side="left", padx=4)
|
||||
self._suche_geplant = None
|
||||
self.v_suche.trace_add("write", lambda *_: self._suche_planen())
|
||||
self._suchfeld = e
|
||||
self._suchrahmen = sf
|
||||
|
||||
def _menue(self, master):
|
||||
self.menueleiste = mn.Menueleiste(master, self.root)
|
||||
self.menueleiste.pack(side="left")
|
||||
d = self.menueleiste.menue("Datei")
|
||||
d.add_command(label="Neue Abrechnung...", accelerator="Strg+N", command=self.neu)
|
||||
d.add_command(label="Abrechnung öffnen...", accelerator="Strg+O", command=self.abrechnung_waehlen)
|
||||
@@ -144,7 +191,8 @@ class App:
|
||||
h.add_command(label="Provisionsarten (Airtime / Jahresbonus)...", command=self.provisionsarten)
|
||||
h.add_command(label="Standard-Staffel Jahresbonus...", command=self.standard_staffel)
|
||||
|
||||
hi = self.menueleiste.menue("?", underline=None)
|
||||
self.hilfe_menue = "Hilfe" if w.MODERN else "?"
|
||||
hi = self.menueleiste.menue(self.hilfe_menue, underline=None)
|
||||
hi.add_command(label="Kurzanleitung", accelerator="F1", command=self.hilfe)
|
||||
hi.add_command(label="Rundgang durch das Programm", command=self.rundgang)
|
||||
hi.add_command(label="Tipp des Tages...", command=self.tipp_des_tages)
|
||||
@@ -153,116 +201,169 @@ class App:
|
||||
hi.add_separator()
|
||||
hi.add_command(label=f"Info über {w.APP_NAME}...", command=lambda: dlg.InfoDialog(self.root).zeigen())
|
||||
|
||||
def _toolbar(self):
|
||||
tb = tk.Frame(self.inhalt, bg=w.FACE, bd=0 if w.MODERN else 1, relief="raised")
|
||||
self.tb: dict[str, w.ToolbarButton] = {}
|
||||
tb.pack(side="top", fill="x")
|
||||
for text, icon, cmd, tip in (
|
||||
("Neu", "neu", self.neu, "Neue Abrechnung anlegen (Strg+N)"),
|
||||
("Öffnen", "oeffnen", self.abrechnung_waehlen, "Gespeicherte Abrechnung öffnen (Strg+O)"),
|
||||
(None, None, None, None),
|
||||
("Import", "import", self.importieren, "CSV-/Excel-Dateien einlesen (Strg+I)"),
|
||||
("Export", "export", self.exportieren, "Händler-Abrechnungen als Excel speichern (Strg+E)"),
|
||||
(None, None, None, None),
|
||||
("Händler", "haendler", self.haendler, "Händler und Kennungen verwalten (Strg+H)"),
|
||||
("Prozent", "prozent", self.prozent, "Auszahlung für Auswahl in Prozent festlegen (F4)"),
|
||||
("Betrag", "rechner", self.betrag_bearbeiten, "Betrag der Position ändern (F2)"),
|
||||
):
|
||||
if text is None:
|
||||
w.trenner(tb).pack(side="left", fill="y", padx=4, pady=3)
|
||||
continue
|
||||
self.tb[text] = w.ToolbarButton(tb, text, self.icons(icon), cmd, tip)
|
||||
self.tb[text].pack(side="left", padx=1, pady=1)
|
||||
self.v_suche = tk.StringVar()
|
||||
sf = tk.Frame(tb, bg=w.FACE)
|
||||
sf.pack(side="right", padx=8)
|
||||
tk.Label(sf, text="Suchen:").pack(side="left")
|
||||
e = tk.Entry(sf, textvariable=self.v_suche, width=22)
|
||||
e.pack(side="left", padx=4)
|
||||
self._suche_geplant = None
|
||||
self.v_suche.trace_add("write", lambda *_: self._suche_planen())
|
||||
self._suchfeld = e
|
||||
self._suchrahmen = sf
|
||||
FILTER = (("alle", "Alle"), ("unzugeordnet", "Nicht zugeordnet"), ("manuell", "Von Hand geändert"),
|
||||
("doppelt", "Doppelt"), ("abgerechnet", "Bereits abgerechnet"))
|
||||
|
||||
def _body(self):
|
||||
desk = tk.Frame(self.inhalt, bg=w.TEAL)
|
||||
m = w.MODERN
|
||||
desk = tk.Frame(self.inhalt, bg=w.BG)
|
||||
desk.pack(side="top", fill="both", expand=True)
|
||||
# Trennlinie zwischen den Spalten: grau und erhaben wie ein Win95-Splitter, mit der Maus ziehbar.
|
||||
# Wie im Win95-Explorer wandert beim Ziehen nur eine Linie mit; neu aufgebaut wird erst beim
|
||||
# Loslassen (flüssig, auch mit vielen Positionen).
|
||||
pw = self.pw = tk.PanedWindow(desk, orient="horizontal", bg=w.FACE, sashwidth=7,
|
||||
sashrelief="flat" if w.MODERN else "raised",
|
||||
# Trennlinie zwischen den Spalten, mit der Maus ziehbar. Beim Ziehen wandert nur eine Linie mit;
|
||||
# neu aufgebaut wird erst beim Loslassen (flüssig, auch mit vielen Positionen).
|
||||
pw = self.pw = tk.PanedWindow(desk, orient="horizontal", bg=w.BG, sashwidth=6 if m else 7,
|
||||
sashrelief="flat" if m else "raised",
|
||||
bd=0, opaqueresize=False, sashcursor="sb_h_double_arrow")
|
||||
pw.pack(fill="both", expand=True, padx=6, pady=6)
|
||||
pw.pack(fill="both", expand=True, padx=0 if m else 6, pady=0 if m else 6)
|
||||
|
||||
links = tk.Frame(pw, bg=w.FACE)
|
||||
# --- Abrechnungen (ein Klick öffnet)
|
||||
self.f_a = w.Fenster(links, "Abrechnungen", self.icons("oeffnen"))
|
||||
self.f_a.pack(fill="x", pady=(0, 6))
|
||||
r, self.atree = w.scroll_tree(self.f_a.inhalt, columns=("status",), show="tree headings", height=5,
|
||||
selectmode="browse")
|
||||
self.atree.heading("#0", text="Abrechnung", anchor="w")
|
||||
self.atree.heading("status", text="Status")
|
||||
self.atree.column("#0", width=170, minwidth=80, stretch=False)
|
||||
self.atree.column("status", width=90, anchor="w", stretch=False)
|
||||
# ---------------- Seitenleiste: Abrechnungen und Händler
|
||||
links = tk.Frame(pw, bg=w.FACE, bd=0 if m else 2, relief="flat" if m else "raised")
|
||||
innen = tk.Frame(links, bg=w.FACE)
|
||||
innen.pack(fill="both", expand=True, padx=12 if m else 4, pady=(16, 10) if m else 4)
|
||||
seite = dict(style="Seite.Treeview") if m else {}
|
||||
|
||||
self.f_a = tk.Frame(innen, bg=w.FACE)
|
||||
self.f_a.pack(fill="x", pady=(0, 16 if m else 6))
|
||||
zeile = tk.Frame(self.f_a, bg=w.FACE)
|
||||
zeile.pack(fill="x", pady=(0, 4))
|
||||
w.abschnitt(zeile, "Abrechnungen").pack(side="left", padx=(8 if m else 2, 0))
|
||||
self.b_neu = w.knopf(zeile, "+ Neu", self.neu, padx=8, pady=0, **self._link())
|
||||
self.b_neu.pack(side="right")
|
||||
w.Tooltip(self.b_neu, "Neue Abrechnung anlegen (Strg+N)")
|
||||
r, self.atree = w.scroll_tree(self.f_a, columns=("status",), show="tree", height=5, selectmode="browse",
|
||||
auto=True, **seite)
|
||||
self.atree.column("#0", width=150, minwidth=80, stretch=False)
|
||||
self.atree.column("status", width=110, anchor="e", stretch=False)
|
||||
w.auto_breite(self.atree, {"#0": 1})
|
||||
self.atree.tag_configure("leise", foreground=w.SHADOW)
|
||||
self._punkte = {f: w.punkt(self.root, getattr(w, f)) for f in ("BLAU", "GOLD", "GRUEN", "ROT", "SHADOW")}
|
||||
r.pack(fill="both", expand=True)
|
||||
self.atree.bind("<<TreeviewSelect>>", self._abrechnung_gewaehlt)
|
||||
|
||||
# --- Händler-Fenster
|
||||
self.f_h = w.Fenster(links, "Händler", self.icons("haendler"))
|
||||
self.f_h = tk.Frame(innen, bg=w.FACE)
|
||||
self.f_h.pack(fill="both", expand=True)
|
||||
r, self.htree = w.scroll_tree(self.f_h.inhalt, columns=("anzahl", "summe"), show="tree headings",
|
||||
selectmode="browse")
|
||||
self.htree.heading("#0", text="Händler", anchor="w")
|
||||
self.htree.heading("anzahl", text="Pos.")
|
||||
self.htree.heading("summe", text="Auszahlung")
|
||||
self.htree.column("#0", width=150, minwidth=80, stretch=False)
|
||||
self.htree.column("anzahl", width=38, anchor="e", stretch=False)
|
||||
self.htree.column("summe", width=84, anchor="e", stretch=False)
|
||||
zeile = tk.Frame(self.f_h, bg=w.FACE)
|
||||
zeile.pack(fill="x", pady=(0, 4))
|
||||
w.abschnitt(zeile, "Händler").pack(side="left", padx=(8 if m else 2, 0))
|
||||
self.b_haendler = w.knopf(zeile, "Verwalten", self.haendler, padx=8, pady=0, **self._link())
|
||||
self.b_haendler.pack(side="right")
|
||||
w.Tooltip(self.b_haendler, "Händler und Kennungen verwalten (Strg+H)")
|
||||
r, self.htree = w.scroll_tree(self.f_h, columns=("anzahl", "summe"), show="tree", selectmode="browse",
|
||||
auto=True, **seite)
|
||||
self.htree.column("#0", width=140, minwidth=80, stretch=False)
|
||||
self.htree.column("anzahl", width=40, anchor="e", stretch=False)
|
||||
self.htree.column("summe", width=96, anchor="e", stretch=False)
|
||||
w.auto_breite(self.htree, {"#0": 1})
|
||||
self.htree.tag_configure("grau", foreground=w.SHADOW)
|
||||
self.htree.tag_configure("fett", font=w.FONT_BOLD)
|
||||
r.pack(fill="both", expand=True)
|
||||
self.htree.bind("<<TreeviewSelect>>", self._haendler_gewaehlt)
|
||||
self.htree.bind("<Double-1>", lambda e: self._haendler_doppelklick())
|
||||
pw.add(links, minsize=240, width=310)
|
||||
pw.add(links, minsize=240, width=320)
|
||||
|
||||
# ---------------- Inhalt: Kopf der Abrechnung, Kennzahlen, Positionen
|
||||
rechts = tk.Frame(pw, bg=w.BG)
|
||||
innen = tk.Frame(rechts, bg=w.BG)
|
||||
innen.pack(fill="both", expand=True, padx=(16, 20) if m else 0, pady=(16, 0) if m else 0)
|
||||
|
||||
kopf = tk.Frame(innen, bg=w.BG)
|
||||
kopf.pack(fill="x", pady=(0, 14 if m else 6))
|
||||
texte = tk.Frame(kopf, bg=w.BG)
|
||||
texte.pack(side="left", fill="x", expand=True)
|
||||
self.l_status = w.abschnitt(texte, "", bg=w.BG)
|
||||
self.l_status.pack(anchor="w")
|
||||
self.l_titel = tk.Label(texte, text="", bg=w.BG, font=w.FONT_TITEL, anchor="w")
|
||||
self.l_titel.pack(anchor="w")
|
||||
self.l_info = tk.Label(texte, text="", bg=w.BG, fg=w.TEXT2, anchor="w")
|
||||
self.l_info.pack(anchor="w")
|
||||
self.b_export = w.knopf(kopf, "Exportieren", self.exportieren, primaer=True)
|
||||
self.b_export.pack(side="right", anchor="s")
|
||||
w.Tooltip(self.b_export, "Händler-Abrechnungen als Excel speichern (Strg+E)")
|
||||
self.b_import = w.knopf(kopf, "Importieren", self.importieren)
|
||||
self.b_import.pack(side="right", anchor="s", padx=8)
|
||||
w.Tooltip(self.b_import, "CSV-/Excel-Dateien einlesen (Strg+I)")
|
||||
self.b_erledigt = w.knopf(kopf, "Als erledigt markieren", self.erledigt_umschalten)
|
||||
self.b_erledigt.pack(side="right", anchor="s")
|
||||
self.tb = {"Neu": self.b_neu, "Import": self.b_import, "Export": self.b_export, "Händler": self.b_haendler}
|
||||
|
||||
kacheln = tk.Frame(innen, bg=w.BG)
|
||||
kacheln.pack(fill="x", pady=(0, 14 if m else 6))
|
||||
self.kpi: dict[str, tuple[tk.Label, tk.Label]] = {}
|
||||
for i, (key, titel) in enumerate((("pos", "Positionen"), ("orig", "Provision"), ("ausz", "Auszahlung"),
|
||||
("air", "Airtime"))):
|
||||
k = w.Karte(kacheln)
|
||||
k.grid(row=0, column=i, sticky="nsew", padx=(0 if i == 0 else 7, 0 if i == 3 else 7))
|
||||
kacheln.columnconfigure(i, weight=1, uniform="kpi")
|
||||
ki = tk.Frame(k, bg=w.FACE)
|
||||
ki.pack(fill="both", expand=True, padx=16 if m else 8, pady=12 if m else 6)
|
||||
w.abschnitt(ki, titel).pack(anchor="w")
|
||||
farbe = {"ausz": w.NAVY, "air": w.GRUEN}.get(key, w.DARK)
|
||||
wert = tk.Label(ki, text="", font=w.FONT_ZAHL, fg=farbe, anchor="w")
|
||||
wert.pack(anchor="w", pady=(4, 0))
|
||||
info = tk.Label(ki, text="", fg=w.TEXT2, anchor="w")
|
||||
info.pack(anchor="w")
|
||||
self.kpi[key] = (wert, info)
|
||||
|
||||
self.f_p = w.Karte(innen)
|
||||
self.f_p.pack(fill="both", expand=True, pady=(0, 12 if m else 0))
|
||||
kopfzeile = tk.Frame(self.f_p, bg=w.FACE)
|
||||
kopfzeile.pack(fill="x", padx=14 if m else 4, pady=10 if m else 4)
|
||||
self.l_ptitel = tk.Label(kopfzeile, text="Positionen", font=(w.FONT_FAMILY, w.FONT_SIZE + 1, "bold"))
|
||||
self.l_ptitel.pack(side="left", padx=(0, 12))
|
||||
self.pills: dict[str, w.Pill] = {}
|
||||
for key, text in self.FILTER:
|
||||
self.pills[key] = w.Pill(kopfzeile, text, lambda k=key: self._filter_setzen(k), aktiv=key == self.filter)
|
||||
self.pills[key].pack(side="left", padx=(0, 6))
|
||||
self.l_auswahl = tk.Label(kopfzeile, text="", fg=w.TEXT2)
|
||||
self.l_auswahl.pack(side="right")
|
||||
if m:
|
||||
w.trenner(self.f_p, vertikal=False).pack(fill="x")
|
||||
|
||||
# --- Positionen-Fenster
|
||||
self.f_p = w.Fenster(pw, "Positionen", self.icons("neu"))
|
||||
cols = ("datum", "rufnummer", "tarif", "zeitraum", "kennung", "orig", "proz", "ausz")
|
||||
r2, self.ptree = w.scroll_tree(self.f_p.inhalt, columns=cols, show="tree headings", selectmode="extended")
|
||||
self.ptree.heading("#0", text="Provisionsart / Kunde", anchor="w")
|
||||
r2, self.ptree = w.scroll_tree(self.f_p, columns=cols, show="tree headings", selectmode="extended")
|
||||
if m:
|
||||
r2.configure(highlightthickness=0)
|
||||
kopf_text = (lambda t: t.upper()) if m else (lambda t: t)
|
||||
self.ptree.heading("#0", text=kopf_text("Provisionsart / Kunde"), anchor="w")
|
||||
self.ptree.column("#0", width=200, minwidth=140, stretch=False)
|
||||
for c, t, b, a in (("datum", "Datum", 76, "w"), ("rufnummer", "Rufnummer", 100, "w"),
|
||||
("tarif", "Tarif", 120, "w"), ("zeitraum", "Zeitraum", 84, "w"),
|
||||
("kennung", "Kennung", 70, "w"),
|
||||
("orig", "Provision", 78, "e"), ("proz", "%", 52, "e"), ("ausz", "Auszahlung", 84, "e")):
|
||||
self.ptree.heading(c, text=t, anchor=a)
|
||||
for c, t, b, a in (("datum", "Datum", 86, "w"), ("rufnummer", "Rufnummer", 112, "w"),
|
||||
("tarif", "Tarif", 120, "w"), ("zeitraum", "Zeitraum", 94, "w"),
|
||||
("kennung", "Kennung", 80, "w"),
|
||||
("orig", "Provision", 100, "e"), ("proz", "%", 64, "e"), ("ausz", "Auszahlung", 112, "e")):
|
||||
self.ptree.heading(c, text=kopf_text(t), anchor=a)
|
||||
self.ptree.column(c, width=b, minwidth=80 if c == "tarif" else 30, anchor=a, stretch=False)
|
||||
w.auto_breite(self.ptree, {"#0": 0.6, "tarif": 0.4})
|
||||
self.ptree.tag_configure("gruppe", background=w.LIGHT, font=w.FONT_BOLD)
|
||||
self.ptree.tag_configure("manuell", foreground="#0000c0")
|
||||
self.ptree.tag_configure("abw", foreground="#a00000")
|
||||
self.ptree.tag_configure("negativ", foreground="#a00000")
|
||||
self.ptree.tag_configure("zebra", background=w.ZEBRA)
|
||||
self.ptree.tag_configure("manuell", foreground=w.MANUELL)
|
||||
self.ptree.tag_configure("abw", foreground=w.ROT)
|
||||
self.ptree.tag_configure("negativ", foreground=w.ROT)
|
||||
self.ptree.tag_configure("abgerechnet", foreground=w.SHADOW)
|
||||
r2.pack(fill="both", expand=True)
|
||||
self.ptree.bind("<Double-1>", self._doppelklick)
|
||||
self.ptree.bind("<Button-3>", self._kontextmenue)
|
||||
self.ptree.bind("<<TreeviewSelect>>", lambda e: self._status_auswahl())
|
||||
|
||||
leiste = tk.Frame(self.f_p.inhalt, bg=w.FACE)
|
||||
leiste.pack(fill="x", pady=(4, 0))
|
||||
self.b_prozent = w.knopf(leiste, "Prozent...", self.prozent, self.icons("prozent"))
|
||||
leiste = tk.Frame(self.f_p, bg=w.FACE)
|
||||
leiste.pack(fill="x", padx=14 if m else 2, pady=10 if m else (4, 2))
|
||||
self.b_prozent = w.knopf(leiste, "Prozent...", self.prozent, None if m else self.icons("prozent"))
|
||||
self.b_prozent.pack(side="left")
|
||||
w.knopf(leiste, "Betrag ändern...", self.betrag_bearbeiten, self.icons("rechner")).pack(side="left", padx=4)
|
||||
w.knopf(leiste, "Betrag ändern...", self.betrag_bearbeiten,
|
||||
None if m else self.icons("rechner")).pack(side="left", padx=6)
|
||||
w.knopf(leiste, "Zurücksetzen", self.zuruecksetzen).pack(side="left")
|
||||
self.v_alle_auf = tk.BooleanVar(value=True)
|
||||
w.knopf(leiste, "Alle zuklappen", lambda: self._klappen(False)).pack(side="right")
|
||||
w.knopf(leiste, "Alle aufklappen", lambda: self._klappen(True)).pack(side="right", padx=4)
|
||||
tk.Label(leiste, text="Blau = von Hand · Rot = abweichend · Grau = abgerechnet", fg=w.SHADOW).pack(
|
||||
side="left", padx=8)
|
||||
pw.add(self.f_p, minsize=500)
|
||||
w.knopf(leiste, "Alle aufklappen", lambda: self._klappen(True)).pack(side="right", padx=6)
|
||||
pw.add(rechts, minsize=560)
|
||||
|
||||
@staticmethod
|
||||
def _link() -> dict:
|
||||
"""Kleine Textknöpfe in der Seitenleiste (modern ohne Rahmen, in Akzentfarbe)."""
|
||||
return dict(fg=w.NAVY, highlightthickness=0, font=w.FONT_BOLD) if w.MODERN else {}
|
||||
|
||||
def _filter_setzen(self, key: str):
|
||||
self.filter = key
|
||||
for k, pill in self.pills.items():
|
||||
pill.setzen(k == key)
|
||||
self._tabelle()
|
||||
|
||||
def _tasten(self):
|
||||
r = self.root
|
||||
@@ -300,15 +401,35 @@ class App:
|
||||
self.rahmen.set_titel(w.APP_NAME)
|
||||
self.status.set("info", " Keine Abrechnung geöffnet – Datei > Neue Abrechnung oder Import")
|
||||
self._abrechnungsliste()
|
||||
self._seitenkopf()
|
||||
self._haendlerliste(behalte_auswahl)
|
||||
self._tabelle()
|
||||
|
||||
def _seitenkopf(self):
|
||||
"""Titel, Status und Knöpfe über den Kennzahlen."""
|
||||
a = self.db.abrechnung(self.aid) if self.aid else None
|
||||
if not a:
|
||||
self.l_status.configure(text="Keine Abrechnung geöffnet".upper() if w.MODERN else "Keine Abrechnung")
|
||||
self.l_titel.configure(text="Willkommen")
|
||||
self.l_info.configure(text="Links „+ Neu“ anlegen oder gleich Dateien importieren.")
|
||||
self.b_erledigt.configure(text="Als erledigt markieren", state="disabled")
|
||||
return
|
||||
erledigt = self.db.erledigt(self.aid)
|
||||
dateien = self.db.dateien(self.aid)
|
||||
status = "erledigt" if erledigt else "offen"
|
||||
self.l_status.configure(text=f"Abrechnung · {status}".upper() if w.MODERN else f"Abrechnung ({status})")
|
||||
self.l_titel.configure(text=a["titel"])
|
||||
self.l_info.configure(text=f"{len(dateien)} {'Datei' if len(dateien) == 1 else 'Dateien'} importiert · "
|
||||
f"{sum(d['anzahl'] for d in dateien)} Zeilen" if dateien else "Noch nichts importiert")
|
||||
self.b_erledigt.configure(text="Wieder öffnen" if erledigt else "Als erledigt markieren", state="normal")
|
||||
|
||||
def _abrechnungsliste(self):
|
||||
t = self.atree
|
||||
t.delete(*t.get_children())
|
||||
for a in self.db.abrechnungen():
|
||||
status = "erledigt" if a["erledigt"] else (f"{a['abgerechnet']} abgerechnet" if a["abgerechnet"] else "offen")
|
||||
t.insert("", "end", iid=str(a["id"]), text=a["titel"], values=(status,))
|
||||
status, farbe = (("erledigt", "GRUEN") if a["erledigt"] else
|
||||
(f"{a['abgerechnet']} abgerechnet", "GOLD") if a["abgerechnet"] else ("offen", "BLAU"))
|
||||
t.insert("", "end", iid=str(a["id"]), text=" " + a["titel"], values=(status,), image=self._punkte[farbe])
|
||||
if self.aid and t.exists(str(self.aid)):
|
||||
t.selection_set(str(self.aid))
|
||||
t.see(str(self.aid))
|
||||
@@ -398,6 +519,10 @@ class App:
|
||||
pos = [p for p in pos if such in " ".join(str(p.get(k) or "") for k in (
|
||||
"rufnummer", "vorname", "name", "tarif", "provisionsart", "kennung", "zeitraum", "aktionscode",
|
||||
"imei")).lower()]
|
||||
pruef = {"unzugeordnet": lambda p: p["haendler_id"] == UNZUGEORDNET, "manuell": lambda p: p["manuell"],
|
||||
"doppelt": lambda p: p["doppelt"], "abgerechnet": lambda p: p["abgerechnet"]}.get(self.filter)
|
||||
if pruef:
|
||||
pos = [p for p in pos if pruef(p)]
|
||||
gruppen: dict[str, list[dict]] = defaultdict(list)
|
||||
for p in exp.sortiert(pos):
|
||||
gruppen[self._gruppe_von(p)].append(p)
|
||||
@@ -414,8 +539,8 @@ class App:
|
||||
pz = fmt_percent(next(iter(proz))) if len(proz) == 1 and None not in proz else ("gemischt" if so else "")
|
||||
t.insert("", "end", iid=giid, text=f"{gname} ({len(plist)})", open=(war_leer or giid in offen or bool(such)),
|
||||
values=("", "", "", "", "", fmt(so), pz, fmt(sa)), tags=("gruppe",))
|
||||
for p in plist:
|
||||
tags = []
|
||||
for i, p in enumerate(plist):
|
||||
tags = ["zebra"] if i % 2 else []
|
||||
if p["abgerechnet"] or p["doppelt"]:
|
||||
tags.append("abgerechnet")
|
||||
elif p["manuell"]:
|
||||
@@ -434,17 +559,30 @@ class App:
|
||||
name = {ALLE: "Alle Positionen", UNZUGEORDNET: "Nicht zugeordnet",
|
||||
EIGEN: "Eigene Kennungen (ausgeblendet, werden nicht exportiert)"}.get(
|
||||
self.ansicht, self.haendler_namen.get(self.ansicht, ""))
|
||||
self.f_p.set_titel(f"Positionen – {name}")
|
||||
self.l_ptitel.configure(text=f"Positionen – {name}")
|
||||
so = sum(p["betrag_orig"] for p in pos)
|
||||
sa = sum(p["betrag"] for p in pos)
|
||||
air = [p for p in pos if p.get("typ") == regeln.AIRTIME]
|
||||
self.status.set("pos", f" {len(pos)} Positionen")
|
||||
self.status.set("orig", f" Provision: {fmt(so)}")
|
||||
self.status.set("ausz", f" Auszahlung: {fmt(sa)}")
|
||||
self._kennzahl("pos", str(len(pos)), name)
|
||||
self._kennzahl("orig", fmt(so), "laut Anbieter")
|
||||
self._kennzahl("ausz", fmt(sa), f"{round(sa * 100 / so)} % im Schnitt" if so > 0 else "")
|
||||
self._kennzahl("air", fmt(sum(p["betrag"] for p in air)),
|
||||
(f"{len(air)} Posten · eigene Excel-Datei" if any(p["betrag"] for p in air) else f"{len(air)} Posten")
|
||||
if air else "keine Airtime")
|
||||
self.l_auswahl.configure(text="")
|
||||
|
||||
def _kennzahl(self, key: str, wert: str, info: str):
|
||||
self.kpi[key][0].configure(text=wert)
|
||||
self.kpi[key][1].configure(text=info)
|
||||
|
||||
def _status_auswahl(self):
|
||||
pids = self._ausgewaehlte_pids()
|
||||
if len(pids) > 1 or (pids and any(i.startswith("g") for i in self.ptree.selection())):
|
||||
ps = [p for p in self.positionen if p["id"] in set(pids)]
|
||||
self.l_auswahl.configure(text=f"{len(ps)} ausgewählt · {fmt(sum(p['betrag'] for p in ps))}")
|
||||
self.status.set("pos", f" {len(ps)} ausgewählt")
|
||||
self.status.set("orig", f" Provision: {fmt(sum(p['betrag_orig'] for p in ps))}")
|
||||
self.status.set("ausz", f" Auszahlung: {fmt(sum(p['betrag'] for p in ps))}")
|
||||
@@ -1015,15 +1153,22 @@ class App:
|
||||
res = dlg.EinstellungenDialog(self.root, self.settings, str(self.db.path)).zeigen()
|
||||
if not res:
|
||||
return
|
||||
if res["design"] != w.DESIGN:
|
||||
self.settings["design"] = res["design"]
|
||||
config.save(self.settings)
|
||||
w.info(self.root, "Einstellungen", "Das neue Design wird beim nächsten Programmstart wirksam.")
|
||||
if res["pfad"] and Path(res["pfad"]) != self.db.path:
|
||||
try:
|
||||
self.db_wechseln(res["pfad"])
|
||||
except Exception as exc:
|
||||
w.fehler(self.root, "Einstellungen", f"Datenbank konnte nicht geöffnet werden:\n{exc}")
|
||||
if res["design"] != w.DESIGN:
|
||||
self.design_wechseln(res["design"])
|
||||
|
||||
def design_umschalten(self):
|
||||
self.design_wechseln("modern" if w.DUNKEL else "dunkel")
|
||||
|
||||
def design_wechseln(self, design: str):
|
||||
"""Fenster mit dem neuen Design neu aufbauen (main() startet es nach dem Schließen wieder)."""
|
||||
self.settings["design"] = design
|
||||
self.neustart = True
|
||||
self.root.after_idle(self.beenden)
|
||||
|
||||
def hilfe(self):
|
||||
w.info(self.root, "Kurzanleitung",
|
||||
@@ -1082,7 +1227,7 @@ class App:
|
||||
"(freenet-Unterhändlernummer, Vodafone-VO-Nr. oder Kundenname in GROSSBUCHSTABEN)."),
|
||||
(tb["Neu"], "Neue Abrechnung",
|
||||
"Für jeden Abrechnungszeitraum – meist ein Monat – legen Sie eine neue Abrechnung an. "
|
||||
"Mit „Öffnen“ holen Sie frühere Abrechnungen wieder hervor."),
|
||||
"Frühere Abrechnungen öffnen Sie mit einem Klick in der Liste darunter."),
|
||||
(tb["Import"], "Dateien importieren",
|
||||
"Wählen Sie die CSV- und Excel-Dateien von freenet und Vodafone – gerne mehrere auf einmal. "
|
||||
"Das Programm erkennt das Format selbst und warnt bei doppelt importierten Dateien."),
|
||||
@@ -1090,13 +1235,16 @@ class App:
|
||||
"Links sehen Sie jeden Händler mit Anzahl der Positionen und Auszahlungssumme.\n\n"
|
||||
"„(Nicht zugeordnet)“ ist alles, was keinem Unterhändler gehört – meist Ihre eigene Filiale. "
|
||||
"Das wird nicht exportiert."),
|
||||
(self.pills["alle"], "Filter",
|
||||
"Mit diesen Knöpfen zeigen Sie nur bestimmte Positionen: nicht zugeordnete, von Hand geänderte, "
|
||||
"doppelte oder bereits abgerechnete."),
|
||||
(self.f_p, "Positionen prüfen",
|
||||
"Rechts stehen die Positionen des gewählten Händlers, gruppiert nach Provisionsart.\n\n"
|
||||
"- Doppelklick auf eine Position: Betrag frei ändern\n"
|
||||
"- Rechtsklick: Auszahlung 0 €, Händler zuweisen, ...\n"
|
||||
"- Blau = von Hand geändert, Rot = durch Prozent-Regel geändert"),
|
||||
(self.b_prozent, "Auszahlung in Prozent",
|
||||
"Graue Gruppenzeile (Provisionsart) markieren und hier klicken – z. B. 80 % für den Sonderbonus. "
|
||||
"Gruppenzeile (Provisionsart) markieren und hier klicken – z. B. 80 % für den Sonderbonus. "
|
||||
"Mit „Als Standard merken“ gilt das auch in den nächsten Monaten automatisch."),
|
||||
(tb["Export"], "Exportieren",
|
||||
"Zum Schluss erstellt „Export“ für jeden Händler eine Excel-Datei im gewohnten Abrechnungsformat "
|
||||
@@ -1106,8 +1254,9 @@ class App:
|
||||
"Provision laut Anbieter und Ihre Auszahlung."),
|
||||
(self._suchrahmen, "Suchen",
|
||||
"Rufnummer, Name oder Tarif eingeben – die Liste filtert sofort. Tastenkürzel: Strg+F."),
|
||||
(self.menueleiste.knoepfe["?"], "Das war's!",
|
||||
"Diesen Rundgang, den Einrichtungs-Assistenten und die Tipps finden Sie jederzeit hier im Menü „?“.\n\n"
|
||||
(self.menueleiste.knoepfe[self.hilfe_menue], "Das war's!",
|
||||
"Diesen Rundgang, den Einrichtungs-Assistenten und die Tipps finden Sie jederzeit hier im Menü "
|
||||
f"„{self.hilfe_menue}“.\n\n"
|
||||
f"Viel Erfolg mit dem {w.APP_NAME}!"),
|
||||
]
|
||||
self._rundgang = ass.Rundgang(self, schritte)
|
||||
@@ -1211,11 +1360,16 @@ def main():
|
||||
ctypes.windll.shcore.SetProcessDpiAwareness(1)
|
||||
except Exception:
|
||||
pass
|
||||
root = tk.Tk(className="Provisionsmanager95")
|
||||
app = App(root)
|
||||
if len(sys.argv) > 1:
|
||||
root.after(200, lambda: app.importieren([a for a in sys.argv[1:] if Path(a).exists()]))
|
||||
root.mainloop()
|
||||
dateien = [a for a in sys.argv[1:] if Path(a).exists()]
|
||||
while True:
|
||||
root = tk.Tk(className="Provisionsmanager95")
|
||||
app = App(root)
|
||||
if dateien:
|
||||
root.after(200, lambda: app.importieren(dateien))
|
||||
dateien = []
|
||||
root.mainloop()
|
||||
if not app.neustart: # sonst: Design wurde umgeschaltet – Fenster neu aufbauen
|
||||
break
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in new issue
Block a user