diff --git a/AUFGABE_BEWEISKETTE_PRUEFBAR.md b/AUFGABE_BEWEISKETTE_PRUEFBAR.md new file mode 100644 index 0000000..7b0a57b --- /dev/null +++ b/AUFGABE_BEWEISKETTE_PRUEFBAR.md @@ -0,0 +1,71 @@ +# Beweiskette: von Anfang bis Ende prüfbar + +Seine Vorgabe vom 06.09.2026, wörtlich: + +> „ich will für den prüfer sichtbar machen: ich wollte das so. die software dokumentiert das, +> und damit wird es nachvollziehbar, weil es einen checkmark nun gibt in der sqlite" +> — „das soll alles von anfang bis ende prüfbar sein" +> — „damit denke ich überfüllen wir sogar das gobd, sogar mehr, weil es nun eine beweiskette +> darstellt" + +**Grundgedanke:** GoBD verlangt Nachvollziehbarkeit und Unveränderbarkeit — aber **keinen +dokumentierten Freigabe-Entscheid**. Den gibt es jetzt zusätzlich. Damit die Kette trägt, muss +jedes Glied für sich prüfbar sein und keines allein tragen. + +--- + +## Schritt 1 — Freigabe vor dem Beleg ✅ ERLEDIGT (Commit 95a9063) + +- Rückfrage vor dem Schreiben, Text nennt § 14 UStG, § 14 Abs. 4 Nr. 4 UStG, § 147 Abs. 3 AO, + § 146 Abs. 4 AO; Angebot bekommt einen eigenen Text (ausdrücklich **keine** Rechnung). +- Storno/Korrektur und Berichtigung behalten ihre eigene Rückfrage — nicht verdoppelt. +- Spalte `freigabe` (ISO-Zeitstempel) in `nummern` im gemeinsamen Nummernbuch, mit + ALTER-TABLE-Migration. **Alte Zeilen bleiben leer** — dort wurde nie gefragt. +- `pruef_freigabe.py` prüft beide Richtungen. Alle 16 Prüfstände grün. + +--- + +## Schritt 2 — Freigabe zusätzlich in die PDF-Metadaten ⬜ OFFEN + +**Warum das nötig ist:** `gemeinsam.py` sagt über die SQLite selbst: *„Die Datei ist ein REGISTER, +keine zweite Buchhaltung: die Wahrheit bleiben die Rechnungs-PDFs. Geht das Buch verloren, fehlt +keine Rechnung — es muss nur neu gefüllt werden."* Ein solcher Neuaufbau kennt die +Freigabe-Zeitstempel **nicht**. Damit hinge die Beweiskette an der schwächsten Datei. + +**Zu tun:** `freigabe` in `pdf_renderer._kenndaten()` aufnehmen (die maschinenlesbaren Kenndaten im +`/Subject`, die das Steuerjournal ohnehin liest). Dann steht der Zeitstempel an zwei Stellen und +übersteht den Verlust des Registers. + +**Prüfstand:** `pruef_freigabe.py` erweitern — nach dem Ja muss `bestand.kenndaten_lesen(pdf)` den +Zeitstempel liefern, und er muss mit dem in der SQLite übereinstimmen. + +--- + +## Schritt 3 — Steuerprüfungs-Ausdruck im Beherbergungssteuer-Tool ⬜ OFFEN + +Sein Wortlaut: *„wir müssen im kassenbuch, was das beherbergungstool ist, einen button in die +einstellungen machen: steuerprüfungs-ausdruck. damit bekommt der prüfer einen ausdruck aus der db +und kann damit das in der tabelle prüfen auf richtigkeit, oder halt wenn der typ das als +papierversion hat."* + +**Zu tun:** Knopf in den Einstellungen, der aus der Datenbank ein PDF erzeugt, das ein Prüfer gegen +die Belege halten kann. + +**Vor dem Bauen zu klären:** +- Welcher Zeitraum? (Jahr, Monat, frei wählbar) +- Welche Spalten? Mindestens: Nummer, Datum, Art, Beträge, Vorgang/Storno-Bezug, **Freigabe** +- Wie wird gezeigt, dass nichts fehlt? Lückenlosigkeit der Nummern ist der Kern jeder Prüfung — + Lücken müssen **sichtbar** sein, nicht stillschweigend übersprungen. +- Summen je Zeitraum, damit der Prüfer gegenrechnen kann. +- Kennzeichnung als Auswertung, **nicht** als Beleg (sonst dasselbe Problem wie beim Amt-Blatt). + +⚠️ **Nicht verwechseln:** Der bestehende „Bericht fürs Amt" ist die **Steueranmeldung** nach +kommunaler Satzung. Der Prüfungs-Ausdruck ist etwas anderes: eine Auswertung für die +Betriebsprüfung. Beide bleiben nebeneinander bestehen. + +--- + +## Reihenfolge + +Schritt 2 zuerst — er schließt eine Lücke in dem, was gerade gebaut wurde, und ist klein. +Schritt 3 danach, mit eigenem Plan, weil dort Entscheidungen offen sind. diff --git a/README.md b/README.md index 5ed8d4a..389fec8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,30 @@ # Rechnungstool Campinghof Bartl +> ### Umbenennung, 06.09.2026 +> +> Die Repositories heissen jetzt: +> +> | bisher | jetzt | +> |---|---| +> | `beherbergungssteuer` | **`Steuerrechnungstool`** - https://git.pcore.de/TheMockTv/Steuerrechnungstool | +> | `rechnungstool` | unveraendert - https://git.pcore.de/TheMockTv/rechnungstool | +> +> Grund: „Beherbergungssteuer“ beschrieb nur noch einen Teil dessen, was das Programm tut. +> Alte Links leitet Forgejo weiter, bestehende Klone laufen weiter - das `origin` sollte +> trotzdem umgestellt werden: +> +> ``` +> git remote set-url origin https://git.pcore.de/TheMockTv/Steuerrechnungstool.git +> ``` +> +> **Fehler und Wünsche** bitte als Issue im jeweiligen Repository melden - die Adresse steht +> auch im Programm unter *Hilfe → Info* und im Hinweis beim Start. +> +> ⚠️ Der **lokale Ordner** heißt weiterhin `C:\claude\beherbergungssteuer`. Der Pfad steht fest +> verdrahtet in mehreren Prüfständen und in den Spec-Dateien - das Umbenennen ist ein +> eigener Schritt. + + Desktop-Programm (Python/Tkinter), das die alte Excel-Rechnungsvorlage ersetzt: Kundendaten + Anzahl eingeben → druckfertige **PDF**-Rechnung im Campinghof-Layout. diff --git a/app.py b/app.py index ff41f8f..a6a644c 100644 --- a/app.py +++ b/app.py @@ -31,6 +31,7 @@ import api import config import berechnung import gemeinsam +import hinweis import ordnerwahl import pdf_renderer from helfer import ANREDEN, kunde_maengel, parse_date, parse_num, saeubern, safe_filename, ziffern @@ -117,15 +118,10 @@ class RechnungsApp(ThemeMixin, MeldungMixin, KorrekturMixin, EinstellungenMixin, # es morgen ohne berichtigte Rechnung im Ordner und keiner weiss warum. self.protocol("WM_DELETE_WINDOW", self._beenden) - # Titelleiste dunkel (nach dem Mappen des Fensters) - self.after(60, lambda: self._dark_titlebar(self)) - # Beim ersten Start nach dem Speicherort fragen (wenn noch keiner gesetzt ist) - self.after(250, self._frage_speicherort_falls_noetig) - # ... und danach nach dem gemeinsamen Ordner fuer das Nummernbuch. - self.after(400, self._frage_gemeinsam_falls_noetig) - # ... und danach einmalig nach dem Stand der Rechnungsnummer. - self.after(600, self._frage_nummer_falls_noetig) - self.after(500, self._push_abholen) + # NICHTS laeuft an, bevor der Erprobungshinweis bestaetigt ist. Wer ihn + # ablehnt, soll gar nicht erst arbeiten koennen - und es soll auch kein + # halb gestartetes Programm hinter dem Fenster stehenbleiben. + self.after(0, self._hinweis_pruefen) # ---------------------------------------------------------------- Menue @@ -646,6 +642,40 @@ class RechnungsApp(ThemeMixin, MeldungMixin, KorrekturMixin, EinstellungenMixin, ttk.Button(links, text="Zurücksetzen", command=self.reset_formular).pack(side="left", padx=6) # --------------------------------------------------- Gemeinsames Buch + # ------------------------------------------------- Erprobungshinweis + def _hinweis_pruefen(self): + """Hinweis auf die Erprobungsfassung. Ablehnen beendet das Programm. + + Erst wenn er bestaetigt ist, faehrt der Rest des Starts an - sonst + stuenden beim Ablehnen noch Timer in der Warteschlange, die auf ein + Fenster zugreifen, das es nicht mehr gibt. + """ + if not hinweis.angenommen(self.cfg): + print(f"[hinweis] Kenntnisnahme noch offen (Fassung {hinweis.FASSUNG})") + if not self.frage(hinweis.TITEL, hinweis.text("rechnungstool"), + ja="Ja, gelesen", nein="Nein, beenden", default="no"): + print("[hinweis] abgelehnt - Programm wird beendet") + self.destroy() + return + zeile = hinweis.vermerken(self.cfg) + try: + config.save_config(self.cfg) + except OSError as e: # noqa: BLE001 + print("[hinweis] konnte nicht gespeichert werden:", e) + print("[hinweis]", zeile) + self._start_fortsetzen() + + def _start_fortsetzen(self): + """Der uebrige Start - laeuft erst nach der Kenntnisnahme an.""" + self.after(0, lambda: self._dark_titlebar(self)) + # Beim ersten Start nach dem Speicherort fragen (wenn noch keiner gesetzt ist) + self.after(200, self._frage_speicherort_falls_noetig) + # ... und danach nach dem gemeinsamen Ordner fuer das Nummernbuch. + self.after(350, self._frage_gemeinsam_falls_noetig) + # ... und danach einmalig nach dem Stand der Rechnungsnummer. + self.after(550, self._frage_nummer_falls_noetig) + self.after(450, self._push_abholen) + def _buch_oeffnen(self): """Nummernbuch aus dem eingestellten Ordner - oder None.""" ordner = str(self.cfg.get("gemeinsam_ordner", "") or "") diff --git a/einstellungen.py b/einstellungen.py index f39eb25..f63155d 100644 --- a/einstellungen.py +++ b/einstellungen.py @@ -11,6 +11,7 @@ import tkinter as tk from tkinter import ttk, messagebox, filedialog, colorchooser import berechnung +import hinweis import theme import bilder import config @@ -57,6 +58,17 @@ class EinstellungenMixin: text="Daten eingeben, PDF erstellen, drucken.\n" "Steuersätze, Preise und Firmendaten lassen sich\n" "im Menü Einstellungen anpassen.").pack(anchor="w", pady=(8, 12)) + # Der Erprobungshinweis steht hier dauerhaft - mit der Signaturzeile + # darunter. Sonst waere die Kenntnisnahme ein Klick, den spaeter + # niemand mehr nachlesen kann. + ttk.Separator(frm, orient="horizontal").pack(fill="x", pady=(4, 10)) + ttk.Label(frm, justify="left", wraplength=430, + text=hinweis.text("rechnungstool")).pack(anchor="w") + _sig = hinweis.signatur(self.cfg) + if _sig: + ttk.Label(frm, justify="left", wraplength=430, foreground=self.col_accent, + text=_sig).pack(anchor="w", pady=(10, 0)) + ttk.Separator(frm, orient="horizontal").pack(fill="x", pady=(10, 10)) ttk.Label(frm, text=config.MARKE, foreground=self.col_muted).pack(anchor="w") ttk.Button(frm, text="OK", command=d.destroy, width=10).pack(anchor="e", pady=(14, 0)) d.bind("", lambda e: d.destroy()) diff --git a/hinweis.py b/hinweis.py new file mode 100644 index 0000000..170b3ee --- /dev/null +++ b/hinweis.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +"""Der Hinweis auf die Erprobungsfassung - und die Kenntnisnahme des Nutzers. + +WARUM ES DAS GIBT +----------------- +Beide Programme schreiben Belege, die steuerlich zaehlen. Solange sie erprobt +werden, muss der Betreiber wissen, woran er ist: dass es eine Erprobungsfassung +ist, dass fuer falsch ausgestellte Belege nicht gehaftet wird, und dass er jeden +Beleg vor dem Herausgeben SELBST ansehen muss - dafuer gibt es die Vorschau. + +Sein Wortlaut (06.09.2026): + + "bei jedem oeffnen, solange wir die testen, ein popup, dass dies eine + experimentelle software ist und ich keine haftung fuer irgendwelche falsch + ausgestellten uebernehme. der user muss selbst pruefen ueber den vorschau + button auf beiden tools, ob das alles seine richtigkeit hat. wenn nicht, + auf dem link eine issue erstellen. und das alles auch noch mal in die info + mit signatur: user hat dies gelesen und zur kenntnis genommen. wenn er nein + drueckt oder ablehnt, schliesst sich die software und geht beim naechsten + start wieder mit dem selben auf, bis er auf ja drueckt." + +WIE ES WIRKT +------------ +* Beim Start kommt der Hinweis, BEVOR gearbeitet werden kann. +* "Nein" beendet das Programm. Es wird nichts geschrieben, nichts gespeichert. +* "Ja" wird mit Benutzername, Zeitpunkt und Fassung in der Konfiguration + vermerkt - das ist die Signatur. Danach kommt der Hinweis nicht mehr. +* Aendert sich der TEXT (also FASSUNG), wird erneut gefragt. Eine Zustimmung + gilt fuer das, was dagestanden hat, nicht fuer alles Kuenftige. +* Unter "Info" steht derselbe Text dauerhaft, mit der Signaturzeile darunter. + +Diese Datei liegt in BEIDEN Programmen gleich. Wer sie aendert, kopiert sie +hinueber - sonst steht in zwei Programmen zweierlei. +""" + +import datetime +import getpass + +# Fassung des HINWEISTEXTES, nicht des Programms. Wird der Text geaendert, +# gehoert dieses Datum mitgeaendert - dann wird die Kenntnisnahme neu eingeholt. +FASSUNG = "2026-09-06" + +# Wo Fehler gemeldet werden. Je Programm eine eigene Adresse. +# +# Das frueher "Beherbergungssteuer" genannte Programm heisst seit dem +# 06.09.2026 STEUERRECHNUNGSTOOL - so wie sein Repository. Der lokale Ordner +# heisst noch beherbergungssteuer: der Pfad steht in mehreren Pruefstaenden und +# in den Spec-Dateien fest, das ist ein eigener Schritt. +ISSUES = { + "rechnungstool": "https://git.pcore.de/TheMockTv/rechnungstool", + "steuerrechnungstool": "https://git.pcore.de/TheMockTv/Steuerrechnungstool", +} + +TITEL = "Erprobungsfassung – bitte lesen" + + +def text(programm): + """Der Hinweistext. `programm` ist ein Schluessel aus ISSUES.""" + url = ISSUES.get(programm, "") + return ( + "Diese Software ist eine ERPROBUNGSFASSUNG.\n\n" + "Sie wird noch getestet. Für Belege, die falsch ausgestellt werden, " + "wird keine Haftung übernommen.\n\n" + "Prüfen Sie jeden Beleg selbst, bevor Sie ihn herausgeben oder " + "verbuchen. Nutzen Sie dafür die Vorschau – in diesem Programm und im " + "jeweils anderen. Stimmt etwas nicht, melden Sie es bitte als Fehler " + "unter:\n\n" + f" {url}\n\n" + "Mit „Ja\" bestätigen Sie, dass Sie diesen Hinweis gelesen und zur " + "Kenntnis genommen haben.\n" + "Mit „Nein\" wird das Programm beendet; der Hinweis erscheint beim " + "nächsten Start erneut." + ) + + +def _stand(cfg): + return (str(cfg.get("hinweis_fassung", "") or ""), + str(cfg.get("hinweis_zeit", "") or ""), + str(cfg.get("hinweis_benutzer", "") or "")) + + +def angenommen(cfg): + """Hat der Nutzer GENAU DIESE Fassung schon zur Kenntnis genommen?""" + fassung, zeit, benutzer = _stand(cfg) + return fassung == FASSUNG and bool(zeit) and bool(benutzer) + + +def vermerken(cfg): + """Die Kenntnisnahme festhalten. Gibt die Signaturzeile zurueck.""" + cfg["hinweis_fassung"] = FASSUNG + cfg["hinweis_zeit"] = datetime.datetime.now().isoformat(timespec="seconds") + try: + cfg["hinweis_benutzer"] = getpass.getuser() + except Exception: # noqa: BLE001 - ohne Benutzernamen ist es kein Grund zu scheitern + cfg["hinweis_benutzer"] = "unbekannt" + return signatur(cfg) + + +def signatur(cfg): + """Die Zeile fuer den Info-Dialog - oder '' , wenn nichts vorliegt.""" + fassung, zeit, benutzer = _stand(cfg) + if not (fassung and zeit and benutzer): + return "" + try: + wann = datetime.datetime.fromisoformat(zeit).strftime("%d.%m.%Y um %H:%M") + except ValueError: + wann = zeit + return (f"Gelesen und zur Kenntnis genommen von {benutzer} " + f"am {wann} (Fassung {fassung}).") diff --git a/pruef_angebot.py b/pruef_angebot.py index 320dcd6..3e11e7c 100644 --- a/pruef_angebot.py +++ b/pruef_angebot.py @@ -87,6 +87,8 @@ def askyesno(titel, text, **kw): # Die Freigabe vor dem Beleg (§ 14 UStG) wird bewusst bejaht - geprueft wird hier # der Ablauf DANACH. Dass ein "Nein" keinen Beleg erzeugt, prueft pruef_freigabe.py. antworten["jetzt erstellen?"] = True +# Der Erprobungshinweis wird hier bejaht - geprueft wird er in pruef_hinweis.py. +antworten["Erprobungsfassung"] = True messagebox.askyesno = askyesno messagebox.showinfo = lambda *a, **k: None warnungen = [] diff --git a/pruef_api.py b/pruef_api.py index bf62f60..0afaf76 100644 --- a/pruef_api.py +++ b/pruef_api.py @@ -75,6 +75,8 @@ def pruefe(bedingung, text): messagebox.showinfo = messagebox.showwarning = messagebox.showerror = lambda *a, **k: None +# Der Erprobungshinweis wird von der pauschalen Antwort unten mit erfasst. +# Geprueft wird er eigens in pruef_hinweis.py. messagebox.askyesno = lambda *a, **k: True f = A.RechnungsApp() diff --git a/pruef_dialoge.py b/pruef_dialoge.py index ec07f7f..32070f5 100644 --- a/pruef_dialoge.py +++ b/pruef_dialoge.py @@ -41,6 +41,8 @@ from tkinter import messagebox # noqa: E402 messagebox.showinfo = lambda *a, **k: None messagebox.showwarning = lambda *a, **k: None messagebox.showerror = lambda *a, **k: None +# Der Erprobungshinweis wird von der pauschalen Antwort unten mit erfasst. +# Geprueft wird er eigens in pruef_hinweis.py. messagebox.askyesno = lambda *a, **k: True # Sicherheitsfrage bejahen diff --git a/pruef_doppelte_leistung.py b/pruef_doppelte_leistung.py index 78b13d3..841ff5d 100644 --- a/pruef_doppelte_leistung.py +++ b/pruef_doppelte_leistung.py @@ -80,6 +80,8 @@ def askyesno(titel, text, **kw): # Die Freigabe vor dem Beleg (§ 14 UStG) wird bewusst bejaht - geprueft wird hier # der Ablauf DANACH. Dass ein "Nein" keinen Beleg erzeugt, prueft pruef_freigabe.py. antworten["jetzt erstellen?"] = True +# Der Erprobungshinweis wird hier bejaht - geprueft wird er in pruef_hinweis.py. +antworten["Erprobungsfassung"] = True messagebox.askyesno = askyesno messagebox.showinfo = lambda *a, **k: None messagebox.showwarning = lambda *a, **k: warnungen.append(a[0] if a else "") diff --git a/pruef_flaeche.py b/pruef_flaeche.py index 1333794..b94a565 100644 --- a/pruef_flaeche.py +++ b/pruef_flaeche.py @@ -67,6 +67,8 @@ def pruefe(bedingung, text): messagebox.showinfo = messagebox.showwarning = messagebox.showerror = lambda *a, **k: None +# Der Erprobungshinweis wird von der pauschalen Antwort unten mit erfasst. +# Geprueft wird er eigens in pruef_hinweis.py. messagebox.askyesno = lambda *a, **k: True f = A.RechnungsApp() diff --git a/pruef_freigabe.py b/pruef_freigabe.py index 3fcda1c..9a663fa 100644 --- a/pruef_freigabe.py +++ b/pruef_freigabe.py @@ -83,6 +83,8 @@ def askyesno(titel, text, **kw): raise AssertionError(f"unangemeldeter Dialog: {titel!r}") +# Der Erprobungshinweis wird hier bejaht - geprueft wird er in pruef_hinweis.py. +antworten["Erprobungsfassung"] = True messagebox.askyesno = askyesno messagebox.showinfo = lambda *a, **k: None warnungen = [] diff --git a/pruef_hinweis.py b/pruef_hinweis.py new file mode 100644 index 0000000..6558969 --- /dev/null +++ b/pruef_hinweis.py @@ -0,0 +1,173 @@ +# -*- coding: utf-8 -*- +"""Pruefstand: der Hinweis auf die Erprobungsfassung und die Kenntnisnahme. + +Sein Wortlaut vom 06.09.2026: + + "wenn er nein drueckt oder ablehnt, schliesst sich die software und geht + beim naechsten start wieder mit dem selben auf, bis er auf ja drueckt" + "und das alles auch noch mal in die info mit signatur: user hat dies + gelesen und zur kenntnis genommen" + +Geprueft wird die ganze Strecke, weil jeder Teil fuer sich wertlos waere: + + 1. Der Hinweis kommt beim Start und nennt Haftung, Vorschau und die + Fehleradresse. + 2. NEIN -> das Fenster ist zu, und in der Konfiguration steht NICHTS. + Ein "Nein", das trotzdem vermerkt wird, waere eine Luege. + 3. Neuer Start -> es wird WIEDER gefragt. + 4. JA -> Signatur mit Benutzer, Zeit und Fassung; danach wird nicht mehr + gefragt. + 5. Andere Fassung des Textes -> es wird erneut gefragt. Eine Zustimmung gilt + fuer das, was dagestanden hat, nicht fuer alles Kuenftige. +""" +import json +import os +import shutil +import sys +import tempfile +import threading + +QUELLE = r"C:\claude\rechnungstool" +sys.path.insert(0, QUELLE) + +fehler = [] + + +def pruefe(bedingung, text): + zeile = (" OK | " if bedingung else "FEHLER | ") + text + kod = sys.stdout.encoding or "utf-8" + print(zeile.encode(kod, "replace").decode(kod)) + if not bedingung: + fehler.append(text) + + +def _reissleine(): + print("ABBRUCH | Pruefstand haengt (Zeitgrenze)") + os._exit(2) + + +_wecker = threading.Timer(120, _reissleine) +_wecker.daemon = True +_wecker.start() + +tmp = tempfile.mkdtemp(prefix="hinweis_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["output_dir"] = ordner +roh["nummer_eingerichtet"] = True +for weg in ("hinweis_fassung", "hinweis_zeit", "hinweis_benutzer"): + roh.pop(weg, None) +json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), + ensure_ascii=False, indent=2) + +os.environ["CAMPINGHOF_TEST"] = "1" +import app as A # noqa: E402 +import hinweis as H # noqa: E402 +from tkinter import messagebox # noqa: E402 + +antworten = {"Fertig": False, "Hausnummer": True} +gesehen = [] + + +def askyesno(titel, text, **kw): + gesehen.append((str(titel), str(text))) + for schlue, wert in antworten.items(): + if schlue in titel: + return wert + raise AssertionError(f"unangemeldeter Dialog: {titel!r}") + + +messagebox.askyesno = askyesno +messagebox.showinfo = lambda *a, **k: None +messagebox.showwarning = lambda *a, **k: None +messagebox.showerror = lambda *a, **k: print("[fehler]", a[0] if a else "") + + +def gelesen(): + return json.load(open(config.CONFIG_PATH, encoding="utf-8")) + + +# ---- 1./2. Ablehnen: Fenster zu, nichts vermerkt ----------------------- +antworten["Erprobungsfassung"] = False +f = A.RechnungsApp() +f.update() # laesst das after(0) laufen -> Hinweis kommt + +treffer = [t for t in gesehen if "Erprobungsfassung" in t[0]] +pruefe(bool(treffer), "der Hinweis kommt beim Start") +if treffer: + inhalt = treffer[0][1] + pruefe("ERPROBUNGSFASSUNG" in inhalt.upper(), "er nennt die Erprobungsfassung") + pruefe("Haftung" in inhalt, "er nennt die Haftung") + pruefe("Vorschau" in inhalt, "er verweist auf die Vorschau") + pruefe("git.pcore.de/TheMockTv/rechnungstool" in inhalt, + "er nennt die Adresse fuer Fehlermeldungen") + +def fenster_zu(fenster): + """True, wenn es das Fenster nicht mehr gibt. + + Nach destroy() antwortet winfo_exists() nicht mehr mit 0, sondern wirft - + die ganze Anwendung ist weg. Beides heisst hier dasselbe. + """ + import tkinter + try: + return not fenster.winfo_exists() + except tkinter.TclError: + return True + + +pruefe(fenster_zu(f), "nach Nein ist das Fenster zu") +cfg = gelesen() +pruefe(not cfg.get("hinweis_zeit"), "nach Nein steht nichts in der Konfiguration") + +# ---- 3. Neuer Start: es wird wieder gefragt ---------------------------- +gesehen.clear() +antworten["Erprobungsfassung"] = True +f = A.RechnungsApp() +f.update() +pruefe(any("Erprobungsfassung" in t[0] for t in gesehen), + "beim naechsten Start wird wieder gefragt") + +# ---- 4. Zustimmen: Signatur steht --------------------------------------- +cfg = gelesen() +pruefe(cfg.get("hinweis_fassung") == H.FASSUNG, + f"die Fassung ist vermerkt ({cfg.get('hinweis_fassung')!r})") +pruefe(bool(cfg.get("hinweis_zeit")), "der Zeitpunkt ist vermerkt") +pruefe(bool(cfg.get("hinweis_benutzer")), "der Benutzer ist vermerkt") +sig = H.signatur(f.cfg) +pruefe("gelesen und zur kenntnis genommen" in sig.lower(), + f"die Signaturzeile ist lesbar ({sig!r})") + +# ---- 5. Kein zweites Mal fragen ----------------------------------------- +gesehen.clear() +f.destroy() +f = A.RechnungsApp() +f.update() +pruefe(not any("Erprobungsfassung" in t[0] for t in gesehen), + "nach dem Ja wird nicht noch einmal gefragt") + +# ---- 6. Andere Fassung -> wieder fragen --------------------------------- +gesehen.clear() +f.destroy() +H.FASSUNG = "9999-12-31" # so, als waere der Text geaendert worden +f = A.RechnungsApp() +f.update() +pruefe(any("Erprobungsfassung" in t[0] for t in gesehen), + "bei geaenderter Fassung wird erneut gefragt") + +_wecker.cancel() +try: + f.destroy() +except Exception: # noqa: BLE001 + pass +shutil.rmtree(tmp, ignore_errors=True) + +if fehler: + print(f"ROT | {len(fehler)} Punkt(e) nicht erfuellt") + sys.exit(1) +print("GRUEN | Hinweis, Ablehnung und Signatur wirken") diff --git a/pruef_kernregeln.py b/pruef_kernregeln.py index 21c8c0a..87a8f80 100644 --- a/pruef_kernregeln.py +++ b/pruef_kernregeln.py @@ -98,6 +98,8 @@ def askyesno(titel, text="", **kw): # Die Freigabe vor dem Beleg (§ 14 UStG) wird bewusst bejaht - geprueft wird hier # der Ablauf DANACH. Dass ein "Nein" keinen Beleg erzeugt, prueft pruef_freigabe.py. antworten["jetzt erstellen?"] = True +# Der Erprobungshinweis wird hier bejaht - geprueft wird er in pruef_hinweis.py. +antworten["Erprobungsfassung"] = True messagebox.askyesno = askyesno messagebox.showinfo = lambda *a, **k: meldungen.append(("info", a[0] if a else "")) messagebox.showwarning = lambda *a, **k: meldungen.append(("warnung", a[0] if a else "")) diff --git a/pruef_kette.py b/pruef_kette.py index 3f4cd25..72e4fed 100644 --- a/pruef_kette.py +++ b/pruef_kette.py @@ -93,6 +93,8 @@ def askyesno(titel, text="", **kw): # Die Freigabe vor dem Beleg (§ 14 UStG) wird bewusst bejaht - geprueft wird hier # der Ablauf DANACH. Dass ein "Nein" keinen Beleg erzeugt, prueft pruef_freigabe.py. antworten["jetzt erstellen?"] = True +# Der Erprobungshinweis wird hier bejaht - geprueft wird er in pruef_hinweis.py. +antworten["Erprobungsfassung"] = True messagebox.askyesno = askyesno messagebox.showinfo = messagebox.showwarning = messagebox.showerror = \ lambda *a, **k: None diff --git a/pruef_montage.py b/pruef_montage.py index f264da1..20d65b3 100644 --- a/pruef_montage.py +++ b/pruef_montage.py @@ -89,6 +89,8 @@ warnungen = [] # Die Freigabe vor dem Beleg (§ 14 UStG) wird bewusst bejaht - geprueft wird hier # der Ablauf DANACH. Dass ein "Nein" keinen Beleg erzeugt, prueft pruef_freigabe.py. antworten["jetzt erstellen?"] = True +# Der Erprobungshinweis wird hier bejaht - geprueft wird er in pruef_hinweis.py. +antworten["Erprobungsfassung"] = True messagebox.askyesno = askyesno messagebox.showinfo = lambda *a, **k: None messagebox.showwarning = lambda *a, **k: warnungen.append(a[0] if a else "") diff --git a/pruef_nummernbuch.py b/pruef_nummernbuch.py index 89bc8db..1d83585 100644 --- a/pruef_nummernbuch.py +++ b/pruef_nummernbuch.py @@ -125,6 +125,8 @@ def askyesno(titel, text, **kw): # Die Freigabe vor dem Beleg (§ 14 UStG) wird bewusst bejaht - geprueft wird hier # der Ablauf DANACH. Dass ein "Nein" keinen Beleg erzeugt, prueft pruef_freigabe.py. antworten["jetzt erstellen?"] = True +# Der Erprobungshinweis wird hier bejaht - geprueft wird er in pruef_hinweis.py. +antworten["Erprobungsfassung"] = True messagebox.askyesno = askyesno tool = A.RechnungsApp() diff --git a/pruef_nummernsperre.py b/pruef_nummernsperre.py index 782ef7f..c3efa44 100644 --- a/pruef_nummernsperre.py +++ b/pruef_nummernsperre.py @@ -67,6 +67,8 @@ import gemeinsam # noqa: E402 from tkinter import messagebox # noqa: E402 warnungen = [] +# Der Erprobungshinweis wird von der pauschalen Antwort unten mit erfasst. +# Geprueft wird er eigens in pruef_hinweis.py. messagebox.askyesno = lambda t, x, **k: ("Fertig" not in t) messagebox.showinfo = lambda *a, **k: None messagebox.showwarning = lambda *a, **k: warnungen.append(a[0] if a else "") diff --git a/pruef_nur_storno.py b/pruef_nur_storno.py index 0a75bc7..a107881 100644 --- a/pruef_nur_storno.py +++ b/pruef_nur_storno.py @@ -70,6 +70,8 @@ import app as A # noqa: E402 import bestand as BE # noqa: E402 from tkinter import messagebox # noqa: E402 +# Der Erprobungshinweis wird von der pauschalen Antwort unten mit erfasst. +# Geprueft wird er eigens in pruef_hinweis.py. messagebox.askyesno = lambda *a, **k: True messagebox.showinfo = lambda *a, **k: None messagebox.showwarning = lambda *a, **k: None diff --git a/pruef_storno.py b/pruef_storno.py index d403091..f121e73 100644 --- a/pruef_storno.py +++ b/pruef_storno.py @@ -95,6 +95,8 @@ antworten["Hausnummer"] = True # "Musterweg 1" hat eine, greift nur im Son # Die Freigabe vor dem Beleg (§ 14 UStG) wird bewusst bejaht - geprueft wird hier # der Ablauf DANACH. Dass ein "Nein" keinen Beleg erzeugt, prueft pruef_freigabe.py. antworten["jetzt erstellen?"] = True +# Der Erprobungshinweis wird hier bejaht - geprueft wird er in pruef_hinweis.py. +antworten["Erprobungsfassung"] = True messagebox.askyesno = askyesno messagebox.showinfo = lambda *a, **k: None warnungen = [] diff --git a/pruef_vorschau.py b/pruef_vorschau.py index f2917ce..77979a6 100644 --- a/pruef_vorschau.py +++ b/pruef_vorschau.py @@ -63,6 +63,8 @@ def pruefe(bedingung, text): messagebox.showinfo = messagebox.showwarning = messagebox.showerror = lambda *a, **k: None +# Der Erprobungshinweis wird von der pauschalen Antwort unten mit erfasst. +# Geprueft wird er eigens in pruef_hinweis.py. messagebox.askyesno = lambda *a, **k: True # Im Pruflauf oeffnet das Programm nichts mehr (sonst stehen nach einem Lauf