Funde der drei Agenten: zwei kaputte Dialoge, haengende Flags, scharfe Pruefstaende
REGRESSION aus der Modulaufteilung (Code-Review-Agent): - einstellungen.py fehlten "berechnung" und "datetime". Menue > Leistungskatalog und > Rechnungsnummer stuerzten mit NameError ab und liessen ein leeres, modales Fenster stehen. Preise und Nummernkreis waren nicht mehr einstellbar. KEIN Pruefstand hat das gemerkt - keiner oeffnete je einen Dialog. Neu: pruef_dialoge.py oeffnet ALLE Dialoge und schliesst sie wieder. HAENGENDE ZWISCHENSPEICHER (derselbe Agent, gemessen): - "Zuruecksetzen" liess storno_stand/berichtigung_stand stehen. Danach gehoerten die Daten eines FREMDEN Gastes zum Storno der alten Rechnung, und die Berichtigung schrieb ein Blatt mit Nummer A im Kenndatensatz und Nummer B auf dem Blatt. reset_formular fragt jetzt nach und verwirft sauber. - _neue_nummer_vorschlagen fasst die Nummer nicht mehr an, solange ein Vorgang laeuft (traf auch dlg_nummer). - Fenster schliessen mit offenem Storno fragt jetzt nach (WM_DELETE_WINDOW). - _storno_verwerfen bricht ab, wenn das PDF nicht geloescht werden kann, statt den Zaehler trotzdem zurueckzudrehen. - Der Storno prueft die Kundenangaben und fragt, wenn die alte Rechnung sie nicht hergibt. - Zaehler nicht speicherbar -> sichtbare Meldung statt Traceback ins Nichts. - storno.py: v1-Rueckfall nimmt den ersten Satz OHNE extra_blatt. - EINE Quelle fuer die naechste Nummer: _neue_nummer_vorschlagen benutzt jetzt naechste_freie_nummer, der Jahreswechsel steht nur noch dort. Vorher schlug das Formular 2026-001 vor, waehrend der Storno-Weg 2025-088 nahm. PRUEFSTAENDE (Audit-Agent: 16 von 27 eingebauten Fehlern blieben unbemerkt): - Neu pruef_kernregeln.py (27 Pruefungen) fuer die zwei Regeln, um die es geht: Nummer von HAND auf eine vergebene setzen, Zaehler hinter dem Ordner, Jahreswechsel, Zaehler nur vorwaerts, Storno ueber den DIALOGKNOPF statt der internen Methode, zweite Berichtigung am selben Tag. Der Bestand wird ueber SHA256 verglichen - "Datei ist noch da" heisst nicht "unveraendert". - Der Text des Storno-PDFs wird gelesen: Positionszeile negativ, nicht nur der Summenblock. - Tautologien raus: Selbstvergleich beim storno_datum, all() ueber eine leere Liste, "nicht" in einem deutschen Text. - Unangemeldeter Dialog laesst den Lauf scheitern, statt still "Nein" zu sagen. - pruef_nummern.py: .PDF gross geschrieben, Rechnung_*.txt, echte Praefix-Falle. - Gegenprobe: sechs Mutationen eingebaut, die vorher gruen blieben - alle sechs werden jetzt rot. RECHT (Gegenpruefungs-Agent): Zitate praezisiert. § 146 Abs. 4 AO verbietet nur Aenderungen, bei denen der urspruengliche Inhalt nicht mehr feststellbar ist - ein protokollierter Storno-Vermerk waere erlaubt, wir verzichten trotzdem bewusst darauf. Der § 14c-Hinweis bleibt, gilt aber gegenueber Endverbrauchern nicht (EuGH C-378/21, BMF v. 27.02.2024) - er zielt auf Firmengaeste. 142 Pruefungen gruen (100 + 27 + 8 + 7). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EKvGkdNW1vKdnMPAM9Bp3X
This commit is contained in:
parent
2277a12e0f
commit
d67448c58f
11 changed files with 538 additions and 29 deletions
67
app.py
67
app.py
|
|
@ -66,6 +66,10 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
|
|||
self._neue_nummer_vorschlagen()
|
||||
self.recalc()
|
||||
|
||||
# Beim Schliessen nachfragen, wenn ein Storno noch offen ist - sonst liegt
|
||||
# es morgen ohne berichtigte Rechnung im Ordner und keiner weiss warum.
|
||||
self.protocol("WM_DELETE_WINDOW", self._beenden)
|
||||
|
||||
# Titelleiste dunkel (nach dem Mappen des Fensters)
|
||||
self.after(60, lambda: self._dark_titlebar(self))
|
||||
# Beim ersten Start nach dem Speicherort fragen (wenn noch keiner gesetzt ist)
|
||||
|
|
@ -83,7 +87,7 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
|
|||
("Formular zurücksetzen", self.reset_formular),
|
||||
("Ausgabeordner öffnen", self._ordner_oeffnen),
|
||||
("-", None),
|
||||
("Beenden", self.destroy),
|
||||
("Beenden", self._beenden), # fragt nach, wenn ein Storno offen ist
|
||||
],
|
||||
"Einstellungen": [
|
||||
("Steuersätze…", self.dlg_steuern),
|
||||
|
|
@ -403,12 +407,16 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
|
|||
|
||||
# ----------------------------------------------------------- Aktionen
|
||||
def _neue_nummer_vorschlagen(self):
|
||||
jahr = self.cfg.get("rechnung_jahr", datetime.date.today().year)
|
||||
zaehler = self.cfg.get("rechnung_zaehler", 0)
|
||||
heute_jahr = datetime.date.today().year
|
||||
if heute_jahr != jahr:
|
||||
jahr, zaehler = heute_jahr, 0
|
||||
self.r_nummer.set(config.format_rechnungsnummer(jahr, zaehler + 1))
|
||||
"""Nächste freie Nummer ins Formular - EINE Quelle, sonst laufen sie auseinander.
|
||||
|
||||
Läuft gerade ein Storno oder eine Berichtigung, gehört die Nummer im Feld
|
||||
zu diesem Vorgang und wird nicht angefasst.
|
||||
"""
|
||||
if self.storno_stand or self.berichtigung_stand:
|
||||
return
|
||||
frei = naechste_freie_nummer(config.ensure_output_dir(self.cfg), self.cfg)
|
||||
if frei:
|
||||
self.r_nummer.set(frei)
|
||||
|
||||
|
||||
def kunde_pruefen(self):
|
||||
|
|
@ -548,7 +556,9 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
|
|||
return
|
||||
|
||||
# Nummer schon vergeben? Dann NICHT drueberschreiben: eine ausgestellte
|
||||
# Rechnung muss unveraendert bestehen bleiben (§ 146 Abs. 4 AO), und jede
|
||||
# Rechnung bleibt liegen - § 146 Abs. 4 AO verbietet Aenderungen, bei denen
|
||||
# der urspruengliche Inhalt nicht mehr feststellbar ist; ein unangetasteter
|
||||
# Beleg ist im Pruefungsfall die einfachere Geschichte. Und jede
|
||||
# Nummer darf es nur einmal geben (§ 14 Abs. 4 Nr. 4 UStG). Also neue Nummer.
|
||||
vorhanden = vorhandene_rechnungen(out_dir, nummer)
|
||||
if vorhanden:
|
||||
|
|
@ -619,10 +629,49 @@ class RechnungsApp(ThemeMixin, KorrekturMixin, EinstellungenMixin, tk.Tk):
|
|||
return
|
||||
self.cfg["rechnung_jahr"] = jahr
|
||||
self.cfg["rechnung_zaehler"] = z
|
||||
config.save_config(self.cfg)
|
||||
try:
|
||||
config.save_config(self.cfg)
|
||||
except OSError as e: # noqa: BLE001 - die PDF ist schon geschrieben
|
||||
# Ohne diese Meldung merkt niemand, dass der Zaehler stehengeblieben
|
||||
# ist: die Ausgabe geht in eine Konsole, die es in der EXE nicht gibt.
|
||||
messagebox.showwarning(
|
||||
"Rechnungsnummer nicht gespeichert",
|
||||
f"Die Rechnung ist geschrieben, der Zählerstand aber nicht:\n{e}\n\n"
|
||||
f"Bitte im Menü unter „Rechnungsnummer…“ den Zähler auf {z} setzen, "
|
||||
"sonst wird die nächste Nummer noch einmal vergeben.")
|
||||
self._neue_nummer_vorschlagen()
|
||||
|
||||
def _beenden(self):
|
||||
"""Schliessen - mit Rückfrage, wenn ein Storno noch offen ist."""
|
||||
if self.storno_stand:
|
||||
if not messagebox.askyesno(
|
||||
"Storno ist noch offen",
|
||||
f"Der Storno {self.storno_stand['nummer']} zu Rechnung "
|
||||
f"{self.storno_stand['zu']} liegt im Ordner, die berichtigte "
|
||||
"Rechnung fehlt noch.\n\n"
|
||||
"Beim Schließen bleibt er so liegen – morgen weiß niemand mehr, "
|
||||
"warum.\n\nTrotzdem beenden?", icon="warning", default="no"):
|
||||
return
|
||||
self.destroy()
|
||||
|
||||
def reset_formular(self):
|
||||
# Ein laufender Vorgang darf nicht stumm liegenbleiben: sonst gehören die
|
||||
# neu eingetippten Daten plötzlich zum Storno der fremden Rechnung.
|
||||
if self.storno_stand:
|
||||
if not messagebox.askyesno(
|
||||
"Storno verwerfen?",
|
||||
f"Der Storno {self.storno_stand['nummer']} zu Rechnung "
|
||||
f"{self.storno_stand['zu']} liegt noch bereit.\n\n"
|
||||
"Zurücksetzen verwirft ihn – das Storno-PDF wird gelöscht und "
|
||||
f"es bleibt allein die Rechnung {self.storno_stand['zu']}.\n\n"
|
||||
"Wirklich zurücksetzen?", icon="warning", default="no"):
|
||||
return
|
||||
self._storno_verwerfen("beim Zurücksetzen verworfen")
|
||||
elif self.berichtigung_stand:
|
||||
# Da ist noch nichts geschrieben - einfach fallenlassen.
|
||||
self.berichtigung_stand = None
|
||||
self._storno_anzeigen()
|
||||
|
||||
for v in (self.k_anrede, self.k_vorname, self.k_name, self.k_zusatz,
|
||||
self.k_strasse, self.k_plzort, self.r_anreise, self.r_abreise):
|
||||
v.set("")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue