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
Knoepfe: "Storno" und "Berichtigen" setzen den Weg schon beim Oeffnen fest,
das Popup fragt dann nur noch die Nummer. Vorher entschied ein zweiter Knopf
im Popup - der ist raus.
app.py war 1802 Zeilen, jetzt 672. Neu daneben:
helfer.py Zahlen, Datum, Namen, Pruefungen (87)
bestand.py was im Rechnungsordner liegt, nur lesen (171)
storno.py den spiegelbildlichen Storno rechnen (57)
einzelinstanz.py nur ein Fenster zulassen (90)
theme.py Farben nach Windows hell/dunkel (120)
korrektur.py Storno- und Berichtigungsweg (306)
einstellungen.py die Dialoge aus dem Menue (358)
Die Oberflaeche bleibt eine Klasse - die Teile sind Mixins, damit kein
einziger self-Zugriff umgeschrieben werden musste. Kein Modul importiert
app.py zurueck, keine ungenutzten Importe mehr. Die Pruefstaende holen die
Funktionen jetzt aus dem Modul, dem sie gehoeren.
pruef_storno.py 100 gruen, pruef_nummern.py 7 gruen, Fenster nach der
Aufteilung gestartet und angesehen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKvGkdNW1vKdnMPAM9Bp3X
Fuer Rechnungen, bei denen nur die Kundenangaben falsch sind - falsche
Betraege bleiben der Storno-Weg.
- Kein eigener Dialog: das Storno-Popup hat jetzt zwei Knoepfe. "Nur
berichtigen" laedt die Rechnung ins Formular, sperrt die Nummer und setzt
ein Flag; "PDF erstellen" schreibt daraus das berichtigte Blatt.
- Die Rechnung behaelt Nummer, Datum und Betraege; dazu kommt "Berichtigt am".
Der Zaehler bleibt stehen - das Blatt verbraucht keine Nummer.
- Ueberschrift "Berichtigte Rechnung", darunter die vollstaendige Rechnung,
darunter der Bezug nach § 31 Abs. 5 UStDV und der Satz, dass es KEINE
zusaetzliche Leistung und keine zweite Rechnung ist. Ohne den kann ein
zweites Blatt mit ausgewiesener USt die Steuer nach § 14c UStG ein zweites
Mal ausloesen.
- Kenndaten des Blattes tragen Nullen und "art": "berichtigung". Damit zaehlt
ein Programm, das die Art nicht kennt, 0,00 € statt den Umsatz doppelt; der
Steuerrechner ueberspringt es ganz (einnahmen.py).
- Dateiname "Berichtigte Rechnung_JJJJ-MM-TT_<Nummer>.pdf".
pruef_storno.py: 100 Pruefungen gruen, darunter der ganze Berichtigungsweg
inklusive Text auf dem Blatt und Gegenprobe mit dem Steuerrechner.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKvGkdNW1vKdnMPAM9Bp3X
Die bisherigen Rechnungen tragen den ganzen Namen in der Vornamen-Zeile und
oft gar keine Anschrift. Beim Stornieren stand dann nur "Andres Schaefer" da
und niemand wusste, wer der Gast ist.
- name_aufteilen(): am Leerzeichen trennen, das letzte Wort ist der Nachname.
- adresse_aus_pdf(): der Adressblock steht im Fliesstext zwischen
"Rechnung an:" und der Rechnungsnummer. Anrede wird an der Auswahlliste
erkannt, PLZ/Ort an der 4- bis 5-stelligen Zahl am Zeilenanfang, die Zeile
davor ist die Strasse - aber nur, wenn sie nicht die Namenszeile ist.
- kundendaten_ermitteln(): Kenndaten zuerst, Text als Rueckfall; benutzt vom
Storno (der geht an denselben Empfaenger) und vom Formular.
- Der Hinweis nach dem Storno sagt jetzt, was aus dem Text gelesen wurde und
was in der alten Rechnung schlicht nicht drinsteht.
Es wird nichts erfunden: fehlt die Anschrift in der alten PDF, bleiben die
Felder leer und die Pflichtpruefung greift.
pruef_storno.py: 80 Pruefungen gruen, darunter eine echte Altrechnung im
Muster "alles in der Vornamen-Zeile" - mit und ohne Anschrift.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKvGkdNW1vKdnMPAM9Bp3X
Leerzeichen allein waren schon vorher raus, aber "x" oder "123" als Nachname
und "12345" ohne Ort gingen durch.
- Felder werden vor der Pruefung gesaeubert (Rand- und Mehrfachleerzeichen),
damit geprueft wird, was nachher gedruckt wird.
- Vorname, Nachname/Firma, Strasse brauchen mindestens zwei Buchstaben.
- PLZ/Ort braucht mindestens vier Ziffern UND zwei Buchstaben - damit gehen
auch 4-stellige Postleitzahlen (AT, NL) durch, aber nicht PLZ ohne Ort.
- Fehlende Hausnummer wird nur nachgefragt, nicht gesperrt: Postfach- und
Feldweg-Anschriften gibt es wirklich, eine falsche Sperre waere schlimmer.
pruef_storno.py: 67 Pruefungen gruen, darunter Auslandsanschriften und kurze
Namen, die durchgehen MUESSEN.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKvGkdNW1vKdnMPAM9Bp3X
- Storno-Dialog: die alte Nummer wird eingetippt, das Programm sucht sie im
Ordner und zeigt sofort Datum, Name und den Betrag des Stornos. Kurze
Schreibweise (2026-3) findet 2026-003.
- Zwei Funde beim Pruefen: Storno-PDFs heissen "Storno_..." und fielen sowohl
aus der Ordnerliste als auch aus der Nummernpruefung heraus. Dadurch galt
eine schon stornierte Rechnung als stornierbar und eine vom Storno belegte
Nummer als frei - die haette ein zweites Mal vergeben werden koennen.
- Anrede ist eine Auswahlliste (tippbar fuer Sonderfaelle).
- Vorname, Nachname, Strasse und PLZ/Ort sind Pflicht; ohne sie wird keine
PDF geschrieben (§ 14 Abs. 4 Nr. 1 UStG). Die alte "trotzdem erstellen?"-
Frage ist raus.
Prueflauf: pruef_storno.py 46 Pruefungen gruen, pruef_nummern.py 7 gruen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKvGkdNW1vKdnMPAM9Bp3X
Eine ausgestellte Rechnung darf nicht geaendert und nicht geloescht werden
(§ 146 Abs. 4 AO), und jede Nummer gibt es nur einmal (§ 14 Abs. 4 Nr. 4
UStG). Korrigiert wird deshalb ueber ein eigenes Dokument mit neuer Nummer,
das sich auf die alte bezieht (§ 31 Abs. 5 UStDV).
- Knopf "Rechnung stornieren": alte Nummer waehlen, Programm liest deren PDF
und schreibt eine Stornorechnung mit eigener neuer Nummer, Betraege
negativ, mit Bezug auf Nummer und Datum der alten Rechnung.
- Danach steht das Formular auf der naechsten Nummer, gefuellt aus der alten
Rechnung; die Nummer ist gesperrt. "PDF erstellen" fragt noch einmal nach
und legt bei Nein das Storno-PDF wieder weg - dann bleibt allein das
Original stehen.
- Die alte Rechnung wird nie angefasst.
- Metadaten v3: art / storno_zu / ersetzt / storno_nummer, dazu Anschrift und
Positionen, damit eine Rechnung wieder ins Formular geladen werden kann.
Alle Felder aus v1/v2 bleiben unveraendert - daran haengt das Steuerjournal.
- Zaehler laeuft nur noch vorwaerts; eine vergebene Nummer wird nicht mehr
ueberschrieben, sondern die naechste freie angeboten.
- Combobox im Storno-Dialog blieb leer: die StringVar hing nur an einer
lokalen Variable und wurde weggeraeumt. Jetzt ohne textvariable.
Prueflauf (pruef_storno.py, 36 Pruefungen gruen): echte PDFs in einem
Wegwerf-Ordner, danach liest der Steuerrechner den Ordner - 001 + Storno +
Korrektur ergeben genau die berichtigte Rechnung, kein Beleg unsicher.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKvGkdNW1vKdnMPAM9Bp3X
Zwei offene Fenster lesen beim Start denselben Zaehler aus der config und
schlagen darum dieselbe Rechnungsnummer vor - die Nummer wurde doppelt
vergeben.
- Windows-Mutex sperrt eine zweite Instanz ab.
- Die laufende Instanz legt ihre PID neben der config.json ab; die zweite
sucht darueber das Fenster (nicht ueber den Titel - der haengt am
Firmennamen aus der config) und holt es nach vorn.
- Popup nur noch als Rueckfall, wenn kein Fenster gefunden wird.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKvGkdNW1vKdnMPAM9Bp3X
Der Steuerrechner braucht Brutto->Netto je Steuersatz; auf einer Rechnung
stecken 7 % (Uebernachtung) und 19 % (Waesche, Gas, Rad) gemischt drin, und
die Aufteilung stand bisher nur im Fliesstext.
Neu in /Subject: netto_gesamt, ust_gesamt, ust_bloecke[{satz,netto,ust,brutto}].
Alle Felder der Version 1 bleiben unveraendert - am zwischensumme/steuer_satz/
steuer_betrag haengt der Import des Steuerjournals.
Verifiziert an einer gemischten Testrechnung (7 % + 19 %): Summe der Bloecke
= Zwischensumme = netto_gesamt + ust_gesamt; Steuerjournal liest die v2-PDF
unveraendert ein.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TxFbNUnzkEk7FRFcZQoepm
Auf dem Blatt fuers Amt taucht die Beherbergungssteuer nirgends mehr auf - auch
nicht versteckt im Gesamtbetrag (der zeigt jetzt die Summe OHNE die Steuer, also
genau die Bemessungsgrundlage). Die Zwischensumme entfaellt dort, sie waere
betragsgleich. Ebenso raus: Bankverbindung und Steuernummer im Fuss, und das Wort
"Beherbergungssteuer" in der Ueberschrift.
Die Kundenseite bleibt vollstaendig: Steuer ausgewiesen, im Gesamtbetrag enthalten,
Bankverbindung im Fuss - das ist die Rechnung, die der Gast bezahlt.
Damit Kopf und Fuss die Seiten unterscheiden koennen, wird das PDF zweimal gebaut:
der erste Lauf ermittelt nur, auf welcher Seite das Amt-Blatt beginnt. Auf "Seite 2
ist das Amt" zu wetten waere falsch, sobald die Kundenrechnung ueber eine Seite
laeuft - dann stuende die Bankverbindung auf dem Amt-Blatt und fehlte beim Kunden.
Auf Seite 2 (Kopie fuers Amt) stehen nur noch die Umsatzsteuersaetze,
die Zwischensumme als Bemessungsgrundlage und der Gesamtbetrag. Die Zeile
"zzgl. Beherbergungssteuer" und der Satzungs-Hinweis entfallen dort - das
Amt rechnet die Steuer selbst aus der Zwischensumme.
Die Kundenseite bleibt unveraendert, und die Kenndaten in den PDF-Metadaten
(steuer_satz, steuer_betrag) bleiben erhalten - daran haengt das Steuerjournal.
Bisher gab es nur ein Feld "Name / Firma". Stand in der Anrede etwas,
war die Anrede die erste Zeile des Adressblocks - das Steuerjournal hat
sie als Namen gelesen.
- Kundenblock hat jetzt "Vorname (optional)" und "Nachname / Firma".
Auf der Rechnung stehen beide wie gewohnt in einer Zeile.
- Die Kenndaten der Rechnung (Nummer, Datum, Nachname, Naechte,
Zwischensumme, Steuersatz und -betrag) stehen zusaetzlich als JSON in
den PDF-Metadaten. Das Steuerjournal liest sie von dort und muss
nichts mehr aus dem Fliesstext raten. Der Ausdruck aendert sich nicht.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Desktop-Programm (Python/Tkinter): Kundendaten + Leistungen eingeben,
druckfertige PDF-Rechnung im Briefkopf des Betriebs. Optionale
Beherbergungssteuer wird eigen ausgewiesen und bekommt ein zweites
Blatt fuers Amt.
Firmendaten, Preise und Steuersaetze stehen in der config.json neben
dem Programm (nicht im Repo) und werden im Menue "Einstellungen"
gepflegt. Im Code stehen nur Platzhalter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>