Modernes Design (Standard, Win95 als Legacy), Zeitraum abrechnen, Abrechnung erledigt, Linux-Build
- Design: „modern“ (hell, flach, wie Windows 11, normaler Windows-Rahmen) ist Standard; „win95“ bleibt unter Datei > Einstellungen wählbar (Einstellung „design“). - Export: Zeitraum von–bis (nach Aktivierungsdatum oder Abrechnungszeitraum des Anbieters), exportierte Positionen werden als abgerechnet gekennzeichnet (Betrag/Händler eingefroren) und per Fingerabdruck auch in späteren Dateien/Abrechnungen nicht noch einmal abgerechnet. - Datei > Abrechnung erledigt / wieder öffnen: erledigte Abrechnungen sind gegen Änderungen gesperrt. - DB-Schema v4 (Migration inkl. Fingerabdrücke für alte Positionen). - Linux-Build (python:3.12-bookworm) als zweiter Pipeline-Job mit Tests und Selbsttest; Release-Upload für beide Jobs über build/release_hochladen.sh. - Behoben: normaler Windows-Rahmen stürzte beim Start ab (Hauptrahmen.nativ fehlte). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
97c73b82c7
commit
851fd6600a
17 files changed
+829
-80
No files matched your search
+51
-11
@@ -6,7 +6,8 @@ from tkinter import filedialog, ttk
|
||||
|
||||
from provtool import APP_NAME, APP_ORG, __version__
|
||||
from provtool import regeln
|
||||
from provtool.db import CAPS_ANBIETER, CAPS_WERT, Datenbank, Haendler
|
||||
from provtool import zeitraum as zr
|
||||
from provtool.db import CAPS_ANBIETER, CAPS_WERT, AbrechnungErledigt, Datenbank, Haendler
|
||||
from provtool.gui import win95 as w
|
||||
from provtool.importer import ANBIETER
|
||||
from provtool.money import fmt, fmt_percent, parse_cent, parse_percent
|
||||
@@ -561,9 +562,9 @@ class AbrechnungenDialog(w.Dialog):
|
||||
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),
|
||||
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),
|
||||
("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")
|
||||
@@ -581,8 +582,9 @@ class AbrechnungenDialog(w.Dialog):
|
||||
def _laden(self, sel=None):
|
||||
self.tree.delete(*self.tree.get_children())
|
||||
for a in self.db.abrechnungen():
|
||||
status = "erledigt" if a["erledigt"] else (f"{a['abgerechnet']} abgerechnet" if a["abgerechnet"] else "offen")
|
||||
self.tree.insert("", "end", iid=str(a["id"]),
|
||||
values=(a["titel"], a["geaendert"][:16], a["anzahl"], a["dateien"] or ""))
|
||||
values=(a["titel"], status, 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))
|
||||
@@ -655,7 +657,11 @@ class DateienDialog(w.Dialog):
|
||||
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]))
|
||||
try:
|
||||
self.db.datei_entfernen(int(s[0]))
|
||||
except AbrechnungErledigt as exc:
|
||||
w.info(self, "Datei entfernen", str(exc))
|
||||
return
|
||||
self.geaendert = True
|
||||
self._laden()
|
||||
|
||||
@@ -900,8 +906,32 @@ class ExportDialog(w.Dialog):
|
||||
"eigene Datei „... - Airtime - ...xlsx“.", fg=w.SHADOW, justify="left", anchor="w").grid(
|
||||
row=7, column=0, columnspan=3, sticky="w", pady=(4, 0))
|
||||
g.columnconfigure(1, weight=1)
|
||||
|
||||
z = w.gruppe(f, "Zeitraum und Abrechnung")
|
||||
z.pack(fill="x", pady=(8, 0))
|
||||
zf = tk.Frame(z, bg=w.FACE)
|
||||
zf.pack(fill="x")
|
||||
tk.Label(zf, text="Nur von:").pack(side="left")
|
||||
self.v_von, self.v_bis = tk.StringVar(), tk.StringVar()
|
||||
tk.Entry(zf, textvariable=self.v_von, width=11).pack(side="left", padx=4)
|
||||
tk.Label(zf, text="bis:").pack(side="left")
|
||||
tk.Entry(zf, textvariable=self.v_bis, width=11).pack(side="left", padx=4)
|
||||
w.knopf(zf, "Vormonat", self._vormonat).pack(side="left", padx=(4, 0))
|
||||
self.v_basis = tk.StringVar(value=zr.BASEN[settings.get("export_basis", zr.AKTIVIERUNG)])
|
||||
ttk.Combobox(z, textvariable=self.v_basis, values=list(zr.BASEN.values()), state="readonly",
|
||||
width=40).pack(anchor="w", pady=(4, 0))
|
||||
tk.Label(z, text="(leer = alle offenen Positionen; Datum als TT.MM.JJJJ)", fg=w.SHADOW, anchor="w").pack(
|
||||
fill="x")
|
||||
self.v_kennz = tk.BooleanVar(value=True)
|
||||
tk.Checkbutton(z, text="Exportierte Positionen als abgerechnet kennzeichnen (werden dann nicht noch "
|
||||
"einmal abgerechnet)", variable=self.v_kennz, anchor="w").pack(fill="x", pady=(4, 0))
|
||||
self.knopfleiste([("Exportieren", self._ok), ("Abbrechen", self.abbrechen)]).pack(pady=(0, 10))
|
||||
|
||||
def _vormonat(self):
|
||||
von, bis = zr.vormonat()
|
||||
self.v_von.set(von.strftime("%d.%m.%Y"))
|
||||
self.v_bis.set(bis.strftime("%d.%m.%Y"))
|
||||
|
||||
def _klick(self, e):
|
||||
if self.tree.identify_column(e.x) == "#0":
|
||||
iid = self.tree.identify_row(e.y)
|
||||
@@ -928,6 +958,12 @@ class ExportDialog(w.Dialog):
|
||||
if not ids:
|
||||
w.warnung(self, "Export", "Bitte mindestens einen Händler anhaken.")
|
||||
return
|
||||
try:
|
||||
von, bis = zr.datum_lesen(self.v_von.get()), zr.datum_lesen(self.v_bis.get())
|
||||
except ValueError as exc:
|
||||
w.warnung(self, "Zeitraum", str(exc))
|
||||
return
|
||||
basis = next(k for k, v in zr.BASEN.items() if v == self.v_basis.get())
|
||||
if not self.v_ordner.get().strip():
|
||||
self._ordner()
|
||||
if not self.v_ordner.get().strip():
|
||||
@@ -938,7 +974,10 @@ class ExportDialog(w.Dialog):
|
||||
self.settings.pop("export_pro_anbieter", None)
|
||||
self.result = dict(ids=ids, ordner=self.v_ordner.get().strip(), titel=self.v_titel.get().strip(),
|
||||
pro_anbieter=self.v_anb.get(), null_weglassen=self.v_null.get(),
|
||||
kopfzeile=self.v_kopf.get(), summe=self.v_sum.get(), oeffnen=self.v_auf.get())
|
||||
kopfzeile=self.v_kopf.get(), summe=self.v_sum.get(), oeffnen=self.v_auf.get(),
|
||||
zeitraum=zr.Zeitraum(von, bis, basis) if (von or bis) else None,
|
||||
kennzeichnen=self.v_kennz.get())
|
||||
self.settings["export_basis"] = basis
|
||||
self.destroy()
|
||||
|
||||
|
||||
@@ -960,9 +999,10 @@ class EinstellungenDialog(w.Dialog):
|
||||
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")
|
||||
self.v_design = tk.StringVar(value=settings.get("design", "modern"))
|
||||
tk.Radiobutton(g2, text="Modern (wie Windows 11)", variable=self.v_design, value="modern").pack(anchor="w")
|
||||
tk.Radiobutton(g2, text="Klassisch (Windows 95, Legacy)", variable=self.v_design, value="win95").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))
|
||||
|
||||
@@ -974,7 +1014,7 @@ class EinstellungenDialog(w.Dialog):
|
||||
self.v.set(p)
|
||||
|
||||
def _ok(self):
|
||||
self.result = dict(pfad=self.v.get().strip(), klassisch=self.v_klassisch.get())
|
||||
self.result = dict(pfad=self.v.get().strip(), design=self.v_design.get())
|
||||
self.destroy()
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user