Berichtigen: zweiter Knopf im Storno-Popup, alles haengt an einem Flag
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
This commit is contained in:
parent
abb462725f
commit
d3962ec8d1
3 changed files with 220 additions and 30 deletions
150
app.py
150
app.py
|
|
@ -179,6 +179,33 @@ def adresse_aus_pdf(pfad):
|
|||
return daten
|
||||
|
||||
|
||||
def kunde_maengel(kunde):
|
||||
"""Beanstandungen an Name und Anschrift; leere Liste = in Ordnung.
|
||||
|
||||
Prueft nicht nur "irgendwas eingetippt": ein Feld aus Leerzeichen,
|
||||
Bindestrichen oder einem einzelnen Zeichen ist kein Name und keine
|
||||
Anschrift. Vier Ziffern reichen, damit auch oesterreichische und
|
||||
niederlaendische Postleitzahlen durchgehen.
|
||||
"""
|
||||
maengel = []
|
||||
for feld, schluessel in (("Vorname", "vorname"),
|
||||
("Nachname / Firma", "nachname"),
|
||||
("Straße / Nr.", "strasse")):
|
||||
wert = saeubern(kunde.get(schluessel))
|
||||
if not wert:
|
||||
maengel.append(f"{feld}: ist leer")
|
||||
elif buchstaben(wert) < 2:
|
||||
maengel.append(f"{feld}: „{wert}“ ergibt keinen Namen")
|
||||
|
||||
ort = saeubern(kunde.get("plzort"))
|
||||
if not ort:
|
||||
maengel.append("PLZ / Ort: ist leer")
|
||||
elif ziffern(ort) < 4 or buchstaben(ort) < 2:
|
||||
maengel.append(f"PLZ / Ort: „{ort}“ – bitte Postleitzahl UND Ort, "
|
||||
"z. B. 04416 Markkleeberg")
|
||||
return maengel
|
||||
|
||||
|
||||
def name_aufteilen(ganz):
|
||||
"""'Andres Schäfer' -> ('Andres', 'Schäfer'); ein einzelnes Wort wird Nachname."""
|
||||
teile = str(ganz or "").split()
|
||||
|
|
@ -432,6 +459,9 @@ class RechnungsApp(tk.Tk):
|
|||
# Zwischenspeicher: geschriebenes Storno, das noch bestaetigt werden muss.
|
||||
# Solange es hier liegt, gehoert zur naechsten PDF die Storno-Kette.
|
||||
self.storno_stand = None
|
||||
# Zwischenspeicher fuer eine reine Berichtigung (§ 31 Abs. 5 UStDV):
|
||||
# die naechste PDF ist dann ein berichtigtes Blatt zu dieser Rechnung.
|
||||
self.berichtigung_stand = None
|
||||
|
||||
self._apply_theme() # Farben nach Windows hell/dunkel
|
||||
self._build_menu()
|
||||
|
|
@ -825,7 +855,8 @@ class RechnungsApp(tk.Tk):
|
|||
links = ttk.Frame(f)
|
||||
links.pack(side="left")
|
||||
ttk.Button(links, text="PDF erstellen", command=self.pdf_erstellen, width=18).pack(side="left", padx=4)
|
||||
ttk.Button(links, text="Rechnung stornieren", command=self.dlg_storno, width=20).pack(side="left", padx=4)
|
||||
ttk.Button(links, text="Stornieren / berichtigen", command=self.dlg_storno,
|
||||
width=22).pack(side="left", padx=4)
|
||||
ttk.Button(links, text="Zurücksetzen", command=self.reset_formular, width=14).pack(side="left", padx=4)
|
||||
|
||||
# --------------------------------------------------------- Berechnung
|
||||
|
|
@ -886,10 +917,33 @@ class RechnungsApp(tk.Tk):
|
|||
f"Storno {stand['nummer']} zu Rechnung {stand['zu']} liegt bereit.\n"
|
||||
f"Beim Erstellen wird noch einmal gefragt.")
|
||||
self.nummer_entry.configure(state="readonly")
|
||||
elif self.berichtigung_stand:
|
||||
self.storno_hinweis.set(
|
||||
f"Berichtigung der Rechnung {self.berichtigung_stand['zu']}.\n"
|
||||
"Nummer und Beträge bleiben, nur die Kundenangaben ändern sich.")
|
||||
self.nummer_entry.configure(state="readonly")
|
||||
else:
|
||||
self.storno_hinweis.set("")
|
||||
self.nummer_entry.configure(state="normal")
|
||||
|
||||
def _berichtigung_starten(self, meta, out_dir):
|
||||
"""Rechnung ins Formular holen; "PDF erstellen" schreibt das berichtigte Blatt."""
|
||||
nummer = str(meta.get("rechnungsnummer", ""))
|
||||
fehlend = self._formular_aus_meta(meta)
|
||||
self.berichtigung_stand = {"zu": nummer, "datum": str(meta.get("datum") or ""),
|
||||
"meta": meta}
|
||||
# Die Rechnung behaelt ihre Nummer - genau das ist der Unterschied zum Storno.
|
||||
self.r_nummer.set(nummer)
|
||||
self.r_datum.set(str(meta.get("datum") or self.r_datum.get()))
|
||||
self._storno_anzeigen()
|
||||
hinweis = f"\n\nZum Kunden: {fehlend}." if fehlend else ""
|
||||
messagebox.showinfo(
|
||||
"Rechnung geladen",
|
||||
f"Rechnung {nummer} vom {meta.get('datum','')} ist im Formular.\n\n"
|
||||
"Jetzt die Kundenangaben richtigstellen und \"PDF erstellen\" drücken – "
|
||||
"es entsteht ein berichtigtes Blatt zu dieser Rechnung. Die alte Rechnung "
|
||||
f"bleibt liegen.{hinweis}")
|
||||
|
||||
def _storno_verwerfen(self, grund=""):
|
||||
"""Storno rückgängig: PDF löschen, Zähler zurück, Zwischenspeicher leeren.
|
||||
|
||||
|
|
@ -930,7 +984,7 @@ class RechnungsApp(tk.Tk):
|
|||
stornierte = {nummer_normalisieren(d.get("storno_zu"))
|
||||
for d in alle if d.get("art") == "storno"}
|
||||
offen = [d for d in alle
|
||||
if d.get("art") != "storno"
|
||||
if d.get("art") not in ("storno", "berichtigung")
|
||||
and nummer_normalisieren(d.get("rechnungsnummer")) not in stornierte]
|
||||
if not offen:
|
||||
messagebox.showwarning(
|
||||
|
|
@ -946,10 +1000,14 @@ class RechnungsApp(tk.Tk):
|
|||
frm = ttk.Frame(d, padding=12)
|
||||
frm.pack(fill="both", expand=True)
|
||||
|
||||
ttk.Label(frm, justify="left", text=
|
||||
"Die alte Rechnung bleibt unverändert liegen – sie darf weder geändert\n"
|
||||
"noch gelöscht werden. Das Programm schreibt eine Stornorechnung mit\n"
|
||||
"eigener neuer Nummer, die sich auf sie bezieht.").grid(
|
||||
ttk.Label(frm, justify="left", wraplength=470, text=
|
||||
"Die alte Rechnung bleibt unverändert liegen – sie darf weder geändert noch "
|
||||
"gelöscht werden.\n\n"
|
||||
"Storno erstellen: falsche Beträge oder Leistungen. Es entsteht eine "
|
||||
"Stornorechnung mit eigener neuer Nummer und danach die berichtigte Rechnung.\n"
|
||||
"Nur berichtigen: es stimmen nur die Angaben zum Kunden nicht. Die Rechnung "
|
||||
"behält Nummer und Beträge, es wird nur ein berichtigtes Blatt nachgereicht "
|
||||
"(§ 31 Abs. 5 UStDV).").grid(
|
||||
row=0, column=0, columnspan=2, sticky="w", pady=(0, 10))
|
||||
|
||||
ttk.Label(frm, text="Alte Rechnungsnummer").grid(row=1, column=0, sticky="w", pady=3)
|
||||
|
|
@ -970,7 +1028,8 @@ class RechnungsApp(tk.Tk):
|
|||
if not nr:
|
||||
return None, "Nummer eintragen – das Programm sucht die Rechnung."
|
||||
treffer = next((x for x in alle
|
||||
if nummer_normalisieren(x.get("rechnungsnummer")) == nr), None)
|
||||
if nummer_normalisieren(x.get("rechnungsnummer")) == nr
|
||||
and x.get("art") != "berichtigung"), None)
|
||||
if not treffer:
|
||||
return None, f"Zu {nr} liegt in\n{out_dir}\nkeine Rechnung."
|
||||
if treffer.get("art") == "storno":
|
||||
|
|
@ -998,9 +1057,18 @@ class RechnungsApp(tk.Tk):
|
|||
self._storno_anlegen(meta, out_dir)
|
||||
ent.bind("<Return>", anlegen)
|
||||
|
||||
def berichtigen():
|
||||
meta, meldung = suche()
|
||||
if not meta:
|
||||
v_info.set(meldung)
|
||||
return
|
||||
d.destroy()
|
||||
self._berichtigung_starten(meta, out_dir)
|
||||
|
||||
btns = ttk.Frame(frm)
|
||||
btns.grid(row=3, column=0, columnspan=2, pady=(14, 0), sticky="e")
|
||||
ttk.Button(btns, text="Storno erstellen", command=anlegen).pack(side="right", padx=4)
|
||||
ttk.Button(btns, text="Nur berichtigen", command=berichtigen).pack(side="right", padx=4)
|
||||
ttk.Button(btns, text="Abbrechen", command=d.destroy).pack(side="right")
|
||||
frm.columnconfigure(1, weight=1)
|
||||
|
||||
|
|
@ -1112,31 +1180,15 @@ class RechnungsApp(tk.Tk):
|
|||
def kunde_pruefen(self):
|
||||
"""Beanstandungen an den Kundenfeldern; leere Liste = in Ordnung.
|
||||
|
||||
Prueft nicht nur "irgendwas eingetippt": ein Feld aus Leerzeichen,
|
||||
Bindestrichen oder einem einzelnen Zeichen ist kein Name und keine
|
||||
Anschrift. Die Felder werden dabei gesaeubert.
|
||||
Saeubert die Felder und prueft sie mit kunde_maengel().
|
||||
"""
|
||||
for var in (self.k_anrede, self.k_vorname, self.k_name, self.k_zusatz,
|
||||
self.k_strasse, self.k_plzort):
|
||||
var.set(saeubern(var.get()))
|
||||
|
||||
maengel = []
|
||||
for feld, var in (("Vorname", self.k_vorname),
|
||||
("Nachname / Firma", self.k_name),
|
||||
("Straße / Nr.", self.k_strasse)):
|
||||
wert = var.get()
|
||||
if not wert:
|
||||
maengel.append(f"{feld}: ist leer")
|
||||
elif buchstaben(wert) < 2:
|
||||
maengel.append(f"{feld}: „{wert}“ ergibt keinen Namen")
|
||||
|
||||
ort = self.k_plzort.get()
|
||||
if not ort:
|
||||
maengel.append("PLZ / Ort: ist leer")
|
||||
elif ziffern(ort) < 4 or buchstaben(ort) < 2:
|
||||
maengel.append(f"PLZ / Ort: „{ort}“ – bitte Postleitzahl UND Ort, "
|
||||
"z. B. 04416 Markkleeberg")
|
||||
return maengel
|
||||
return kunde_maengel({"vorname": self.k_vorname.get(),
|
||||
"nachname": self.k_name.get(),
|
||||
"strasse": self.k_strasse.get(),
|
||||
"plzort": self.k_plzort.get()})
|
||||
|
||||
def pdf_erstellen(self):
|
||||
r = self.recalc()
|
||||
|
|
@ -1219,6 +1271,48 @@ class RechnungsApp(tk.Tk):
|
|||
kopf["storno_nummer"] = stand["nummer"]
|
||||
out_dir = config.ensure_output_dir(self.cfg)
|
||||
|
||||
# ---- reine Berichtigung: gleiche Nummer, gleiche Betraege, neues Blatt ----
|
||||
ber = self.berichtigung_stand
|
||||
if ber:
|
||||
kopf["art"] = "berichtigung"
|
||||
kopf["berichtigt_zu"] = ber["zu"]
|
||||
kopf["berichtigt_am"] = datetime.date.today().strftime("%d.%m.%Y")
|
||||
if not messagebox.askyesno(
|
||||
"Berichtigte Rechnung erstellen?",
|
||||
f"Zu Rechnung {ber['zu']} vom {ber['datum']} wird ein berichtigtes "
|
||||
"Blatt geschrieben.\n\n"
|
||||
"Nummer, Leistungen und Beträge bleiben unverändert – die alte "
|
||||
"Rechnung bleibt liegen. Das Blatt gehört zum Gast: erst wenn es "
|
||||
"ihm zugegangen ist, ist die Rechnung berichtigt.\n\nJetzt erstellen?",
|
||||
default="yes"):
|
||||
return
|
||||
# Dateiname: "Berichtigte Rechnung", Datum, Nummer der Rechnung. Das
|
||||
# Blatt verbraucht keine eigene Nummer, das Datum haelt mehrere
|
||||
# Berichtigungen zur selben Rechnung auseinander (sortierbar).
|
||||
pfad = os.path.join(
|
||||
out_dir, f"Berichtigte Rechnung_{datetime.date.today():%Y-%m-%d}"
|
||||
f"_{safe_filename(ber['zu'])}.pdf")
|
||||
if os.path.exists(pfad) and not messagebox.askyesno(
|
||||
"Berichtigung von heute ersetzen?",
|
||||
f"Zu Rechnung {ber['zu']} gibt es von heute schon eine Berichtigung:\n"
|
||||
f"{os.path.basename(pfad)}\n\nDurch die neue ersetzen?"):
|
||||
return
|
||||
try:
|
||||
pdf_renderer.render_rechnung(pfad, self.cfg, r, kopf)
|
||||
except Exception as e: # noqa: BLE001 - dem Nutzer den Fehler zeigen
|
||||
messagebox.showerror("Fehler beim Erstellen",
|
||||
f"Berichtigung konnte nicht erstellt werden:\n{e}")
|
||||
print("[berichtigung] FEHLER:", e)
|
||||
return
|
||||
print(f"[berichtigung] erstellt -> {pfad}")
|
||||
self.berichtigung_stand = None
|
||||
self._storno_anzeigen()
|
||||
if messagebox.askyesno("Fertig",
|
||||
f"Berichtigte Rechnung zu {ber['zu']}:\n{pfad}\n\n"
|
||||
"Jetzt öffnen (zum Drucken)?"):
|
||||
self._oeffne_datei(pfad)
|
||||
return
|
||||
|
||||
# Nummer schon vergeben? Dann NICHT drueberschreiben: eine ausgestellte
|
||||
# Rechnung muss unveraendert bestehen bleiben (§ 146 Abs. 4 AO), und jede
|
||||
# Nummer darf es nur einmal geben (§ 14 Abs. 4 Nr. 4 UStG). Also neue Nummer.
|
||||
|
|
|
|||
|
|
@ -223,6 +223,11 @@ def _seite_inhalt(story, st, cfg, rechnung, kopf, gesamtrechnung_betont):
|
|||
info_rows.append(("Rechnung vom:", kopf["storno_datum"]))
|
||||
elif art == "korrektur" and kopf.get("ersetzt"):
|
||||
info_rows.append(("Ersetzt Rechnung:", kopf.get("ersetzt", "")))
|
||||
elif art == "berichtigung":
|
||||
# Die Rechnung behaelt Nummer und Datum - dazu kommt der Tag der
|
||||
# Berichtigung, damit man die Reihenfolge sieht.
|
||||
if kopf.get("berichtigt_am"):
|
||||
info_rows.append(("Berichtigt am:", kopf["berichtigt_am"]))
|
||||
if kopf.get("anreise"):
|
||||
info_rows.append(("Anreise:", kopf.get("anreise", "")))
|
||||
if kopf.get("abreise"):
|
||||
|
|
@ -252,7 +257,8 @@ def _seite_inhalt(story, st, cfg, rechnung, kopf, gesamtrechnung_betont):
|
|||
# in der Ueberschrift. Der Zusatz sagt nur noch, fuer wen das Blatt ist.
|
||||
# Das Wort "Stornorechnung" muss auf dem Blatt stehen, damit der Beleg
|
||||
# eindeutig als Aufhebung erkennbar ist.
|
||||
titel = "Stornorechnung" if art == "storno" else "Rechnungsübersicht"
|
||||
titel = {"storno": "Stornorechnung",
|
||||
"berichtigung": "Berichtigte Rechnung"}.get(art, "Rechnungsübersicht")
|
||||
titel += " (für das Amt)" if gesamtrechnung_betont else ""
|
||||
story.append(Paragraph(titel, st["h"]))
|
||||
|
||||
|
|
@ -382,6 +388,19 @@ def _seite_inhalt(story, st, cfg, rechnung, kopf, gesamtrechnung_betont):
|
|||
f"Diese Stornorechnung hebt die Rechnung {bezug} vollständig auf. "
|
||||
"Bereits gezahlte Beträge werden erstattet oder mit der berichtigten "
|
||||
"Rechnung verrechnet.", st["dank"]))
|
||||
elif art == "berichtigung":
|
||||
# Der Satz ist kein Beiwerk: ein zweites Blatt mit ausgewiesener USt,
|
||||
# das nicht klar als Berichtigung erkennbar ist, kann die Steuer ein
|
||||
# zweites Mal ausloesen (§ 14c UStG).
|
||||
block.append(Paragraph(
|
||||
f"Dieses Blatt berichtigt die Angaben zur Rechnung "
|
||||
f"{kopf.get('berichtigt_zu','')} vom {kopf.get('datum','')} nach "
|
||||
"§ 31 Abs. 5 UStDV. Leistungen, Beträge und Umsatzsteuer sind "
|
||||
"unverändert – es handelt sich <b>nicht</b> um eine zusätzliche "
|
||||
"Leistung und nicht um eine zweite Rechnung. Die Rechnung behält "
|
||||
"ihre Nummer.", st["dank"]))
|
||||
if texte.get("dank1"):
|
||||
block.append(Paragraph(texte["dank1"], st["dank"]))
|
||||
else:
|
||||
if art == "korrektur" and kopf.get("ersetzt"):
|
||||
hinweis = f"Diese Rechnung ersetzt die Rechnung {kopf['ersetzt']}"
|
||||
|
|
@ -422,6 +441,22 @@ def _kenndaten(rechnung, kopf):
|
|||
Steuerrechner summieren dadurch von allein richtig (+100 -100 +120 = 120).
|
||||
"""
|
||||
aufschlag = rechnung.aufschlaege[0] if rechnung.aufschlaege else None
|
||||
if kopf.get("art") == "berichtigung":
|
||||
# Das berichtigte Blatt zeigt dieselben Betraege wie die Rechnung, ist aber
|
||||
# KEINE zweite Einnahme. Es traegt deshalb Nullen: ein Programm, das "art"
|
||||
# nicht kennt, zaehlt dann 0,00 € statt den Umsatz ein zweites Mal.
|
||||
return json.dumps({
|
||||
"quelle": "rechnungstool", "version": 3, "art": "berichtigung",
|
||||
"rechnungsnummer": kopf.get("berichtigt_zu", ""),
|
||||
"berichtigt_zu": kopf.get("berichtigt_zu", ""),
|
||||
"berichtigt_am": kopf.get("berichtigt_am", ""),
|
||||
"datum": kopf.get("datum", ""),
|
||||
"nachname": kopf.get("nachname", ""), "vorname": kopf.get("vorname", ""),
|
||||
"zwischensumme": 0.0, "gesamt": 0.0,
|
||||
"steuer_satz": 0.0, "steuer_betrag": 0.0,
|
||||
"netto_gesamt": 0.0, "ust_gesamt": 0.0, "ust_bloecke": [],
|
||||
"kunde": kopf.get("kunde", {}),
|
||||
}, ensure_ascii=False)
|
||||
daten = {
|
||||
"quelle": "rechnungstool",
|
||||
"version": 3,
|
||||
|
|
|
|||
|
|
@ -290,7 +290,9 @@ import pdf_renderer as P # noqa: E402
|
|||
def alt_rechnung(name, dateiname, kunde_zeilen):
|
||||
"""Schreibt eine Rechnung wie die alte Fassung: ohne kunde/positionen in den Kenndaten."""
|
||||
rr = B.berechne(f.cfg["katalog"], {erste: {"anzahl": 3, "personen": 0}}, f.cfg)
|
||||
kopf = {"rechnungsnummer": "2026-900", "datum": "12.08.2026", "anreise": "", "abreise": "",
|
||||
# Nummer aus dem Dateinamen, damit Kenndaten und Datei zusammenpassen
|
||||
nummer = dateiname.split("_")[1].replace(".pdf", "")
|
||||
kopf = {"rechnungsnummer": nummer, "datum": "12.08.2026", "anreise": "", "abreise": "",
|
||||
"naechte": "", "kunde_zeilen": kunde_zeilen, "nachname": "", "vorname": name}
|
||||
ziel = os.path.join(ordner, dateiname)
|
||||
P.render_rechnung(ziel, f.cfg, rr, kopf)
|
||||
|
|
@ -328,6 +330,65 @@ pruefe("aus dem Rechnungstext gelesen" in meldung, f"Herkunft wird gesagt ({meld
|
|||
for name in ("Rechnung_2026-900.pdf", "Rechnung_2026-901.pdf"):
|
||||
os.remove(os.path.join(ordner, name))
|
||||
|
||||
# ---- 11. Berichtigung: gleicher Dialog, zweiter Knopf -------------------
|
||||
alt3 = alt_rechnung("Andres Schäfer", "Rechnung_2026-910.pdf", ["", "Andres Schäfer", "", "", ""])
|
||||
meta3 = A.kenndaten_lesen(alt3)
|
||||
vorher = sorted(os.listdir(ordner))
|
||||
zaehler_vor_berichtigung = f.cfg["rechnung_zaehler"]
|
||||
|
||||
f._berichtigung_starten(meta3, ordner)
|
||||
pruefe(f.berichtigung_stand is not None, "Flag fuer die Berichtigung gesetzt")
|
||||
pruefe(f.r_nummer.get() == "2026-910", f"Rechnung behaelt ihre Nummer ({f.r_nummer.get()})")
|
||||
pruefe(str(f.nummer_entry.cget("state")) == "readonly", "Nummernfeld gesperrt")
|
||||
pruefe(f.k_vorname.get() == "Andres" and f.k_name.get() == "Schäfer",
|
||||
"Name aufgeteilt im Formular")
|
||||
|
||||
# ohne Anschrift wird auch hier nichts geschrieben
|
||||
warnungen.clear()
|
||||
f.anzahl_vars[erste].set("3")
|
||||
f.update()
|
||||
f.pdf_erstellen()
|
||||
pruefe(warnungen and "Kundenangaben" in warnungen[0], "Pflichtpruefung gilt auch hier")
|
||||
pruefe(sorted(os.listdir(ordner)) == vorher, "nichts geschrieben")
|
||||
|
||||
# Anschrift ergaenzen und erstellen
|
||||
f.k_strasse.set("Bornaer Chaussee 36")
|
||||
f.k_plzort.set("04416 Markkleeberg")
|
||||
antworten["Berichtigte Rechnung erstellen"] = True
|
||||
f.update()
|
||||
f.pdf_erstellen()
|
||||
import datetime as _dt
|
||||
ziel = os.path.join(ordner, f"Berichtigte Rechnung_{_dt.date.today():%Y-%m-%d}_2026-910.pdf")
|
||||
pruefe(os.path.exists(ziel), f"Blatt geschrieben ({os.path.basename(ziel)})")
|
||||
pruefe(f.berichtigung_stand is None, "Flag nach dem Erstellen wieder weg")
|
||||
pruefe(str(f.nummer_entry.cget("state")) == "normal", "Nummernfeld wieder frei")
|
||||
pruefe(os.path.exists(alt3), "die berichtigte Rechnung liegt unangetastet da")
|
||||
pruefe(f.cfg["rechnung_zaehler"] == zaehler_vor_berichtigung,
|
||||
f"Zaehler unveraendert - die Berichtigung verbraucht keine Nummer "
|
||||
f"({f.cfg['rechnung_zaehler']} vs {zaehler_vor_berichtigung})")
|
||||
|
||||
meta_b = A.kenndaten_lesen(ziel)
|
||||
pruefe(meta_b.get("art") == "berichtigung", "art = berichtigung")
|
||||
pruefe(meta_b.get("berichtigt_zu") == "2026-910", "Bezug auf die Rechnung")
|
||||
pruefe(float(meta_b.get("gesamt") or 0) == 0.0,
|
||||
f"Kenndaten tragen 0 - keine zweite Einnahme ({meta_b.get('gesamt')})")
|
||||
|
||||
from pypdf import PdfReader as _PR # noqa: E402
|
||||
text = _PR(ziel).pages[0].extract_text()
|
||||
pruefe("Berichtigte Rechnung" in text, "Ueberschrift steht auf dem Blatt")
|
||||
pruefe("2026-910" in text, "Rechnungsnummer steht drauf")
|
||||
pruefe("nicht" in text and "31 Abs. 5 UStDV" in text, "der Schutzsatz steht drauf")
|
||||
pruefe("04416 Markkleeberg" in text, "berichtigte Anschrift steht drauf")
|
||||
|
||||
liste3, kaputt3 = einnahmen.scanne(ordner, {"ust_saetze": [7, 19]})
|
||||
pruefe(not any("Berichtigte" in os.path.basename(e.pdf_pfad) for e in liste3),
|
||||
"Steuerrechner zaehlt das Blatt nicht als Einnahme")
|
||||
pruefe(not any("Berichtigte" in os.path.basename(p) for p, _ in kaputt3),
|
||||
f"und meldet es nicht als Fehler ({kaputt3})")
|
||||
|
||||
for name in ("Rechnung_2026-910.pdf", os.path.basename(ziel)):
|
||||
os.remove(os.path.join(ordner, name))
|
||||
|
||||
f.destroy()
|
||||
print("---")
|
||||
print(f"Ordner: {ordner}")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue