diff --git a/provtool/gui/app.py b/provtool/gui/app.py index 804f5c1..10f97df 100644 --- a/provtool/gui/app.py +++ b/provtool/gui/app.py @@ -195,8 +195,23 @@ class App: bd=0, opaqueresize=False, sashcursor="sb_h_double_arrow") pw.pack(fill="both", expand=True, padx=6, pady=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) + w.auto_breite(self.atree, {"#0": 1}) + r.pack(fill="both", expand=True) + self.atree.bind("<>", self._abrechnung_gewaehlt) + # --- Händler-Fenster - self.f_h = w.Fenster(pw, "Händler", self.icons("haendler")) + self.f_h = w.Fenster(links, "Händler", self.icons("haendler")) + 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") @@ -211,7 +226,7 @@ class App: r.pack(fill="both", expand=True) self.htree.bind("<>", self._haendler_gewaehlt) self.htree.bind("", lambda e: self._haendler_doppelklick()) - pw.add(self.f_h, minsize=240, width=310) + pw.add(links, minsize=240, width=310) # --- Positionen-Fenster self.f_p = w.Fenster(pw, "Positionen", self.icons("neu")) @@ -284,9 +299,26 @@ class App: self.positionen = [] self.rahmen.set_titel(w.APP_NAME) self.status.set("info", " Keine Abrechnung geöffnet – Datei > Neue Abrechnung oder Import") + self._abrechnungsliste() self._haendlerliste(behalte_auswahl) self._tabelle() + 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,)) + if self.aid and t.exists(str(self.aid)): + t.selection_set(str(self.aid)) + t.see(str(self.aid)) + + def _abrechnung_gewaehlt(self, _e=None): + sel = self.atree.selection() + if sel and int(sel[0]) != self.aid: + self.ansicht = ALLE + self.oeffnen(int(sel[0])) + def _suche_planen(self): """Suche erst kurz nach dem letzten Tastendruck ausführen (flüssiges Tippen).""" if self._suche_geplant is not None: @@ -466,7 +498,7 @@ class App: else: self._aktualisieren() - def importieren(self, dateien: list[str] | None = None): + def importieren(self, dateien: list[str] | None = None, ziel: int | None = None): if not dateien: dateien = list(filedialog.askopenfilenames( parent=self.root, title="Provisionsabrechnungen importieren", @@ -477,7 +509,22 @@ class App: if not dateien: return self.settings["import_ordner"] = str(Path(dateien[0]).parent) - if not self._brauche_abrechnung() or self._gesperrt(): + if ziel is None: # jedes Mal fragen, in welche Abrechnung + if not self.db.abrechnungen(): + if not self.neu(): + return + else: + res = dlg.AbrechnungenDialog(self.root, self.db, self.aid, fuer_import=True).zeigen() + if not res: + return + if res[0] == "neu": + if not self.neu(): + return + else: + self.aid, self.ansicht = res[1], ALLE + else: + self.aid = ziel + if self._gesperrt(): return bericht = [] self.root.configure(cursor="watch") @@ -1018,7 +1065,7 @@ class App: self.ansicht = ALLE self._aktualisieren() if res["dateien"]: - self.importieren(res["dateien"]) + self.importieren(res["dateien"], ziel=self.aid) if res["rundgang"]: self.root.after(200, self.rundgang) diff --git a/provtool/gui/dialogs.py b/provtool/gui/dialogs.py index 7b6e158..3f7e48b 100644 --- a/provtool/gui/dialogs.py +++ b/provtool/gui/dialogs.py @@ -558,10 +558,13 @@ class ProvisionsartenDialog(w.Dialog): # =========================================================================== Abrechnungen class AbrechnungenDialog(w.Dialog): - def __init__(self, master, db: Datenbank, aktuelle_id: int | None): - super().__init__(master, "Abrechnung öffnen", resizable=True) + def __init__(self, master, db: Datenbank, aktuelle_id: int | None, fuer_import: bool = False): + """fuer_import: Ziel eines Imports wählen (Ergebnis ("oeffnen", id) oder ("neu",)).""" + super().__init__(master, "Import – in welche Abrechnung?" if fuer_import else "Abrechnung öffnen", + resizable=True) self.db = db - tk.Label(self, text="Gespeicherte Abrechnungen:", anchor="w").pack(fill="x", padx=10, pady=(10, 2)) + tk.Label(self, text="In welche Abrechnung sollen die Dateien importiert werden?" if fuer_import + else "Gespeicherte Abrechnungen:", anchor="w").pack(fill="x", padx=10, pady=(10, 2)) r, self.tree = w.scroll_tree(self, columns=("titel", "status", "geaendert", "anzahl", "dateien"), show="headings", height=12, selectmode="browse") for c, t, b in (("titel", "Bezeichnung", 180), ("status", "Status", 110), ("geaendert", "Zuletzt geändert", 120), @@ -572,9 +575,13 @@ class AbrechnungenDialog(w.Dialog): 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") + if fuer_import: + w.knopf(k, "Hierhin importieren", self._oeffnen, default="active").pack(side="left") + w.knopf(k, "Neue Abrechnung...", self._neu).pack(side="left", padx=6) + else: + 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) @@ -600,6 +607,10 @@ class AbrechnungenDialog(w.Dialog): self.result = ("oeffnen", self._sel()) self.destroy() + def _neu(self): + self.result = ("neu",) + self.destroy() + def _umbenennen(self): aid = self._sel() if not aid: diff --git a/tests/test_gui.py b/tests/test_gui.py index 47c495d..08c4619 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -1129,3 +1129,35 @@ def _alle(widget): for kind in widget.winfo_children(): yield kind yield from _alle(kind) + + +def test_abrechnungen_links_und_import_fragt_nach_ziel(lauf_modern, home, monkeypatch): + from provtool.gui import win95 as w + from provtool.gui.dialogs import AbrechnungenDialog, ImportBericht + monkeypatch.setattr(w, "frage", lambda *a, **k: True) # „bereits importiert – trotzdem?“ (Testdatei ist schon drin) + lauf = lauf_modern + app = lauf.app + alt = app.aid + neu = app.db.abrechnung_neu("November 2026") + app._aktualisieren() + assert set(app.atree.get_children()) == {str(alt), str(neu)}, "Abrechnungen fehlen links" + app.atree.selection_set(str(neu)) + lauf.root.update() + assert app.aid == neu and not app.positionen, "Klick links öffnet die Abrechnung nicht" + app.atree.selection_set(str(alt)) + lauf.root.update() + + def ziel_waehlen(): + d = lauf.dialog(AbrechnungenDialog) + d.tree.selection_set(str(neu)) + d._oeffnen() + + lauf.ablauf([ + (IMMER, lambda: app.importieren([str(home / "freenet.csv")]), "Import"), + (lambda: lauf.dialog(AbrechnungenDialog) is not None, ziel_waehlen, "Ziel: November"), + (lambda: lauf.dialog(ImportBericht) is not None, lambda: lauf.dialog(ImportBericht).abbrechen(), "Bericht zu"), + ]) + assert not lauf.uebrig, lauf.diagnose() + assert app.aid == neu and len(app.positionen) == 3, "Import nicht in der gewählten Abrechnung" + assert app.atree.selection() == (str(neu),) + lauf.pruefen()