Win95-Titelleiste und -Menüleiste, Einrichtungs-Assistent, Rundgang, Tipp des Tages
Windows-Build / windows-exe (push) Successful in 1m43s
Windows-Build / windows-exe (push) Successful in 1m43s
- selbst gezeichneter Fensterrahmen (verschieben, maximieren, minimieren, Größe ändern), abschaltbar - Dialoge mit Win95-Titelleiste; vorherige Modalsperre wird nach Rückfragen wiederhergestellt - Assistent: Speicherort, Unterhändler (Nummern aus Abrechnung vorschlagen), erste Abrechnung - Rundgang mit Hinweis-Sprechblasen, Tipp des Tages Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6f860746ca
commit
6b520bce35
7 files changed
+1128
-43
No files matched your search
+119
-40
@@ -15,7 +15,9 @@ from tkinter import filedialog
|
||||
from provtool import APP_NAME, config
|
||||
from provtool import export as exp
|
||||
from provtool.db import UNZUGEORDNET, Datenbank
|
||||
from provtool.gui import assistent as ass
|
||||
from provtool.gui import dialogs as dlg
|
||||
from provtool.gui import rahmen as rh
|
||||
from provtool.gui import win95 as w
|
||||
from provtool.importer import ANBIETER, ImportFehler, lies_datei
|
||||
from provtool.money import fmt, fmt_percent, parse_cent
|
||||
@@ -67,30 +69,40 @@ class App:
|
||||
self.icons = w.Icons(root)
|
||||
root._app_icon = self.icons("app", 2) # type: ignore[attr-defined]
|
||||
root.iconphoto(True, self.icons("app", 2), self.icons("app", 1))
|
||||
root.title(APP_NAME)
|
||||
root.geometry(self.settings.get("fenster", "1200x720"))
|
||||
root.minsize(900, 520)
|
||||
root.protocol("WM_DELETE_WINDOW", self.beenden)
|
||||
root.report_callback_exception = self._fehler_handler
|
||||
klassisch = bool(self.settings.get("klassischer_rahmen", True))
|
||||
w.KLASSISCH = klassisch
|
||||
self.rahmen = rh.Hauptrahmen(root, APP_NAME, self.icons("app"), self.beenden, klassisch=klassisch)
|
||||
self.rahmen.set_titel(APP_NAME)
|
||||
self.inhalt = self.rahmen.inhalt
|
||||
|
||||
self._menue()
|
||||
self._toolbar()
|
||||
self._body()
|
||||
self.status = w.Statusleiste(root, [("info", 0), ("pos", 16), ("orig", 22), ("ausz", 22)])
|
||||
self.status = w.Statusleiste(self.inhalt, [("info", 0), ("pos", 16), ("orig", 22), ("ausz", 22)])
|
||||
griff = self.rahmen.griff(self.status)
|
||||
if griff:
|
||||
griff.pack(side="right", anchor="se")
|
||||
self.status.pack(side="bottom", fill="x")
|
||||
self._tasten()
|
||||
self.rahmen.geometrie_setzen(self.settings.get("fenster"), self.settings.get("maximiert", False))
|
||||
|
||||
abr = self.db.abrechnungen()
|
||||
if abr:
|
||||
self.oeffnen(abr[0]["id"])
|
||||
else:
|
||||
self._aktualisieren()
|
||||
root.after(300, self._willkommen)
|
||||
if not self.settings.get("assistent_erledigt") and not self.db.haendler_liste():
|
||||
root.after(400, self.assistent)
|
||||
elif self.settings.get("tipps_beim_start", True):
|
||||
root.after(500, self.tipp_des_tages)
|
||||
|
||||
# ================================================================= Aufbau
|
||||
def _menue(self):
|
||||
m = tk.Menu(self.root, tearoff=0)
|
||||
d = tk.Menu(m, tearoff=0)
|
||||
self.menueleiste = rh.Menueleiste(self.inhalt, self.root)
|
||||
self.menueleiste.pack(side="top", fill="x")
|
||||
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)
|
||||
d.add_separator()
|
||||
@@ -103,32 +115,32 @@ class App:
|
||||
d.add_command(label="Einstellungen…", command=self.einstellungen)
|
||||
d.add_separator()
|
||||
d.add_command(label="Beenden", accelerator="Alt+F4", command=self.beenden)
|
||||
m.add_cascade(label="Datei", menu=d, underline=0)
|
||||
|
||||
b = tk.Menu(m, tearoff=0)
|
||||
b = self.menueleiste.menue("Bearbeiten")
|
||||
b.add_command(label="Betrag ändern…", accelerator="F2", command=self.betrag_bearbeiten)
|
||||
b.add_command(label="Auszahlung in Prozent…", accelerator="F4", command=self.prozent)
|
||||
b.add_command(label="Auf Regel zurücksetzen", command=self.zuruecksetzen)
|
||||
b.add_separator()
|
||||
b.add_command(label="Alle Beträge neu berechnen", command=self.neu_berechnen)
|
||||
m.add_cascade(label="Bearbeiten", menu=b, underline=0)
|
||||
|
||||
h = tk.Menu(m, tearoff=0)
|
||||
h = self.menueleiste.menue("Händler")
|
||||
h.add_command(label="Händler verwalten…", accelerator="Strg+H", command=self.haendler)
|
||||
h.add_command(label="Kennung des Eintrags zuordnen…", command=self.kennung_zuordnen_auswahl)
|
||||
h.add_command(label="Auswahl fest einem Händler zuweisen…", command=self.haendler_zuweisen)
|
||||
h.add_command(label="Auswahl automatisch zuordnen", command=lambda: self.haendler_zuweisen(automatisch=True))
|
||||
m.add_cascade(label="Händler", menu=h, underline=0)
|
||||
|
||||
hi = tk.Menu(m, tearoff=0)
|
||||
hi = self.menueleiste.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)
|
||||
hi.add_separator()
|
||||
hi.add_command(label="Einrichtungs-Assistent…", command=self.assistent)
|
||||
hi.add_separator()
|
||||
hi.add_command(label=f"Info über {APP_NAME}…", command=lambda: dlg.InfoDialog(self.root).zeigen())
|
||||
m.add_cascade(label="?", menu=hi)
|
||||
self.root.configure(menu=m)
|
||||
|
||||
def _toolbar(self):
|
||||
tb = tk.Frame(self.root, bg=w.FACE, bd=1, relief="raised")
|
||||
tb = tk.Frame(self.inhalt, bg=w.FACE, bd=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)"),
|
||||
@@ -144,7 +156,8 @@ class App:
|
||||
if text is None:
|
||||
w.trenner(tb).pack(side="left", fill="y", padx=4, pady=3)
|
||||
continue
|
||||
w.ToolbarButton(tb, text, self.icons(icon), cmd, tip).pack(side="left", padx=1, pady=1)
|
||||
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)
|
||||
@@ -153,9 +166,10 @@ class App:
|
||||
e.pack(side="left", padx=4)
|
||||
self.v_suche.trace_add("write", lambda *_: self._tabelle())
|
||||
self._suchfeld = e
|
||||
self._suchrahmen = sf
|
||||
|
||||
def _body(self):
|
||||
desk = tk.Frame(self.root, bg=w.TEAL)
|
||||
desk = tk.Frame(self.inhalt, bg=w.TEAL)
|
||||
desk.pack(side="top", fill="both", expand=True)
|
||||
pw = tk.PanedWindow(desk, orient="horizontal", bg=w.TEAL, sashwidth=6, bd=0)
|
||||
pw.pack(fill="both", expand=True, padx=6, pady=6)
|
||||
@@ -202,7 +216,8 @@ class App:
|
||||
|
||||
leiste = tk.Frame(self.f_p.inhalt, bg=w.FACE)
|
||||
leiste.pack(fill="x", pady=(4, 0))
|
||||
w.knopf(leiste, "Prozent…", self.prozent, self.icons("prozent")).pack(side="left")
|
||||
self.b_prozent = w.knopf(leiste, "Prozent…", self.prozent, 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, "Zurücksetzen", self.zuruecksetzen).pack(side="left")
|
||||
self.v_alle_auf = tk.BooleanVar(value=True)
|
||||
@@ -235,12 +250,12 @@ class App:
|
||||
if self.aid and self.db.abrechnung(self.aid):
|
||||
self.positionen = self.db.positionen(self.aid)
|
||||
titel = self.db.abrechnung(self.aid)["titel"]
|
||||
self.root.title(f"{APP_NAME} - [{titel}]")
|
||||
self.rahmen.set_titel(f"{APP_NAME} - [{titel}]")
|
||||
self.status.set("info", f" Abrechnung: {titel}")
|
||||
else:
|
||||
self.aid = None
|
||||
self.positionen = []
|
||||
self.root.title(APP_NAME)
|
||||
self.rahmen.set_titel(APP_NAME)
|
||||
self.status.set("info", " Keine Abrechnung geöffnet – Datei ▸ Neue Abrechnung oder Import")
|
||||
self._haendlerliste(behalte_auswahl)
|
||||
self._tabelle()
|
||||
@@ -746,22 +761,32 @@ class App:
|
||||
self.ansicht = ALLE
|
||||
self._aktualisieren()
|
||||
|
||||
def db_wechseln(self, pfad: str | None):
|
||||
"""Öffnet (oder legt an) eine andere Datenbank. pfad=None: Standard-Speicherort."""
|
||||
ziel = Path(pfad) if pfad else config.app_dir() / "provisionen.db"
|
||||
db = Datenbank(ziel) # wirft bei Fehler – alte DB bleibt dann offen
|
||||
self.db.close()
|
||||
self.db = db
|
||||
self.settings["db_pfad"] = str(ziel) if pfad else None
|
||||
config.save(self.settings)
|
||||
abr = self.db.abrechnungen()
|
||||
self.aid = abr[0]["id"] if abr else None
|
||||
self.ansicht = ALLE
|
||||
self._aktualisieren()
|
||||
|
||||
def einstellungen(self):
|
||||
neu = dlg.EinstellungenDialog(self.root, self.settings, str(self.db.path)).zeigen()
|
||||
if neu and Path(neu) != self.db.path:
|
||||
res = dlg.EinstellungenDialog(self.root, self.settings, str(self.db.path)).zeigen()
|
||||
if not res:
|
||||
return
|
||||
if res["klassisch"] != bool(self.settings.get("klassischer_rahmen", True)):
|
||||
self.settings["klassischer_rahmen"] = res["klassisch"]
|
||||
config.save(self.settings)
|
||||
w.info(self.root, "Einstellungen", "Die Änderung der Titelleiste wird beim nächsten Programmstart wirksam.")
|
||||
if res["pfad"] and Path(res["pfad"]) != self.db.path:
|
||||
try:
|
||||
db = Datenbank(neu)
|
||||
self.db_wechseln(res["pfad"])
|
||||
except Exception as exc:
|
||||
w.fehler(self.root, "Einstellungen", f"Datenbank konnte nicht geöffnet werden:\n{exc}")
|
||||
return
|
||||
self.db.close()
|
||||
self.db = db
|
||||
self.settings["db_pfad"] = neu
|
||||
config.save(self.settings)
|
||||
abr = self.db.abrechnungen()
|
||||
self.aid = abr[0]["id"] if abr else None
|
||||
self.ansicht = ALLE
|
||||
self._aktualisieren()
|
||||
|
||||
def hilfe(self):
|
||||
w.info(self.root, "Kurzanleitung",
|
||||
@@ -779,12 +804,65 @@ class App:
|
||||
"5. Exportieren (Strg+E): je Händler eine Excel-Datei im Abrechnungsformat.\n\n"
|
||||
"Alles wird automatisch in der Datenbank gespeichert.")
|
||||
|
||||
def _willkommen(self):
|
||||
if not self.db.haendler_liste():
|
||||
if w.frage(self.root, "Willkommen",
|
||||
f"Willkommen beim {APP_NAME}!\n\nEs sind noch keine Händler angelegt.\n"
|
||||
"Jetzt die Unterhändler mit ihren Kennungen anlegen?"):
|
||||
self.haendler()
|
||||
def assistent(self):
|
||||
res = ass.Einrichtungsassistent(self).zeigen()
|
||||
self.settings["assistent_erledigt"] = True
|
||||
config.save(self.settings)
|
||||
self._aktualisieren()
|
||||
if not res:
|
||||
return
|
||||
if res["titel"]:
|
||||
self.aid = self.db.abrechnung_neu(res["titel"])
|
||||
self.ansicht = ALLE
|
||||
self._aktualisieren()
|
||||
if res["dateien"]:
|
||||
self.importieren(res["dateien"])
|
||||
if res["rundgang"]:
|
||||
self.root.after(200, self.rundgang)
|
||||
|
||||
def rundgang(self):
|
||||
tb = self.tb
|
||||
schritte = [
|
||||
(self.rahmen.titelleiste or self.menueleiste, "Willkommen zum Rundgang!",
|
||||
"In ein paar Schritten sehen Sie, wo Sie was finden.\n\nWeiter geht es mit „Weiter >“ oder der "
|
||||
"Eingabetaste, beenden können Sie jederzeit mit Esc."),
|
||||
(tb["Händler"], "Händler",
|
||||
"Hier legen Sie Ihre Unterhändler an: Name, Auszahlung in % und woran ihre Positionen erkannt werden "
|
||||
"(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."),
|
||||
(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."),
|
||||
(self.f_h, "Händlerliste",
|
||||
"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.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. "
|
||||
"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."),
|
||||
(self.status, "Summen",
|
||||
"Unten sehen Sie immer die Summe der angezeigten bzw. markierten Positionen: "
|
||||
"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"
|
||||
"Viel Erfolg mit dem Provisions-Manager 95!"),
|
||||
]
|
||||
self._rundgang = ass.Rundgang(self, schritte)
|
||||
|
||||
def tipp_des_tages(self):
|
||||
ass.TippDesTages(self).zeigen()
|
||||
config.save(self.settings)
|
||||
|
||||
def _fehler_handler(self, exc, val, tb):
|
||||
text = "".join(traceback.format_exception(exc, val, tb))
|
||||
@@ -799,7 +877,8 @@ class App:
|
||||
|
||||
def beenden(self):
|
||||
try:
|
||||
self.settings["fenster"] = self.root.geometry()
|
||||
self.settings["fenster"] = self.rahmen.normale_geometrie()
|
||||
self.settings["maximiert"] = self.rahmen.maximiert
|
||||
config.save(self.settings)
|
||||
self.db.close()
|
||||
finally:
|
||||
|
||||
Reference in new issue
Block a user