Im Journal wird nichts mehr von Hand geloescht
Sein Einwand: 'wenn man in der beherbergungstool ein datensatz loescht, bleibt die in der sqlite - oder du patchst es raus, dass man das garnicht mehr loeschen kann'. Beides stimmt. Im gemeinsamen Nummernbuch BLEIBT die Nummer stehen, und das ist richtig: vergeben ist vergeben, die Rechnung liegt beim Gast. Das Loeschen im Journal gehoert dagegen weg - eine Zeile aus einer PDF kaeme beim naechsten Einlesen ohnehin zurueck, eine Zeile aus dem Excel-Altbestand (quelle='xlsx') gibt es dagegen nirgends sonst; die waere fuer immer weg, samt Uebernachtung im Amtsbericht. § 146 Abs. 4 AO verbietet Aenderungen, bei denen der urspruengliche Inhalt nicht mehr feststellbar ist. Raus sind: Papierkorb-Knopf, Entf-Taste, Menuepunkt und die Haken-Spalte, die nur dafuer da war. Damit ist das Monatsblatt reine Anzeige. Der Weg, der bleibt: die PDF aus dem Rechnungsordner nehmen - dann raeumt der Scan die Zeile von selbst weg, und nur dann, wenn das Laufwerk erreichbar ist (ein abgezogenes NAS darf die Buchhaltung nicht leeren). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYeAfLtccFrbU3MTj1MMTx
This commit is contained in:
parent
c230a27cff
commit
0218abaebe
3 changed files with 25 additions and 114 deletions
78
app.py
78
app.py
|
|
@ -102,7 +102,6 @@ class App(tk.Tk):
|
|||
|
||||
self.bind("<F5>", lambda _e: self.einlesen())
|
||||
self.bind("<Control-f>", lambda _e: self.suchfeld.focus_set())
|
||||
self.bind("<Delete>", lambda _e: self.buchung_loeschen())
|
||||
self.protocol("WM_DELETE_WINDOW", self._beenden)
|
||||
|
||||
self.aktualisieren()
|
||||
|
|
@ -137,7 +136,6 @@ class App(tk.Tk):
|
|||
daten.add_command(label="Rechnungen einlesen (F5)", command=self.einlesen)
|
||||
daten.add_command(label="Alle Rechnungen neu einlesen", command=lambda: self.einlesen(voll=True))
|
||||
daten.add_separator()
|
||||
daten.add_command(label="Markierte Buchung löschen (Entf)", command=self.buchung_loeschen)
|
||||
daten.add_separator()
|
||||
daten.add_command(label="Steuersatz ändern…", command=self.satz_aendern)
|
||||
daten.add_command(label="Firmendaten des Rechnungstools wählen…",
|
||||
|
|
@ -160,9 +158,11 @@ class App(tk.Tk):
|
|||
ttk.Button(leiste, text="Rechnungen einlesen", command=self.einlesen).pack(side="left")
|
||||
ttk.Button(leiste, text="PDF fürs Amt", command=self.bericht_monat).pack(side="left", padx=(6, 0))
|
||||
|
||||
self.papierkorb = ttk.Button(leiste, text="🗑 Löschen", state="disabled",
|
||||
command=self.angehakte_loeschen)
|
||||
self.papierkorb.pack(side="left", padx=(6, 0))
|
||||
# Kein Papierkorb mehr: im Journal wird nichts von Hand geloescht. Eine
|
||||
# Zeile aus einer PDF kaeme beim naechsten Einlesen ohnehin zurueck, eine
|
||||
# Zeile aus dem Excel-Altbestand waere fuer immer weg - samt Uebernachtung
|
||||
# im Amtsbericht (§ 146 Abs. 4 AO). Soll eine Buchung raus, wandert die PDF
|
||||
# aus dem Rechnungsordner; den Rest macht der Scan von selbst.
|
||||
|
||||
ttk.Label(leiste, text="Suche:").pack(side="left", padx=(20, 4))
|
||||
self.suchfeld = ttk.Entry(leiste, textvariable=self.suche, width=22)
|
||||
|
|
@ -175,7 +175,7 @@ class App(tk.Tk):
|
|||
self.blaetter.pack(fill="both", expand=True, padx=10, pady=(0, 6))
|
||||
self.monate = {}
|
||||
for m in range(1, 13):
|
||||
tab = MonatsTab(self.blaetter, m, bei_haken=self.papierkorb_auffrischen)
|
||||
tab = MonatsTab(self.blaetter, m)
|
||||
self.blaetter.add(tab, text=MONATE[m - 1])
|
||||
self.monate[m] = tab
|
||||
self.blaetter.select(min(date.today().month, 12) - 1)
|
||||
|
|
@ -262,7 +262,6 @@ class App(tk.Tk):
|
|||
|
||||
self.jahr_summe.setzen(summiere(sichtbar), sichtbar)
|
||||
self.jahr_wahl.config(values=self._jahre())
|
||||
self.papierkorb_auffrischen()
|
||||
self.ordner_anzeige.config(
|
||||
text=f"Rechnungen: {self.pdf_ordner}" if self.pdf_ordner
|
||||
else "kein Rechnungs-Ordner gewählt (Datei → Rechnungs-Ordner wählen…)")
|
||||
|
|
@ -588,71 +587,6 @@ class App(tk.Tk):
|
|||
"Nicht eingelesen",
|
||||
f"{len(fehler)} Datei(en) sind keine lesbare Rechnung:\n\n{namen}",
|
||||
parent=self)
|
||||
|
||||
# Im Journal wird NICHTS von Hand geaendert - weder angelegt noch bearbeitet.
|
||||
# Jede Zeile gehoert zu einer Rechnung aus dem Rechnungstool; wer hier Betrag
|
||||
# oder Nummer verstellt, meldet dem Amt etwas anderes, als auf dem Beleg
|
||||
# steht, und beim naechsten Einlesen kaeme die Aenderung ohnehin zurueck.
|
||||
# Stimmt eine Rechnung nicht, wird sie im Rechnungstool storniert oder
|
||||
# berichtigt. Loeschen bleibt moeglich (Entf / Papierkorb) - es entfernt nur
|
||||
# die Zeile aus dem Zwischenspeicher, die PDF bleibt liegen.
|
||||
def _markierte(self):
|
||||
return self.monate[self._monat].markierte()
|
||||
|
||||
def buchung_loeschen(self):
|
||||
b = self._markierte()
|
||||
if not b:
|
||||
return
|
||||
if not messagebox.askyesno(
|
||||
"Buchung löschen",
|
||||
f"Rechnung {b.voll_nummer} ({b.nachname}) aus dem Journal löschen?\n\n"
|
||||
"Die PDF-Datei bleibt erhalten – beim nächsten Einlesen "
|
||||
"taucht die Buchung wieder auf.", parent=self):
|
||||
return
|
||||
self.journal.loeschen(b.id)
|
||||
self.aktualisieren()
|
||||
self.melde(f"Buchung {b.voll_nummer} gelöscht")
|
||||
|
||||
# ---- Anhaken + Papierkorb ----------------------------------------------
|
||||
def _angehakte(self):
|
||||
"""Alle angehakten Buchungen – über alle Monate hinweg."""
|
||||
return [b for m in range(1, 13) for b in self.monate[m].angehakte()]
|
||||
|
||||
def papierkorb_auffrischen(self):
|
||||
anzahl = len(self._angehakte())
|
||||
self.papierkorb.config(
|
||||
text=f"🗑 Löschen ({anzahl})" if anzahl else "🗑 Löschen",
|
||||
state="normal" if anzahl else "disabled")
|
||||
|
||||
def angehakte_loeschen(self):
|
||||
angehakt = self._angehakte()
|
||||
if not angehakt:
|
||||
return
|
||||
|
||||
aus_pdf = [b for b in angehakt if b.quelle == "pdf"]
|
||||
namen = "\n".join(f"• {b.datum.strftime('%d.%m.%Y')} {b.voll_nummer} "
|
||||
f"{b.nachname or 'ohne Namen'}" for b in angehakt[:15])
|
||||
if len(angehakt) > 15:
|
||||
namen += f"\n… und {len(angehakt) - 15} weitere"
|
||||
hinweis = ""
|
||||
if aus_pdf:
|
||||
hinweis = (f"\n\n{len(aus_pdf)} davon stammen aus einer Rechnungs-PDF – die Dateien "
|
||||
"bleiben liegen und tauchen beim nächsten Einlesen wieder auf.")
|
||||
|
||||
if not messagebox.askyesno(
|
||||
"Angehakte Buchungen löschen",
|
||||
f"{len(angehakt)} Buchung(en) aus dem Journal löschen?\n\n{namen}{hinweis}",
|
||||
parent=self):
|
||||
return
|
||||
|
||||
for b in angehakt:
|
||||
self.journal.loeschen(b.id)
|
||||
for m in range(1, 13):
|
||||
self.monate[m].haken_loesen()
|
||||
self.aktualisieren()
|
||||
self.papierkorb_auffrischen()
|
||||
self.melde(f"{len(angehakt)} Buchung(en) gelöscht")
|
||||
|
||||
def satz_aendern(self):
|
||||
fenster = tk.Toplevel(self)
|
||||
fenster.title("Steuersatz")
|
||||
|
|
|
|||
13
gemeinsam.py
13
gemeinsam.py
|
|
@ -209,6 +209,19 @@ class Nummernbuch:
|
|||
zeilen = con.execute("SELECT nummer FROM nummern")
|
||||
return {z["nummer"] for z in zeilen}
|
||||
|
||||
def hoechster_zaehler(self, jahr):
|
||||
"""Groesster Zaehler dieses Jahres im Buch (0, wenn nichts drinsteht).
|
||||
|
||||
Aus '2026-442' wird 442. Nummern anderer Reihen (Angebote 2026-A001)
|
||||
zaehlen NICHT mit - die haben mit der Rechnungsnummer nichts zu tun.
|
||||
"""
|
||||
hoch = 0
|
||||
for nummer in self.nummern(jahr):
|
||||
teil = str(nummer).split("-")[-1]
|
||||
if teil.isdigit():
|
||||
hoch = max(hoch, int(teil))
|
||||
return hoch
|
||||
|
||||
def eintrag(self, nummer):
|
||||
with self._verbindung() as con:
|
||||
z = con.execute("SELECT * FROM nummern WHERE nummer=?", (str(nummer),)).fetchone()
|
||||
|
|
|
|||
48
gui_monat.py
48
gui_monat.py
|
|
@ -9,11 +9,7 @@ from tkinter import ttk, font as tkfont
|
|||
|
||||
from modell import summiere, eur, eur_kurz
|
||||
|
||||
HAKEN_AN = "☑"
|
||||
HAKEN_AUS = "☐"
|
||||
|
||||
SPALTEN = [
|
||||
("haken", HAKEN_AUS, 34, "center"), # zum Anhaken fürs Löschen
|
||||
("datum", "Datum", 90, "center"),
|
||||
("nummer", "Rechnung", 95, "center"),
|
||||
("art", "Art", 132, "center"), # Storno zu ... / storniert / Neuausstellung
|
||||
|
|
@ -87,14 +83,16 @@ class SummenLeiste(tk.Frame):
|
|||
|
||||
|
||||
class MonatsTab(ttk.Frame):
|
||||
"""Tabelle eines Monats. `bei_auswahl` wird mit der markierten Buchung gerufen."""
|
||||
"""Tabelle eines Monats - reine Anzeige.
|
||||
|
||||
def __init__(self, master, monat: int, bei_haken=None):
|
||||
Hier wird nichts angehakt und nichts geloescht: die Zahlen kommen aus den
|
||||
Rechnungen, nicht aus diesem Fenster.
|
||||
"""
|
||||
|
||||
def __init__(self, master, monat: int):
|
||||
super().__init__(master, padding=(8, 8, 8, 6))
|
||||
self.monat = monat
|
||||
self.bei_haken = bei_haken # meldet dem Fenster, dass sich die Auswahl änderte
|
||||
self._buchungen = []
|
||||
self._angehakt = set() # ids der angehakten Buchungen
|
||||
|
||||
self.baum = ttk.Treeview(self, columns=[s[0] for s in SPALTEN],
|
||||
show="headings", selectmode="browse")
|
||||
|
|
@ -121,37 +119,6 @@ class MonatsTab(ttk.Frame):
|
|||
|
||||
self.columnconfigure(0, weight=1)
|
||||
self.rowconfigure(0, weight=1)
|
||||
|
||||
self.baum.bind("<Button-1>", self._klick)
|
||||
# Kein Doppelklick zum Bearbeiten: die Zahlen kommen aus der Rechnung,
|
||||
# nicht aus diesem Fenster.
|
||||
|
||||
# ---- Anhaken -----------------------------------------------------------
|
||||
def _klick(self, ereignis):
|
||||
"""Klick in die Haken-Spalte schaltet die Zeile an/aus."""
|
||||
if self.baum.identify_region(ereignis.x, ereignis.y) != "cell":
|
||||
return
|
||||
if self.baum.identify_column(ereignis.x) != "#1":
|
||||
return
|
||||
zeile = self.baum.identify_row(ereignis.y)
|
||||
if not zeile:
|
||||
return
|
||||
b = self._buchungen[int(zeile)]
|
||||
if b.id in self._angehakt:
|
||||
self._angehakt.discard(b.id)
|
||||
else:
|
||||
self._angehakt.add(b.id)
|
||||
self.baum.set(zeile, "haken", HAKEN_AN if b.id in self._angehakt else HAKEN_AUS)
|
||||
if self.bei_haken:
|
||||
self.bei_haken()
|
||||
return "break" # kein Zeilen-Markieren durch den Haken-Klick
|
||||
|
||||
def angehakte(self):
|
||||
return [b for b in self._buchungen if b.id in self._angehakt]
|
||||
|
||||
def haken_loesen(self):
|
||||
self._angehakt.clear()
|
||||
|
||||
def markierte(self):
|
||||
"""Aktuell markierte Buchung oder None."""
|
||||
sel = self.baum.selection()
|
||||
|
|
@ -174,8 +141,6 @@ class MonatsTab(ttk.Frame):
|
|||
doppelte = set(doppelte)
|
||||
aufgehoben = set(aufgehoben)
|
||||
self._buchungen = sorted(buchungen, key=lambda b: (b.datum, b.rechnungsnummer))
|
||||
# Haken nur für Zeilen behalten, die es noch gibt
|
||||
self._angehakt &= {b.id for b in self._buchungen}
|
||||
self._namensspalte_anpassen()
|
||||
self.baum.delete(*self.baum.get_children())
|
||||
for i, b in enumerate(self._buchungen):
|
||||
|
|
@ -196,7 +161,6 @@ class MonatsTab(ttk.Frame):
|
|||
else:
|
||||
art_text = ""
|
||||
self.baum.insert("", "end", iid=str(i), tags=marke, values=(
|
||||
HAKEN_AN if b.id in self._angehakt else HAKEN_AUS,
|
||||
b.datum.strftime("%d.%m.%Y"),
|
||||
("⚠ " + b.voll_nummer) if zweimal else b.voll_nummer,
|
||||
art_text,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue