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
397 lines
18 KiB
Python
397 lines
18 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Pruefstand fuer die Storno-Kette - kompletter Durchlauf in einem Wegwerf-Ordner.
|
|
|
|
Geprueft wird an ECHTEN PDFs, die das Programm selbst schreibt:
|
|
1. Rechnung 2026-001 erstellen
|
|
2. sie stornieren -> Storno 2026-002 mit negativen Betraegen
|
|
3. berichtigte Rechnung 2026-003 mit geaenderter Menge
|
|
4. der Steuerrechner liest den Ordner: 001 + 002 + 003 muss 003 ergeben
|
|
5. Rueckweg: Storno anlegen und verwerfen -> PDF weg, Zaehler zurueck, Original da
|
|
|
|
Die echte config.json wird NICHT angefasst - es wird mit einer Kopie im
|
|
Wegwerf-Ordner gearbeitet.
|
|
"""
|
|
import json
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
|
|
QUELLE = r"C:\claude\rechnungstool"
|
|
sys.path.insert(0, QUELLE)
|
|
|
|
tmp = tempfile.mkdtemp(prefix="storno_pruef_")
|
|
ordner = os.path.join(tmp, "rechnungen")
|
|
os.makedirs(ordner)
|
|
|
|
import config # noqa: E402
|
|
shutil.copy(os.path.join(QUELLE, "config.json"), os.path.join(tmp, "config.json"))
|
|
config.CONFIG_PATH = os.path.join(tmp, "config.json")
|
|
roh = json.load(open(config.CONFIG_PATH, encoding="utf-8"))
|
|
roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0
|
|
roh["output_dir"] = ordner
|
|
json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
|
|
|
import app as A # noqa: E402
|
|
import bestand as BE # noqa: E402
|
|
from tkinter import messagebox # noqa: E402
|
|
|
|
fehler = []
|
|
|
|
|
|
def pruefe(bedingung, text):
|
|
print((" OK | " if bedingung else "FEHLER | ") + text)
|
|
if not bedingung:
|
|
fehler.append(text)
|
|
|
|
|
|
# Dialoge automatisch beantworten; jede Frage wird protokolliert.
|
|
antworten = {}
|
|
gefragt = []
|
|
|
|
|
|
def askyesno(titel, text, **kw):
|
|
gefragt.append(titel)
|
|
for schlue, wert in antworten.items():
|
|
if schlue in titel:
|
|
return wert
|
|
return False
|
|
|
|
|
|
messagebox.askyesno = askyesno
|
|
messagebox.showinfo = lambda *a, **k: None
|
|
warnungen = []
|
|
messagebox.showwarning = lambda *a, **k: warnungen.append(a[0] if a else "")
|
|
messagebox.showerror = lambda *a, **k: print("[fehler]", a[0] if a else "", a[1] if len(a) > 1 else "")
|
|
|
|
f = A.RechnungsApp()
|
|
f.update()
|
|
f.cfg["output_dir"] = ordner
|
|
|
|
# ---- 1. normale Rechnung ------------------------------------------------
|
|
f.k_vorname.set("Anna")
|
|
f.k_name.set("Beispiel")
|
|
f.k_strasse.set("Musterweg 1")
|
|
f.k_plzort.set("12345 Musterstadt")
|
|
f.r_anreise.set("01.08.2026")
|
|
f.r_abreise.set("05.08.2026")
|
|
erste = sorted(f.anzahl_vars)[0]
|
|
f.anzahl_vars[erste].set("4")
|
|
f.update()
|
|
f.pdf_erstellen()
|
|
erste_pdf = os.path.join(ordner, "Rechnung_2026-001_Beispiel.pdf")
|
|
pruefe(os.path.exists(erste_pdf), "Rechnung 2026-001 geschrieben")
|
|
|
|
meta1 = BE.kenndaten_lesen(erste_pdf)
|
|
pruefe(bool(meta1), "Kenndaten der Rechnung lesbar")
|
|
pruefe(meta1.get("version") == 3, f"Metadaten Version 3 (ist {meta1.get('version')})")
|
|
pruefe(meta1.get("art") == "rechnung", "art = rechnung")
|
|
pruefe(len(meta1.get("positionen") or []) == 1, "Positionen stehen in den Kenndaten")
|
|
pruefe(bool(meta1.get("kunde", {}).get("plzort")), "Anschrift steht in den Kenndaten")
|
|
brutto1 = float(meta1["zwischensumme"])
|
|
gesamt1 = float(meta1["gesamt"])
|
|
print(f" Rechnung 001: brutto {brutto1:.2f}, gesamt {gesamt1:.2f}")
|
|
|
|
# ---- 2. stornieren ------------------------------------------------------
|
|
offen = [d for d in BE.rechnungen_im_ordner(ordner) if d.get("art") != "storno"]
|
|
pruefe(len(offen) == 1, "eine stornierbare Rechnung gefunden")
|
|
f._storno_anlegen(offen[0], ordner)
|
|
pruefe(f.storno_stand is not None, "Storno liegt im Zwischenspeicher")
|
|
storno_pdf = f.storno_stand["pfad"] if f.storno_stand else ""
|
|
pruefe(os.path.exists(storno_pdf), f"Storno-PDF geschrieben ({os.path.basename(storno_pdf)})")
|
|
|
|
meta2 = BE.kenndaten_lesen(storno_pdf)
|
|
pruefe(meta2.get("art") == "storno", "art = storno")
|
|
pruefe(meta2.get("storno_zu") == "2026-001", "storno_zu zeigt auf 2026-001")
|
|
pruefe(meta2.get("storno_datum") == meta1.get("datum"), "Datum der alten Rechnung steht drin")
|
|
pruefe(float(meta2["zwischensumme"]) == -brutto1, f"Zwischensumme negativ ({meta2['zwischensumme']})")
|
|
pruefe(float(meta2["gesamt"]) == -gesamt1, f"Gesamt negativ ({meta2['gesamt']})")
|
|
pruefe(all(b["netto"] <= 0 and b["ust"] <= 0 for b in meta2["ust_bloecke"]),
|
|
"alle USt-Bloecke negativ")
|
|
pruefe(f.r_nummer.get() == "2026-003", f"Formular steht auf 2026-003 (ist {f.r_nummer.get()})")
|
|
pruefe(str(f.nummer_entry.cget("state")) == "readonly", "Nummernfeld gesperrt")
|
|
pruefe(f.k_plzort.get() == "12345 Musterstadt", "Anschrift ins Formular uebernommen")
|
|
pruefe(f.anzahl_vars[erste].get() == "4.0", f"Menge uebernommen (ist {f.anzahl_vars[erste].get()})")
|
|
|
|
# ---- 3. berichtigte Rechnung -------------------------------------------
|
|
antworten["Storno wirklich"] = True
|
|
f.anzahl_vars[erste].set("3") # das war der Fehler: 4 statt 3 Naechte
|
|
f.update()
|
|
f.pdf_erstellen()
|
|
dritte = os.path.join(ordner, "Rechnung_2026-003_Beispiel.pdf")
|
|
pruefe(os.path.exists(dritte), "berichtigte Rechnung 2026-003 geschrieben")
|
|
pruefe(f.storno_stand is None, "Zwischenspeicher nach dem Erstellen leer")
|
|
pruefe(str(f.nummer_entry.cget("state")) == "normal", "Nummernfeld wieder frei")
|
|
meta3 = BE.kenndaten_lesen(dritte)
|
|
pruefe(meta3.get("art") == "korrektur", "art = korrektur")
|
|
pruefe(meta3.get("ersetzt") == "2026-001", "ersetzt zeigt auf 2026-001")
|
|
pruefe(meta3.get("storno_nummer") == "2026-002", "storno_nummer zeigt auf 2026-002")
|
|
pruefe(os.path.exists(erste_pdf), "die urspruengliche Rechnung liegt unangetastet da")
|
|
gesamt3 = float(meta3["gesamt"])
|
|
print(f" Rechnung 003: gesamt {gesamt3:.2f}")
|
|
|
|
# ---- 4. was sieht der Steuerrechner? ------------------------------------
|
|
sys.path.insert(0, r"C:\claude\steuerrechner")
|
|
import einnahmen # noqa: E402
|
|
liste, kaputt = einnahmen.scanne(ordner, {"ust_saetze": [7, 19]})
|
|
pruefe(len(liste) == 3, f"Steuerrechner liest 3 Belege (liest {len(liste)})")
|
|
pruefe(not kaputt, f"kein Beleg uebersprungen ({kaputt})")
|
|
pruefe(not any(e.unsicher for e in liste),
|
|
"kein Beleg als unsicher markiert")
|
|
summe = round(sum(e.gesamt for e in liste), 2)
|
|
netto = round(sum(e.netto for e in liste), 2)
|
|
ust = round(sum(e.ust for e in liste), 2)
|
|
pruefe(abs(summe - gesamt3) < 0.005,
|
|
f"Jahressumme = nur die berichtigte Rechnung ({summe:.2f} vs {gesamt3:.2f})")
|
|
print(f" Summe {summe:.2f} | Netto {netto:.2f} | USt {ust:.2f}")
|
|
|
|
# ---- 5. Rueckweg: Storno anlegen und verwerfen --------------------------
|
|
antworten["Storno wirklich"] = False
|
|
offen = [d for d in BE.rechnungen_im_ordner(ordner)
|
|
if d.get("art") == "korrektur"]
|
|
f._storno_anlegen(offen[0], ordner)
|
|
storno2 = f.storno_stand["pfad"]
|
|
zaehler_mit_storno = f.cfg["rechnung_zaehler"]
|
|
pruefe(os.path.exists(storno2), "zweites Storno geschrieben")
|
|
f.anzahl_vars[erste].set("2")
|
|
f.update()
|
|
f.pdf_erstellen() # -> Frage wird mit Nein beantwortet
|
|
pruefe(not os.path.exists(storno2), "verworfenes Storno-PDF ist geloescht")
|
|
pruefe(f.storno_stand is None, "Zwischenspeicher geleert")
|
|
pruefe(f.cfg["rechnung_zaehler"] == zaehler_mit_storno - 1,
|
|
f"Zaehler zurueckgesetzt ({f.cfg['rechnung_zaehler']} statt {zaehler_mit_storno})")
|
|
pruefe(os.path.exists(dritte), "die Rechnung 2026-003 steht unveraendert da")
|
|
pruefe(len([n for n in os.listdir(ordner) if n.endswith('.pdf')]) == 3,
|
|
"es liegen wieder genau 3 PDFs im Ordner")
|
|
|
|
# ---- 6. Suche im Storno-Dialog (Nummer eintippen, nicht auswaehlen) -----
|
|
import tkinter as tk # noqa: E402
|
|
from tkinter import ttk # noqa: E402
|
|
|
|
f.dlg_storno()
|
|
f.update()
|
|
dialog = [w for w in f.winfo_children() if isinstance(w, tk.Toplevel)][-1]
|
|
feld = None
|
|
schild = None
|
|
for rahmen in dialog.winfo_children():
|
|
for w in rahmen.winfo_children():
|
|
if isinstance(w, ttk.Entry):
|
|
feld = w
|
|
elif isinstance(w, ttk.Label) and w.cget("textvariable"):
|
|
schild = w
|
|
pruefe(feld is not None and schild is not None, "Eingabefeld und Meldezeile im Dialog da")
|
|
|
|
|
|
def tippe(text):
|
|
feld.delete(0, "end")
|
|
feld.insert(0, text)
|
|
feld.event_generate("<KeyRelease>")
|
|
f.update()
|
|
return f.getvar(schild.cget("textvariable"))
|
|
|
|
|
|
meldung = tippe("2026-003")
|
|
pruefe("Der Storno lautet" in meldung, f"gueltige Nummer gefunden: {meldung.splitlines()[0]}")
|
|
meldung = tippe("2026-3")
|
|
pruefe("2026-003" in meldung, "kurze Schreibweise 2026-3 findet 2026-003")
|
|
meldung = tippe("2026-001")
|
|
pruefe("bereits storniert" in meldung, "schon stornierte Rechnung wird abgelehnt")
|
|
meldung = tippe("2026-002")
|
|
pruefe("selbst eine Stornorechnung" in meldung, "Storno selbst ist nicht stornierbar")
|
|
meldung = tippe("2026-999")
|
|
pruefe("keine Rechnung" in meldung, "unbekannte Nummer wird abgelehnt")
|
|
meldung = tippe("")
|
|
pruefe("Nummer eintragen" in meldung, "leeres Feld sagt was zu tun ist")
|
|
dialog.destroy()
|
|
|
|
# ---- 7. Pflichtfelder: ohne Anschrift wird nichts geschrieben -----------
|
|
vorher = sorted(os.listdir(ordner))
|
|
warnungen.clear()
|
|
f.k_strasse.set("")
|
|
f.k_plzort.set("")
|
|
f.anzahl_vars[erste].set("2")
|
|
f.update()
|
|
f.pdf_erstellen()
|
|
pruefe(warnungen and "Kundenangaben fehlen" in warnungen[0],
|
|
f"fehlende Anschrift wird gemeldet ({warnungen})")
|
|
pruefe(sorted(os.listdir(ordner)) == vorher, "ohne Anschrift wurde keine PDF geschrieben")
|
|
|
|
warnungen.clear()
|
|
f.k_vorname.set("")
|
|
f.k_strasse.set("Musterweg 1")
|
|
f.k_plzort.set("12345 Musterstadt")
|
|
f.update()
|
|
f.pdf_erstellen()
|
|
pruefe(sorted(os.listdir(ordner)) == vorher, "auch ohne Vornamen wird nichts geschrieben")
|
|
|
|
f.k_vorname.set("Anna")
|
|
f.update()
|
|
f.pdf_erstellen()
|
|
pruefe(len(os.listdir(ordner)) == len(vorher) + 1,
|
|
"mit allen Pflichtfeldern entsteht die PDF")
|
|
|
|
# ---- 8. Was zaehlt als "richtig ausgefuellt"? ---------------------------
|
|
def kunde_setzen(vor, nach, strasse, ort):
|
|
f.k_vorname.set(vor)
|
|
f.k_name.set(nach)
|
|
f.k_strasse.set(strasse)
|
|
f.k_plzort.set(ort)
|
|
return f.kunde_pruefen()
|
|
|
|
|
|
faelle_schlecht = [
|
|
(" ", "Meier", "Musterweg 1", "12345 Musterstadt", "Vorname nur Leerzeichen"),
|
|
("Anna", " ", "Musterweg 1", "12345 Musterstadt", "Nachname nur Leerzeichen"),
|
|
("Anna", "x", "Musterweg 1", "12345 Musterstadt", "Nachname ein Buchstabe"),
|
|
("Anna", "123", "Musterweg 1", "12345 Musterstadt", "Nachname nur Ziffern"),
|
|
("Anna", "Meier", "-", "12345 Musterstadt", "Strasse nur ein Strich"),
|
|
("Anna", "Meier", "Musterweg 1", "12345", "nur PLZ, kein Ort"),
|
|
("Anna", "Meier", "Musterweg 1", "Musterstadt", "nur Ort, keine PLZ"),
|
|
("Anna", "Meier", "Musterweg 1", " ", "Ort nur Leerzeichen"),
|
|
]
|
|
for vor, nach, strasse, ort, was in faelle_schlecht:
|
|
pruefe(bool(kunde_setzen(vor, nach, strasse, ort)), f"abgelehnt: {was}")
|
|
|
|
faelle_gut = [
|
|
("Anna", "Meier", "Musterweg 1", "12345 Musterstadt", "normaler Fall"),
|
|
("Li", "Ng", "Hauptstr. 2", "04416 Markkleeberg", "kurze Namen"),
|
|
("Jan", "de Vries", "Kerkstraat 12", "1234 AB Amsterdam", "Niederlande, 4 Ziffern"),
|
|
("Hans", "Huber", "Seeweg 3", "5020 Salzburg", "Oesterreich, 4 Ziffern"),
|
|
("Anna", "Camping Nord GmbH", "Am Deich 7a", "12345 Musterstadt", "Firma"),
|
|
]
|
|
for vor, nach, strasse, ort, was in faelle_gut:
|
|
m = kunde_setzen(vor, nach, strasse, ort)
|
|
pruefe(not m, f"angenommen: {was} {m if m else ''}")
|
|
|
|
# Saeubern: Rand- und Mehrfachleerzeichen verschwinden aus den Feldern
|
|
kunde_setzen(" Anna ", " von Meier ", " Musterweg 1 ", " 12345 Musterstadt ")
|
|
pruefe(f.k_vorname.get() == "Anna", f"Vorname getrimmt ({f.k_vorname.get()!r})")
|
|
pruefe(f.k_name.get() == "von Meier", f"Doppelleerzeichen weg ({f.k_name.get()!r})")
|
|
pruefe(f.k_plzort.get() == "12345 Musterstadt", f"Ort gesaeubert ({f.k_plzort.get()!r})")
|
|
|
|
# ---- 9. Strasse ohne Hausnummer: fragen, nicht sperren ------------------
|
|
kunde_setzen("Anna", "Beispiel", "Am Seeufer", "12345 Musterstadt")
|
|
pruefe(not f.kunde_pruefen(), "Anschrift ohne Hausnummer ist kein Mangel")
|
|
vorher = sorted(os.listdir(ordner))
|
|
gefragt.clear()
|
|
antworten["Hausnummer"] = False # Nutzer sagt: nein, doch nicht
|
|
f.anzahl_vars[erste].set("2")
|
|
f.update()
|
|
f.pdf_erstellen()
|
|
pruefe(any("Hausnummer" in t for t in gefragt), f"nach der Hausnummer wird gefragt ({gefragt})")
|
|
pruefe(sorted(os.listdir(ordner)) == vorher, "bei Nein wird nichts geschrieben")
|
|
antworten["Hausnummer"] = True # Nutzer sagt: ja, so ist es richtig
|
|
f.pdf_erstellen()
|
|
pruefe(len(os.listdir(ordner)) == len(vorher) + 1, "bei Ja entsteht die PDF trotzdem")
|
|
|
|
# ---- 10. Altrechnung im Muster "alles in der Vornamen-Zeile" -----------
|
|
import berechnung as B # noqa: E402
|
|
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)
|
|
# 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)
|
|
return ziel
|
|
|
|
|
|
# a) mit Anschrift im Text, Name komplett in der Vornamen-Zeile
|
|
alt = alt_rechnung("Andres Schäfer", "Rechnung_2026-900.pdf",
|
|
["Herr", "Andres Schäfer", "c/o Stellplatz 12",
|
|
"Bornaer Chaussee 36", "04416 Markkleeberg"])
|
|
meta_alt = BE.kenndaten_lesen(alt)
|
|
pruefe(not meta_alt.get("kunde"), "Altrechnung hat keine Kundendaten in den Kenndaten")
|
|
kunde, woher = BE.kundendaten_ermitteln(meta_alt)
|
|
pruefe(kunde["vorname"] == "Andres", f"Vorname aufgeteilt ({kunde['vorname']!r})")
|
|
pruefe(kunde["nachname"] == "Schäfer", f"Nachname aufgeteilt ({kunde['nachname']!r})")
|
|
pruefe(kunde.get("anrede") == "Herr", f"Anrede aus dem Text ({kunde.get('anrede')!r})")
|
|
pruefe(kunde.get("strasse") == "Bornaer Chaussee 36", f"Straße aus dem Text ({kunde.get('strasse')!r})")
|
|
pruefe(kunde.get("plzort") == "04416 Markkleeberg", f"PLZ/Ort aus dem Text ({kunde.get('plzort')!r})")
|
|
pruefe(kunde.get("zusatz") == "c/o Stellplatz 12", f"Zusatz aus dem Text ({kunde.get('zusatz')!r})")
|
|
|
|
# b) sein echter Fall: NUR der Name, keine Anschrift
|
|
alt2 = alt_rechnung("Andres Schäfer", "Rechnung_2026-901.pdf", ["", "Andres Schäfer", "", "", ""])
|
|
kunde2, _ = BE.kundendaten_ermitteln(BE.kenndaten_lesen(alt2))
|
|
pruefe(kunde2["nachname"] == "Schäfer", "auch ohne Anschrift wird der Name aufgeteilt")
|
|
pruefe(not kunde2.get("plzort"), "keine Anschrift erfunden")
|
|
|
|
# c) Formular fuellen: was fehlt, wird benannt
|
|
meldung = f._formular_aus_meta(BE.kenndaten_lesen(alt2))
|
|
pruefe(f.k_vorname.get() == "Andres" and f.k_name.get() == "Schäfer",
|
|
"Formular bekommt Vor- und Nachnamen getrennt")
|
|
pruefe("Straße" in meldung and "PLZ" in meldung and "Nachname vom Vornamen" in meldung, f"fehlende Anschrift wird benannt ({meldung})")
|
|
meldung = f._formular_aus_meta(BE.kenndaten_lesen(alt))
|
|
pruefe(f.k_plzort.get() == "04416 Markkleeberg", "Anschrift landet im Formular")
|
|
pruefe("aus dem Rechnungstext gelesen" in meldung, f"Herkunft wird gesagt ({meldung})")
|
|
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 = BE.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 = BE.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}")
|
|
print("alles gruen" if not fehler else f"{len(fehler)} Fehler: {fehler}")
|
|
sys.exit(1 if fehler else 0)
|