Seine Ansage: "des wegen sollte mann auch eine version c mit only storno
machen weil das gibt es auch".
Bisher gab es nur zwei Wege aus dem Storno:
A - es folgt eine neue Rechnung
B - die Leistung ist schon abgerechnet, Verweis auf die bleibende Nummer
Beide behaupten etwas, das im dritten Fall nicht stimmt: die Buchung ist
geplatzt oder die Rechnung war komplett irrtuemlich, es wird also gar
nichts abgerechnet. Mit A wartet der Gast auf eine Rechnung, die nie
kommt, und es waere unnoetig eine Nummer reserviert; mit B muesste man
auf eine Rechnung verweisen, die es nicht gibt.
C steht als dritter Knopf im selben Schritt (A, B, C, Abbrechen) - kein
zweiter Schritt, weil es nichts einzutippen gibt.
- keine Folgenummer reserviert (die fortlaufende Nummernfolge bekaeme
sonst eine Luecke, § 14 Abs. 4 Nr. 4 UStG)
- kein offener Vorgang, das Formular wartet auf nichts
- eigener Satz auf dem Blatt: "Die Leistung wird nicht abgerechnet - es
folgt keine weitere Rechnung. Bereits gezahlte Betraege werden
erstattet." Kein Wort von einer berichtigten Rechnung
- Kenndaten tragen "nur_storno": true, damit das Steuerjournal den Fall
auseinanderhalten kann
Pruefstand pruef_nur_storno.py (neu, 15 Pruefungen) drueckt den Knopf am
echten Fenster und sieht danach Blatt, Kenndaten, Nummernzaehler und das
Steuerjournal an (Rechnung + Storno stehen drin, gemeldet wird nichts).
pruef_storno (A), pruef_doppelte_leistung (B), pruef_kette und
pruef_nummernsperre laufen unveraendert gruen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017bEsoFUk16DfnNA7MjY36H
Sein Fall: 'der typ hat mit dem alten programm einfach eine neue rechnung
gemacht, nun hat der die gleiche rechnung 3-4 mal drin, aber mit anderen
nummern'. Die Ueberzaehligen muessen storniert werden - und ihr Storno muss auf
die Rechnung verweisen, die BLEIBT, nicht auf eine neue.
Nach dem Auswaehlen der Rechnung fragt das Storno jetzt:
A - es folgt eine neue Rechnung (der bisherige Weg)
B - die Leistung ist schon abgerechnet
Zweistufig, weil er es so braucht: 'ich druecke b und ERST DANN kann ich die
nummer eingeben ... das ein opa, immer step by step, nicht alles auf einmal,
wie bei kindern'. Schritt 1 zeigt nur die Wahl, Schritt 2 das Nummernfeld, mit
Zurueck.
Geprueft wird nicht nur, OB es die Nummer gibt, sondern auch, ob der INHALT
passt (sein Zusatz): anderer Gast = geht nicht, anderer Zeitraum oder Betrag =
Nachfrage. Auf dem Blatt steht dann 'Schon abgerechnet: 2026-001' statt
'Berichtigte Rechnung', dazu der Satz, dass die Leistung versehentlich doppelt
in Rechnung gestellt wurde und keine weitere Rechnung folgt. Es wird keine
Nummer reserviert und kein Vorgang offen gehalten.
Pruefstand pruef_doppelte_leistung.py: faehrt das echte zweistufige Fenster,
prueft die Inhaltspruefung einzeln und am Ende das Steuerjournal - EIN
Aufenthalt (3 Naechte, 39,00) statt zwei.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYeAfLtccFrbU3MTj1MMTx
Seine Ansage: 'er sollte es aber, wenn er neu startet, weil er ja eine Excel
hat, im Popup einmal setzen - das waere fair. Aber ab da ist die fest, nicht
aenderbar ausser ueber Storno und Berichtigen.'
Beim ersten Start fragt das Programm einmal, wo die Nummer weiterlaufen soll.
Vorgeschlagen wird, was Ordner und Nummernbuch hergeben; niedriger nimmt es
nicht an, und vor dem Uebernehmen wird noch einmal gefragt. Danach steht
nummer_eingerichtet in der config, und die Einstellungen zeigen den Zaehler nur
noch an - mit dem Verweis auf Storno und Berichtigung (§ 14 Abs. 4 Nr. 4 UStG,
§ 146 Abs. 4 AO).
Der Jahreswechsel bleibt automatisch: im neuen Jahr faengt es wieder bei 001 an,
und die Rechnungen des Vorjahres heben nichts an. Auch das steht jetzt im
Pruefstand ('beim jahreswechsel sollte die nach dem - zeichen auf 000 gehen,
weil neues jahr').
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYeAfLtccFrbU3MTj1MMTx
Sein Einwand: 'er kann jetzt bescheissen, indem er das in den einstellungen
aendert' - und sein Vorschlag: 'dann koennte man noch die pdfs nutzen als
referenz, die haben auch die nummern'.
Der hoechste je vergebene Stand kommt jetzt aus ZWEI Quellen:
* den PDFs im Rechnungsordner (der Dateiname traegt die Nummer - kein Oeffnen
noetig)
* dem gemeinsamen Nummernbuch (kennt auch Nummern OHNE PDF, den Altbestand)
Der Zaehler in der config taugt nicht als Wahrheit, er ist ja gerade das, was
sich aendern laesst. Deshalb: beim Nummernvorschlag wird ein zurueckgedrehter
Zaehler stillschweigend wieder angehoben, und die Einstellungen nehmen keinen
Wert mehr an, der hinter dem Vergebenen liegt - mit Meldung, welcher Stand
schon erreicht ist und woher er kommt. Nach VORN bleibt er frei: eine
lueckenlose Folge verlangt das Gesetz nicht, jede Nummer aber nur einmal
(§ 14 Abs. 4 Nr. 4 UStG).
Pruefstand pruef_nummernsperre.py: 17 Pruefungen - zurueckgedrehter Zaehler,
Einstellungen vor und zurueck, nur Ordner, nur Buch, und beides weg.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYeAfLtccFrbU3MTj1MMTx
"und rechnungsnummer ist dann angebotsnummer - dann ist das auch weg": das Feld
heisst jetzt Angebotsnummer, und der Hinweis "die Nummer oben ist keine
Rechnungsnummer" konnte damit entfallen. Auf dem ganzen Blatt steht das Wort
Rechnung nur noch im Pflichthinweis, der gerade sagt, dass es keine ist. Der
Pruefstand prueft beides ausdruecklich.
"oder in ein extra ordner gepackt werden": die Angebote landen in "Angebote"
neben den Rechnungen. Der Rechnungsordner ist die Buchhaltung, dort haben sie
nichts zu suchen. Weggeworfen werden duerfen sie trotzdem nicht - ein
abgesandtes Angebot ist ein Handelsbrief (§ 257 HGB) - deshalb ein Unterordner
und kein anderer Ort. Die Nummernsuche schaut jetzt dort nach, und das
Steuerjournal ueberspringt sie ohnehin ueber art="angebot".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYeAfLtccFrbU3MTj1MMTx
Seine Frage war: "was ist rechtssicher, kostenvoranschlag oder proforma - ich
bin kein jurist, deswegen sage ich ja § schauen". Nachgesehen:
* Kostenanschlag ist ein Begriff des WERKvertrags (§ 632 Abs. 3 BGB "Ein
Kostenanschlag ist im Zweifel nicht zu verguten", § 649 BGB) - das ist der
Handwerker-Fall. Bartl vermietet, das passt nicht.
* "Proforma-Rechnung" steht in keinem Gesetz (Zoll- und Exportbegriff) und
traegt als Einziges das Wort "Rechnung".
* Das Angebot ist § 145 BGB: gebunden ist man daran, "es sei denn, dass er die
Gebundenheit ausgeschlossen hat" - deshalb steht "unverbindlich" drauf.
* Im Camping- und Hotelgeschaeft ist "unverbindliches Angebot" der uebliche
Begriff (auch in den Muster-AGB des Branchenverbands BVCD).
Seine Entscheidung: "dann mach angebot draus, ist das gaengigste" - Begruendung
"dann ist der name rechnung weg und erweckt nicht den anschein das man eine
bekommen hat".
Deshalb heisst es jetzt ueberall Angebot: Ueberschrift "Unverbindliches
Angebot", "Angebot an:", "Angebot-Nr.: 2026-A001" und "Datum:" statt
"Rechnungsdatum:" - das Wort Rechnung kommt im ganzen Kopf nicht mehr vor, und
genau das prueft pruef_angebot.py auch nach. Eigene Reihe 2026-A001, Dateiname
Angebot_..., eigener Zaehler (angebot_jahr/angebot_zaehler).
Steuerlich aendert der Name nichts (§ 14 Abs. 1 S. 1 UStG: die Bezeichnung des
Dokuments ist gleichgueltig) - die Sicherheit kommt weiter vom fehlenden
Steuerausweis, siehe UStAE 14c.1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYeAfLtccFrbU3MTj1MMTx
Seine Erklaerung: 'es geht nur darum das er seinen kunden wie eine art
kostenvoranschlag/proforma rechnung das die wissen ah das kommt auf mich zu und
wenn die ja sagen er dann die richtige machen tut'. Ueberschrift jetzt
'Proforma-Rechnung (Kostenvoranschlag)', im Text 'Dies ist ein
Kostenvoranschlag' - damit steht die Natur des Blattes ganz vorn und nicht das
Wort Rechnung.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYeAfLtccFrbU3MTj1MMTx
MINI-API (seine Ansage: "die muessen ja sehen das die beiden auf sind"):
Jedes Programm macht beim Start einen winzigen Server auf 127.0.0.1 mit einem
Port, den das Betriebssystem aussucht, und traegt Port und PID in die
gemeinsame SQLite ein - die ist das Telefonbuch. Zwei Nachrichten reichen:
"ping" wird mit "hallo, bin noch da" beantwortet, "schau_nach" ist der Push.
Der Push traegt KEINE Daten. Er sagt nur "schau in die sqlite" - dort steht die
Meldung ohnehin. Eine Wahrheit, und wenn der Anruf nicht durchgeht, findet das
andere Programm die Meldung beim naechsten Nachsehen.
Gebunden wird nur auf Loopback: von aussen ist nichts erreichbar, Windows fragt
bei 127.0.0.1 nicht nach der Firewall, und die API fuehrt keine Befehle aus.
Kein Netzwerk-Server mit MySQL - dafuer braeuchte er Infrastruktur, die es
nicht gibt; auf verschiedenen Rechnern waere ohnehin ein anderer Bau noetig.
PROFORMA-RECHNUNG: ein Knopf, gewaehlt wird im Popup - die fruehere
Sicherheitsfrage IST diese Auswahl geworden (eine Frage statt zwei).
Seine Bedingung: "musst nur auf passen das der rechner dann die rechnungs
nummer nicht aendert". Die Proforma laeuft in einer EIGENEN Reihe (2026-P001);
Nummernfeld, Rechnungszaehler, Nummernbuch und die Meldung ans Journal bleiben
unberuehrt. Genau das prueft pruef_proforma.py zuerst.
An den Quellen geprueft, nicht an Ratgeberseiten:
* § 14 Abs. 1 S. 1 UStG / UStAE 14.1 - Rechnung ist jedes Dokument, mit dem
abgerechnet wird, "gleichgueltig, wie dieses Dokument im Geschaeftsverkehr
bezeichnet wird". Das Wort "Proforma" allein schuetzt also nicht.
* § 14c Abs. 2 UStG - wer Steuer gesondert ausweist, ohne berechtigt zu sein,
schuldet den Betrag; Korrektur nur mit Antrag und Zustimmung des Finanzamts.
* § 14 Abs. 4 Nr. 4 UStG - die fortlaufende, einmalig vergebene Nummer gilt
fuer Rechnungen.
Deshalb: Ueberschrift "Proforma-Rechnung", der Satz "keine Rechnung im Sinne
des § 14 UStG ... nicht zu bezahlen", "Proforma an:" statt "Rechnung an:", die
Nummer ausdruecklich als KEINE Rechnungsnummer, KEIN Steuersatz und KEIN
Steuerbetrag (auch die Beherbergungssteuer nicht - sonst waere das Blatt halb
ausgewiesen, halb nicht), dafuer "Voraussichtlicher Gesamtbetrag" mit dem
Hinweis, dass alles enthalten ist. Kein Blatt fuers Amt, kein Zahlungstext.
TESTLAUF: _testwahl sagt, was im Auswahlfenster geklickt worden waere;
pruef_proforma setzt _dialog_zeigen und drueckt die echten Knoepfe.
Pruefstaende: pruef_proforma.py (neu, mit dem echten Popup), pruef_montage,
pruef_storno, pruef_kette, pruef_kernregeln, pruef_nummernbuch, pruef_nummern,
pruef_dialoge - alle gruen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYeAfLtccFrbU3MTj1MMTx
Beide Programme stehen nebeneinander offen. Bisher haette das Journal die neue
Rechnung erst beim naechsten Einlesen gesehen.
Kein Absuchen des Rechnungsordners und kein Server: nach jeder Rechnung und
jedem Storno schaut das Rechnungstool nach, ob das Journal ueberhaupt laeuft -
ueber dessen PID, nicht ueber ein Fenster und nicht ueber eine Uhrzeit. Windows
beantwortet das genau; PIDs werden wiederverwendet, deshalb wird zusaetzlich der
Name der EXE verglichen. Laeuft es, kommt eine Zeile in den Briefkasten.
Der Briefkasten IST die gemeinsame SQLite (seine Ansage): zwei Tabellen mehr -
"laeuft" (wer ist offen, mit PID und EXE) und "nachrichten". Damit liegt im
gemeinsamen Ordner genau EINE Datei. Karteileichen (abgestuerztes Programm)
werden beim Nachsehen weggeraeumt, alte Meldungen beim Melden.
Ist das Journal zu, passiert nichts - es liest den Ordner beim naechsten Start
ohnehin. Die Meldung ist eine Anklopfhilfe, keine Datenquelle.
Pruefstand pruef_wache.py (im Steuerjournal, faehrt BEIDE Programme): PID-Pruefung
mit toter Nummer und fremdem EXE-Namen, genau eine Datei im Ordner, Meldung nach
Rechnung und nach Storno, Einlesen samt Anzeige, Ruhe ohne Meldung, kein Melden
an ein geschlossenes Journal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYeAfLtccFrbU3MTj1MMTx
Das Rechnungstool kannte nur die Nummern seiner eigenen PDFs. Im Steuerjournal
stehen aber Buchungen ohne PDF - der Altbestand aus der Excel-Mappe - und deren
Nummern hat es ein zweites Mal vergeben. § 14 Abs. 4 Nr. 4 UStG laesst jede
Nummer nur einmal zu.
Deshalb fuehren beide Programme jetzt EIN gemeinsames Buch (gemeinsam.py): eine
SQLite in einem Ordner, den beide kennen. Es ist ein Register, keine zweite
Buchhaltung - die Wahrheit bleiben die PDFs. Was hineinkommt: jede geschriebene
Rechnung, jeder Storno (mit storno_zu), jede Neuausstellung. Die Nummernvergabe
und die Kollisionspruefung beim Erstellen fragen es mit ab; ein verworfener
Storno gibt seine Nummer wieder frei. Faellt der Ordner aus (Netzlaufwerk weg),
laeuft alles weiter - dann steht es in der Fussleiste.
ordnerwahl.py fragt den Ordner beim ersten Start ab: auswaehlen, OK,
Sicherheitsfrage, bei Nein zurueck ins Feld. Zwei Fehler daran gleich behoben,
bevor sie jemand treffen konnte: das Fenster war zu schmal (der Auswahl-Knopf
lag ausserhalb) und der Griff (grab_set) muss waehrend des Windows-Ordner-
dialogs los sein, sonst nimmt der keine Eingabe an.
Storno: er ist das Spiegelbild der Rechnung - auch bei den UEBERNACHTUNGEN.
Vorher stand dort nichts, und das Steuerjournal meldete dem Amt die Naechte der
aufgehobenen Rechnung weiter mit. Auf dem Blatt steht "4 Naechte (aufgehoben)",
in den Kenndaten -4.
"Neue Rechnung:" auf dem Storno-Blatt heisst jetzt "Berichtigte Rechnung:" -
die alte Beschriftung las sich wie die Nummer DIESES Blattes.
--test / CAMPINGHOF_TEST=1 unterdrueckt den Erststart-Dialog. In der EXE kommt
man ohnehin nicht daran, und die Pruefstaende bleiben sonst im Dialog stehen.
Pruefstand pruef_nummernbuch.py (neu): beide Programme auf einem Ordner - das
Journal traegt drei Altbestands-Nummern ein, das Rechnungstool schlaegt danach
2026-004 statt 2026-001 vor. Alle uebrigen Pruefstaende gruen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYeAfLtccFrbU3MTj1MMTx
Bartl vermietet auch monatsweise an Montagearbeiter. Dieses Blatt schrieb er
bisher von Hand in Word ("Miete fuer Montagearbeiter inkl. 7 % MwSt. fuer den
Monat August 2026", Netto/USt/Brutto nebeneinander). Jetzt ist es ein zweiter
Reiter neben der Campingrechnung: Kunde, Nummer, Datum und Zeitraum gelten fuer
beide - nur die Mitte wechselt, damit niemand den Kunden zweimal tippt.
Die Zeitraum-Felder sind dieselben wie Anreise/Abreise, nur anders beschriftet.
Daraus kommen die Naechte, und die meldet das Steuerjournal ans Amt.
Gerechnet wird im selben Rechenkern: der Reiter baut einen Ein-Zeilen-Katalog
mit dem Monatspreis (brutto, Vorgabe 500,00 unter Einstellungen ->
Montage-Rechnung). Die Beherbergungssteuer kommt obendrauf wie ueberall sonst -
500 + 5 % = 525. Fuer das Steuerjournal ist das eine ganz normale Buchung, art
bleibt "rechnung"; die Kenndaten tragen zusaetzlich vorlage="montage", damit der
Storno-Weg die Rechnung spaeter in den richtigen Reiter laedt (sonst waeren aus
Monaten Wohnwagen-Naechte geworden).
Datum: Tag und Monat lassen sich nicht mehr unmoeglich tippen (eine 4 wird zu
04, 35 und 13 fallen weg), und vor dem Erstellen wird gegen den Kalender
geprueft - den 31.02. faengt erst das. Vorher landete so etwas ungeprueft auf
der Rechnung.
Dazu: die Reiter bekommen Theme-Farben, sonst sieht man nicht, welcher offen ist.
Pruefstand pruef_montage.py: 45 Pruefungen am echten Fenster und an echten
PDFs - Reiterwechsel, Beschriftung, Betraege, das Blatt selbst, danach liest das
Steuerjournal die Datei ein (500,00 Basis, 25,00 Steuer, 30 Uebernachtungen),
Storno, die unveraenderte Campingrechnung und die Datumspruefung.
pruef_kernregeln, pruef_storno, pruef_kette, pruef_nummern, pruef_dialoge gruen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYeAfLtccFrbU3MTj1MMTx
- "Rechnung endgueltig erstellen?" nennt Nummer, Kunde und Betrag und weist
darauf hin, dass danach nur noch Storno oder Berichtigung bleibt
(§ 14 Abs. 4 Nr. 4 UStG, § 31 Abs. 5 UStDV). Geprueft ist auch, dass "Nein"
wirklich nichts schreibt - sonst waere die Frage eine Attrappe.
- Als EXE legt das Programm sich beim ersten Start einen Ordner "Daten" neben
die EXE (config.json). Ist dort nicht schreibbar (EXE unter "Programme"),
weicht es nach LOCALAPPDATA aus. Lag die config.json noch direkt neben der
EXE, zieht sie um - sonst faengt das Programm ohne Firmendaten neu an.
- Aufruf von _storno_fortsetzen entfernt: die Methode gibt es nicht mehr, seit
die Kette auf dem Storno-Blatt steht. War unerreichbar, waere aber beim
naechsten Anfassen ein AttributeError geworden.
- Pruefstaende raeumen ihren Wegwerf-Ordner weg und haben eine Reissleine
(Abbruch nach zwei Minuten), damit kein Dialog mehr ewig offen steht.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKvGkdNW1vKdnMPAM9Bp3X
Statt einer Zustandsmaschine ("offener Storno" ueber den Neustart retten) traegt
das Storno jetzt einfach beide Nummern:
Storno zu Rechnung: 2026-002
Neue Rechnung: 2026-004
und im Text "2026-002 -> 2026-003 -> 2026-004". Damit ist die Kette aus dem
Beleg allein lesbar. Wird das Programm zwischendurch geschlossen, sagt der
Storno, unter welcher Nummer die berichtigte Rechnung gehoert; der Dialog
nennt sie ebenfalls, statt nur "bereits storniert" zu melden.
naechste_freie_nummer_nach() bestimmt die Folgenummer, damit sie nicht von der
Reihenfolge abhaengt, in der der Zaehler mitzieht.
Ausserdem: die Pruefstaende raeumen ihren Wegwerf-Ordner wieder weg, wenn sie
gruen sind. Ein Testlauf hatte 398 Ordner im Temp-Verzeichnis hinterlassen.
Bei Fehlern bleibt der Ordner stehen, damit man hineinsehen kann.
Weitere Funde des Code-Agenten behoben:
- "Kopie von Rechnung_...pdf" und klein geschriebene Dateinamen zaehlen jetzt
bei der Nummernvergabe mit (vorher galt die Nummer als frei).
- Strg+P feuerte auch aus einem offenen Dialog heraus (bind_all -> bind).
167 Pruefungen gruen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKvGkdNW1vKdnMPAM9Bp3X
Duplikatanzeige gehoert nicht hierher, sondern in den Steuerrechner - wieder
ausgebaut (duplikate.py, pruef_duplikate.py).
Zwei echte Loecher, die dabei auffielen:
1. vorhandene_rechnungen() uebersah Windows-Kopien. "Rechnung_2026-013 (1).pdf"
und "... - Kopie.pdf" galten nicht als vergeben, die Nummer wurde also ein
zweites Mal ausgegeben. Genau so ist der Altbestand entstanden. Jetzt zaehlt
alles mit, was nach der Nummer KEINE weitere Ziffer hat - "2026-0135" bleibt
getrennt.
2. Liegt eine Nummer mehrfach im Ordner, nahm der Storno-Dialog blind den ersten
Treffer. Jetzt erscheint eine Auswahlliste mit Datum, Name, Betrag und
Dateiname; ohne Auswahl passiert nichts, und die Meldung nennt die konkrete
Datei.
pruef_kernregeln.py: 37 Pruefungen (10 neue), pruef_nummern.py 10.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKvGkdNW1vKdnMPAM9Bp3X
Der Altbestand wurde nie geprueft - die Sperre verhindert nur NEUE Dubletten.
Neues Modul duplikate.py findet zwei Sorten, und sie bedeuten nicht dasselbe:
Nummer doppelt - zwei Geldbelege mit derselben Rechnungsnummer. Der harte
Fall (§ 14 Abs. 4 Nr. 4 UStG).
Inhalt doppelt - gleicher Gast, gleiches Datum, gleicher Betrag unter zwei
Nummern. Nur ein Hinweis, kann echt sein.
Der schwierige Teil ist das NICHT-Finden: Storno und berichtigte Rechnung
haben absichtlich eigene Nummern, und ein Berichtigungsblatt traegt sogar die
Nummer der Rechnung, die es berichtigt. Nichts davon ist eine Dublette.
PDFs ohne Kenndaten fallen nicht durch: ihre Nummer wird aus dem Dateinamen
gelesen und sie werden getrennt aufgelistet - das ist genau der ungepruefte
Altbestand.
Beim Start laeuft die Pruefung still mit; liegt etwas vor, steht es rot in der
Statuszeile. Kein Popup beim Hochfahren.
pruef_duplikate.py (12 Pruefungen): Ordner mit dem echten Schlamassel gebaut -
zweimal 2026-013 fuer zwei verschiedene Gaeste, dazu eine saubere Storno-Kette
und ein Berichtigungsblatt, die NICHT gemeldet werden duerfen, ein
inhaltsgleiches Paar und eine fremde PDF. Fenster angesehen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKvGkdNW1vKdnMPAM9Bp3X
Bisher zeigte jeder Beleg nur auf seinen Vorgaenger (storno_zu, ersetzt,
storno_nummer). Ein zweites Programm musste sich die Kette daraus
zusammensuchen. Jetzt tragen ALLE Blaetter einer Kette dieselbe
Vorgangsnummer - die der ersten Rechnung. Gruppieren heisst dort: nach
"vorgang" sortieren, fertig. Eine normale Rechnung ist ihr eigener Vorgang.
Wichtig fuer den Fall "schon korrigierte Rechnung wird noch einmal
storniert": Storno und Berichtigung uebernehmen den VORGANG der alten
Rechnung, nicht deren Nummer - sonst zerfaellt die Kette in zwei.
Die Einzelbezuege bleiben zusaetzlich stehen, damit man die Reihenfolge
innerhalb der Kette lesen kann.
Neu pruef_kette.py (13 Pruefungen): spielt den schlimmsten Fall durch -
Rechnung, Storno, berichtigte Rechnung, Berichtigung der Anschrift, zweiter
Storno, zweite berichtigte Rechnung. Alle sechs landen unter einem
Schluessel, die Summe der Kette ergibt genau die letzte gueltige Rechnung
(27,30 €), und der Steuerrechner liest davon 5 Geldbelege - das
Berichtigungsblatt zaehlt nicht mit.
148 Pruefungen gruen (100 + 27 + 13 + 8) plus 7 Dialoge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKvGkdNW1vKdnMPAM9Bp3X
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>