Doppelte Rechnungsnummern zeigen, nur ein Fenster, keine Handbuchungen

Doppelte Rechnungsnummern werden ANGEZEIGT statt still mitgeschleppt: rote
Zeile mit Warnzeichen, markierter Monatsreiter, rote Zeile unter der
Jahressumme und eine Liste aller Belege mit Dateinamen. Gerechnet wird ueber
das ganze Jahr - liegt die zweite Rechnung im naechsten Monat, sieht kein
Monat fuer sich eine Dublette, das Amt bekommt sie trotzdem doppelt. Die
Betraege bleiben in allen Summen und im Amtsbericht; gemeldet wird der Fund,
korrigiert wird nichts von allein.

Vorgeschichte: bis v1.2 war die Rechnungsnummer der Schluessel der Tabelle -
die zweite Rechnung ueberschrieb die erste still und fehlte im Amtsbericht.
v1.3 hat den Schluessel auf die PDF-Datei umgestellt, seitdem bleiben beide
Zeilen stehen. Erst jetzt sieht man den Fall auch.

Einzelinstanz aus dem Steuerrechner uebernommen (nur Mutex- und PID-Name
geaendert): zwei Fenster lesen denselben Rechnungsordner in dieselbe
journal.sqlite3 - was das eine loescht, steht im anderen noch da, und wer
dort "PDF fuers Amt" drueckt, meldet einen Stand, den es nicht mehr gibt.
Die Sperre greift vor ablage.daten_ordner() und nur beim echten Start.

"Neue Buchung" ist raus: jede Zeile gehoert zu einer Rechnung aus dem
Rechnungstool. Wer eine Buchung braucht, schreibt die Rechnung. Bestehende
Zeilen lassen sich weiter bearbeiten.

Pruefstaende: pruef_doppelte.py (27 Pruefungen am echten Fenster),
pruef_einzelinstanz.py (echte Prozesse). Beide gruen, dazu Abnahme mit den
Augen an Hauptfenster, Liste und Bearbeiten-Dialog.

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 19:47:44 +02:00
parent de71fce64f
commit 894bc81c46
8 changed files with 707 additions and 47 deletions

View file

@ -1,14 +1,18 @@
# -*- coding: utf-8 -*-
"""
Dialog für eine Buchung von Hand (z.B. Barzahlung ohne PDF-Rechnung)
und zum Nachbessern einer eingelesenen Zeile.
Dialog zum Nachbessern einer Zeile, die schon im Journal steht.
Buchungen von Hand ANLEGEN gibt es bewusst nicht mehr: jede Zeile gehört zu
einer Rechnung aus dem Rechnungstool. Wer eine Buchung braucht, schreibt die
Rechnung - sonst meldet das Journal ans Amt, was in keinem Rechnungsordner
steht.
Schließen mit ESC, Speichern mit Enter oder Knopf.
"""
import tkinter as tk
from tkinter import ttk, messagebox
from datetime import datetime, date
from datetime import datetime
from modell import (Buchung, STANDARD_SATZ, steuer_betrag, eur, zahl_aus_text,
zerlege_nummer)
@ -22,14 +26,14 @@ class BuchungDialog(tk.Toplevel):
.loeschen – True, wenn die Buchung gelöscht werden soll
"""
def __init__(self, master, jahr: int, satz: float = STANDARD_SATZ, buchung: Buchung = None):
def __init__(self, master, buchung: Buchung, satz: float = STANDARD_SATZ):
super().__init__(master)
self.ergebnis = None
self.loeschen = False
self._vorlage = buchung
self._jahr = buchung.jahr if buchung else jahr
self._jahr = buchung.jahr
self.title("Buchung bearbeiten" if buchung else "Neue Buchung")
self.title("Buchung bearbeiten")
self.resizable(False, False)
self.transient(master)
self.grab_set()
@ -39,13 +43,12 @@ class BuchungDialog(tk.Toplevel):
self.felder = {}
zeilen = [
("datum", "Rechnungsdatum", (buchung.datum.strftime("%d.%m.%Y") if buchung
else date.today().strftime("%d.%m.%Y"))),
("nummer", "Rechnungsnummer", buchung.rechnungsnummer if buchung else ""),
("nachname", "Nachname", buchung.nachname if buchung else ""),
("naechte", "Übernachtungen", str(buchung.naechte) if buchung else "1"),
("gezahlt", "Gezahlt (Zwischensumme)", f"{buchung.gezahlt:.2f}".replace(".", ",") if buchung else ""),
("satz", "Steuersatz %", f"{(buchung.satz if buchung else satz):g}".replace(".", ",")),
("datum", "Rechnungsdatum", buchung.datum.strftime("%d.%m.%Y")),
("nummer", "Rechnungsnummer", buchung.rechnungsnummer),
("nachname", "Nachname", buchung.nachname),
("naechte", "Übernachtungen", str(buchung.naechte)),
("gezahlt", "Gezahlt (Zwischensumme)", f"{buchung.gezahlt:.2f}".replace(".", ",")),
("satz", "Steuersatz %", f"{(buchung.satz or satz):g}".replace(".", ",")),
]
for i, (schluessel, beschriftung, wert) in enumerate(zeilen):
ttk.Label(rahmen, text=beschriftung + ":").grid(row=i, column=0, sticky="w", pady=3, padx=(0, 10))
@ -63,14 +66,13 @@ class BuchungDialog(tk.Toplevel):
knoepfe.grid(row=len(zeilen) + 1, column=0, columnspan=2, sticky="ew", pady=(6, 0))
ttk.Button(knoepfe, text="Speichern", command=self._speichern) \
.pack(side="left", fill="x", expand=True)
if buchung: # nur beim Bearbeiten – eine neue Buchung gibt es noch nicht zu löschen
ttk.Button(knoepfe, text="Löschen", command=self._loeschen) \
.pack(side="left", fill="x", expand=True, padx=(8, 0))
ttk.Button(knoepfe, text="Löschen", command=self._loeschen) \
.pack(side="left", fill="x", expand=True, padx=(8, 0))
rahmen.columnconfigure(1, weight=1)
self.bind("<Escape>", lambda _e: self.destroy())
self.bind("<Return>", lambda _e: self._speichern())
self.felder["datum" if buchung else "nummer"].focus_set()
self.felder["datum"].focus_set()
self._vorschau()
self.update_idletasks()
@ -135,8 +137,8 @@ class BuchungDialog(tk.Toplevel):
naechte=int(zahl_aus_text(self.felder["naechte"].get()) or 0),
gezahlt=gezahlt,
satz=satz,
quelle=self._vorlage.quelle if self._vorlage else "manuell",
pdf_pfad=self._vorlage.pdf_pfad if self._vorlage else "",
id=self._vorlage.id if self._vorlage else 0,
quelle=self._vorlage.quelle,
pdf_pfad=self._vorlage.pdf_pfad,
id=self._vorlage.id,
)
self.destroy()