Gemeinsames Nummernbuch, Storno kuerzt die Naechte, Testlauf-Schalter

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
This commit is contained in:
TheMockTv 2026-09-03 20:38:57 +02:00
parent 5ef4e0a769
commit ecad4b00ea
12 changed files with 790 additions and 16 deletions

View file

@ -27,7 +27,7 @@ import config
import pdf_renderer
from bestand import (kundendaten_ermitteln, naechste_freie_nummer,
naechste_freie_nummer_nach, rechnungen_im_ordner)
from helfer import kunde_maengel, nummer_normalisieren, safe_filename
from helfer import kunde_maengel, nummer_normalisieren, parse_num, safe_filename
from storno import storno_rechnung_bauen
@ -106,6 +106,13 @@ class KorrekturMixin:
"Zähler nicht gespeichert",
f"Die Einstellungen ließen sich nicht schreiben:\n{e}\n\n"
"Bitte im Menü unter „Rechnungsnummer…“ prüfen.")
# Die Nummer ist wieder frei - auch im gemeinsamen Buch, sonst
# ueberspringt das Rechnungstool sie fuer immer.
if self.buch is not None and stand.get("nummer"):
try:
self.buch.entfernen(stand["nummer"])
except Exception as e: # noqa: BLE001
print("[nummernbuch] Storno nicht ausgetragen:", e)
self.storno_stand = None
self._storno_anzeigen()
self._neue_nummer_vorschlagen()
@ -298,7 +305,7 @@ class KorrekturMixin:
def _storno_anlegen(self, meta, out_dir):
"""Schreibt die Storno-PDF und füllt das Formular für die berichtigte Rechnung."""
alt_nummer = str(meta.get("rechnungsnummer", ""))
nummer = naechste_freie_nummer(out_dir, self.cfg)
nummer = naechste_freie_nummer(out_dir, self.cfg, self._belegte_nummern())
if not nummer:
messagebox.showerror("Keine freie Nummer",
"Es wurde keine freie Rechnungsnummer gefunden.")
@ -310,7 +317,7 @@ class KorrekturMixin:
# die naechste freie NACH dem Storno. Sie kommt aufs Storno-Blatt, damit
# die Kette aus dem Beleg allein lesbar ist und kein Zustand gemerkt
# werden muss: alte Nummer -> Storno -> neue Nummer, alles auf einem Blatt.
folge_nummer = naechste_freie_nummer_nach(out_dir, self.cfg, nummer)
folge_nummer = naechste_freie_nummer_nach(out_dir, self.cfg, nummer, self._belegte_nummern())
# Der Storno geht an denselben Empfaenger wie die alte Rechnung - was in
# deren Kenndaten fehlt, wird aus ihrem Adressblock geholt.
kunde, woher = kundendaten_ermitteln(meta)
@ -333,10 +340,17 @@ class KorrekturMixin:
icon="warning", default="no"):
return
# Der Storno ist das Spiegelbild der Rechnung - auch bei den
# UEBERNACHTUNGEN. Stand hier vorher nichts, blieben die Naechte der
# aufgehobenen Rechnung im Steuerjournal stehen: die Betraege kuerzten
# sich, die Zahl der Uebernachtungen nicht, und das Amt bekam sie
# doppelt gemeldet (4 - 0 + 4 = 8 statt 4).
alt_naechte = int(parse_num(str(meta.get("naechte") or 0)) or 0)
kopf = {
"rechnungsnummer": nummer,
"datum": heute,
"anreise": "", "abreise": "", "naechte": "",
"anreise": "", "abreise": "",
"naechte": -abs(alt_naechte) if alt_naechte else "",
"kunde_zeilen": kunde_zeilen,
"nachname": kunde.get("nachname", ""),
"vorname": kunde.get("vorname", ""),
@ -373,6 +387,11 @@ class KorrekturMixin:
}
self._zaehler_aus_nummer_speichern(nummer)
print(f"[storno] erstellt -> {pfad}")
# Der Storno hat eine eigene Nummer - die gehoert ins gemeinsame Buch,
# sonst vergibt sie das andere Programm noch einmal, und das Journal
# erfaehrt nicht, dass die Rechnung aufgehoben ist.
self._ins_buch(nummer, kopf, r, pfad)
self._buch_anzeigen()
fehlend = self._formular_aus_meta(meta)
self.r_nummer.set(folge_nummer or "")