# -*- coding: utf-8 -*- """Die Dialoge aus dem Menue "Einstellungen" - Steuersaetze, Katalog, Firma, Nummer. Alles, was hier eingestellt wird, landet in der config.json. Nichts davon steht hart im Code: Steuersaetze, Preise, Firmendaten, Rechnungsnummernkreis. """ import datetime import os import tkinter as tk from tkinter import ttk, messagebox, filedialog, colorchooser import berechnung import hinweis import theme import bilder import config from helfer import parse_num class EinstellungenMixin: """Einstellungsdialoge. Erwartet die Felder der RechnungsApp.""" def _frage_speicherort_falls_noetig(self): if (self.cfg.get("output_dir") or "").strip(): return vorschlag = config.default_output_dir() self.melden_info( "Speicherort der Rechnungen", "Bitte wähle einen Ordner, in dem die fertigen Rechnungen (PDF) gespeichert werden.\n\n" f"Vorschlag: {vorschlag}\n\n(Lässt sich später unter Einstellungen ändern.)") d = filedialog.askdirectory(title="Ordner für die Rechnungen wählen", initialdir=os.path.expanduser("~")) self.cfg["output_dir"] = d if d else vorschlag config.save_config(self.cfg) config.ensure_output_dir(self.cfg) def dlg_speicherort(self): aktuell = config.ensure_output_dir(self.cfg) d = filedialog.askdirectory(title="Ordner für die Rechnungen wählen", initialdir=aktuell) if d: self.cfg["output_dir"] = d config.save_config(self.cfg) config.ensure_output_dir(self.cfg) self.melden_info("Speicherort", f"Rechnungen werden gespeichert in:\n{d}") def _info(self): d = tk.Toplevel(self) d.title("Info") d.transient(self) d.resizable(False, False) self._style_dialog(d) frm = ttk.Frame(d, padding=18) frm.pack(fill="both", expand=True) ttk.Label(frm, text=config.PROGRAMM, font=(theme.SCHRIFT_NAME, 11, "bold"), foreground=self.col_accent).pack(anchor="w") ttk.Label(frm, justify="left", 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()) d.bind("", lambda e: d.destroy()) d.grab_set() # ====================================================== Einstellungen def dlg_steuern(self): d = tk.Toplevel(self) d.title("Steuersätze") d.transient(self) d.grab_set() self._style_dialog(d) d.geometry("620x470") frm = ttk.Frame(d, padding=12) frm.pack(fill="both", expand=True) ttk.Label(frm, wraplength=580, justify="left", font=(theme.SCHRIFT_NAME, 9, "bold"), text="Steuersätze: Satz % + Name. Die Katalog-Preise sind BRUTTO; die USt dieses " "Satzes wird herausgerechnet und als 'enthalten' ausgewiesen. Haken bei " "'Aufschlag': diese Steuer (z.B. Beherbergungssteuer) wird stattdessen auf den " "Rechnungsbetrag aufgeschlagen und direkt darunter ausgewiesen.").pack(anchor="w", pady=(0, 8)) liste = ttk.Frame(frm) liste.pack(fill="x") arbeit = [] for s in self.cfg.get("steuersaetze", []): arbeit.append({"satz": tk.StringVar(value=str(s.get("satz", ""))), "bez": tk.StringVar(value=s.get("bezeichnung", "")), "extra": tk.BooleanVar(value=bool(s.get("extra_blatt", False)))}) def render_saetze(): for w in liste.winfo_children(): w.destroy() for c, t in enumerate(["Satz %", "Bezeichnung (Name der Steuer)", "Aufschlag", ""]): ttk.Label(liste, text=t, font=(theme.SCHRIFT_NAME, 9, "bold")).grid( row=0, column=c, padx=4, pady=(0, 3), sticky="w") for i, row in enumerate(arbeit, start=1): ttk.Entry(liste, textvariable=row["satz"], width=8, justify="center").grid(row=i, column=0, padx=4, pady=2) ttk.Entry(liste, textvariable=row["bez"], width=32).grid(row=i, column=1, padx=4, pady=2, sticky="w") ttk.Checkbutton(liste, variable=row["extra"]).grid(row=i, column=2, padx=14, pady=2) ttk.Button(liste, text="✕", width=3, command=lambda r=row: (arbeit.remove(r), render_saetze())).grid(row=i, column=3, padx=4) def satz_hinzufuegen(): arbeit.append({"satz": tk.StringVar(value="0"), "bez": tk.StringVar(value="Mehrwertsteuer"), "extra": tk.BooleanVar(value=False)}) render_saetze() render_saetze() ttk.Button(frm, text="+ Steuersatz hinzufügen", command=satz_hinzufuegen).pack(anchor="w", pady=(8, 0)) def speichern(): neu = [] for row in arbeit: s = row["satz"].get().strip() if s == "": continue neu.append({"satz": int(parse_num(s)), "bezeichnung": row["bez"].get().strip() or "USt", "extra_blatt": bool(row["extra"].get())}) if not neu: self.melden_warnung("Leer", "Mindestens ein Steuersatz ist nötig.", parent=d) return self.cfg["steuersaetze"] = neu config.save_config(self.cfg) self._render_positionszeilen() self.recalc() d.destroy() btns = ttk.Frame(frm) btns.pack(side="bottom", fill="x", pady=(12, 0)) ttk.Button(btns, text="Speichern", command=speichern).pack(side="right", padx=4) ttk.Button(btns, text="Abbrechen", command=d.destroy).pack(side="right") def dlg_montage(self): """Bezeichnung, Steuersatz und Monatspreis der Montage-Rechnung. Eigener Dialog und NICHT im Leistungskatalog: die Montage-Rechnung ist eine Monatsmiete, im Katalog stuende sie zwischen lauter Nachtpreisen und wuerde auf jeder Campingrechnung mit angeboten. """ d = tk.Toplevel(self) d.title("Montage-Rechnung") d.transient(self) d.grab_set() self._style_dialog(d) m = self.cfg.get("montage", {}) v_leistung = tk.StringVar(value=str(m.get("leistung", ""))) v_satz = tk.StringVar(value=str(int(parse_num(str(m.get("satz", 7)))))) v_preis = tk.StringVar(value=berechnung.eur_kurz(m.get("preis", 0))) rahmen = ttk.Frame(d, padding=12) rahmen.pack(fill="both", expand=True) ttk.Label(rahmen, wraplength=520, justify="left", text="So steht die eine Zeile auf dem Montage-Blatt. Der Preis ist ein " "BRUTTO-Monatspreis wie im Leistungskatalog - die Umsatzsteuer wird " "herausgerechnet, die Beherbergungssteuer kommt obendrauf.").grid( row=0, column=0, columnspan=2, sticky="w", pady=(0, 10)) saetze = [s2 for s2 in self.cfg.get("steuersaetze", []) if not s2.get("extra_blatt")] zeilen = [("Bezeichnung", v_leistung), ("Umsatzsteuer %", v_satz), ("Monatspreis brutto (EUR)", v_preis)] for i, (lbl, var) in enumerate(zeilen, start=1): ttk.Label(rahmen, text=lbl + ":").grid(row=i, column=0, sticky="w", pady=3) if var is v_satz: w = ttk.Combobox(rahmen, textvariable=var, width=10, state="readonly", values=[str(x.get("satz")) for x in saetze] or ["7", "19"]) else: w = ttk.Entry(rahmen, textvariable=var, width=44) w.grid(row=i, column=1, sticky="ew", padx=(10, 0), pady=3) rahmen.columnconfigure(1, weight=1) def speichern(): name = v_leistung.get().strip() preis = parse_num(v_preis.get()) if not name: self.melden_warnung("Bezeichnung fehlt", "Bitte eine Bezeichnung eintragen.", parent=d) return if preis <= 0: self.melden_warnung("Preis fehlt", "Bitte einen Monatspreis groesser als 0 eintragen.", parent=d) return self.cfg["montage"] = {"leistung": name, "satz": int(parse_num(v_satz.get())), "preis": preis} config.save_config(self.cfg) self.recalc() d.destroy() knoepfe = ttk.Frame(rahmen) knoepfe.grid(row=len(zeilen) + 1, column=0, columnspan=2, sticky="e", pady=(14, 0)) ttk.Button(knoepfe, text="Abbrechen", command=d.destroy).pack(side="right", padx=4) ttk.Button(knoepfe, text="Speichern", command=speichern).pack(side="right", padx=4) d.bind("", lambda _e: d.destroy()) def dlg_katalog(self): d = tk.Toplevel(self) d.title("Leistungskatalog / Preise") d.transient(self) d.grab_set() self._style_dialog(d) info_lbl = ttk.Label(d, padding=(10, 8), wraplength=900, justify="left", text="Leistungen bearbeiten: Bezeichnung, Steuersatz (Auswahl), Preis in € und ob " "pro Person gerechnet wird. Weitere Steuersätze legst du unter " "Einstellungen > Steuersätze an. Unten neue Zeilen anlegen oder mit dem X " "rechts eine Leistung entfernen.") info_lbl.pack(side="top", fill="x") # alle definierten Steuersaetze sind je Position waehlbar saetze_cfg = self.cfg.get("steuersaetze", []) def satz_label(satz): for s in saetze_cfg: if int(s.get("satz", -1)) == int(satz or 0): return f"{s['satz']} % – {s.get('bezeichnung', '')}".rstrip(" –") return f"{satz} %" def combo_werte(aktuell): opts = [f"{s['satz']} % – {s.get('bezeichnung', '')}".rstrip(" –") for s in saetze_cfg] al = satz_label(aktuell) if al not in opts: opts.append(al) return opts def satz_aus_label(lbl): try: return int(parse_num(str(lbl).split("%")[0])) except (ValueError, IndexError): return 0 # Arbeitskopie als Liste von Variablen-Dicts (erlaubt Hinzufuegen/Loeschen) arbeit = [] for pos in self.cfg["katalog"]: arbeit.append({ "leistung": tk.StringVar(value=pos["leistung"]), "satz": tk.StringVar(value=str(pos["satz"])), "preis": tk.StringVar(value=berechnung.eur_kurz(pos["preis"])), "pro_person": tk.BooleanVar(value=bool(pos["pro_person"])), }) def render(): for w in inner.winfo_children(): w.destroy() for c, t in enumerate(["Pos.", "Bezeichnung", "Steuersatz", "Preis €", "pro Person", ""]): ttk.Label(inner, text=t, font=(theme.SCHRIFT_NAME, 9, "bold")).grid( row=0, column=c, padx=4, pady=(0, 4), sticky="w") for i, row in enumerate(arbeit, start=1): ttk.Label(inner, text=str(i), width=4).grid(row=i, column=0, padx=4, pady=1) ttk.Entry(inner, textvariable=row["leistung"], width=64).grid(row=i, column=1, padx=4, pady=1, sticky="we") cb = ttk.Combobox(inner, width=22, state="readonly", values=combo_werte(row["satz"].get())) cb.set(satz_label(row["satz"].get())) cb.bind("<>", lambda ev, rr=row, cc=cb: rr["satz"].set(str(satz_aus_label(cc.get())))) cb.grid(row=i, column=2, padx=4, pady=1, sticky="w") ttk.Entry(inner, textvariable=row["preis"], width=10, justify="right").grid(row=i, column=3, padx=4, pady=1) ttk.Checkbutton(inner, variable=row["pro_person"]).grid(row=i, column=4, padx=4, pady=1) ttk.Button(inner, text="✕", width=3, command=lambda r=row: (arbeit.remove(r), render())).grid(row=i, column=5, padx=4, pady=1) def hinzufuegen(): arbeit.append({ "leistung": tk.StringVar(value="Neue Leistung"), "satz": tk.StringVar(value=str(saetze_cfg[0]["satz"]) if saetze_cfg else "19"), "preis": tk.StringVar(value="0,00"), "pro_person": tk.BooleanVar(value=False), }) render() canvas.update_idletasks() canvas.configure(scrollregion=canvas.bbox("all")) canvas.yview_moveto(1.0) def speichern(): neu = [] for i, row in enumerate(arbeit, start=1): name = row["leistung"].get().strip() if not name: continue # leere Zeilen verwerfen neu.append({ "nr": i, "leistung": name, "satz": int(parse_num(row["satz"].get())), "preis": parse_num(row["preis"].get()), "pro_person": bool(row["pro_person"].get()), }) if not neu: self.melden_warnung("Leer", "Es muss mindestens eine Leistung übrig bleiben.", parent=d) return self.cfg["katalog"] = neu config.save_config(self.cfg) self._render_positionszeilen() self.recalc() d.destroy() # Buttonleiste unten zuerst -> bleibt immer sichtbar btns = ttk.Frame(d, padding=8) btns.pack(side="bottom", fill="x") ttk.Button(btns, text="+ Position hinzufügen", command=hinzufuegen).pack(side="left", padx=4) ttk.Button(btns, text="Speichern", command=speichern).pack(side="right", padx=4) ttk.Button(btns, text="Abbrechen", command=d.destroy).pack(side="right", padx=4) # Scrollbereich body = ttk.Frame(d) body.pack(side="top", fill="both", expand=True) canvas = tk.Canvas(body, highlightthickness=0, bg=self.col_bg) sb = ttk.Scrollbar(body, orient="vertical", command=canvas.yview) inner = ttk.Frame(canvas, padding=8) inner.bind("", lambda e: canvas.configure(scrollregion=canvas.bbox("all"))) canvas.create_window((0, 0), window=inner, anchor="nw") canvas.configure(yscrollcommand=sb.set) canvas.pack(side="left", fill="both", expand=True) sb.pack(side="right", fill="y") render() # Fenstergröße an den Inhalt anpassen (Breite = breiteste Zeile) d.update_idletasks() inhalt_w = inner.winfo_reqwidth() canvas.configure(width=inhalt_w) win_w = min(inhalt_w + sb.winfo_reqwidth() + 26, d.winfo_screenwidth() - 80) win_h = min(inner.winfo_reqheight() + btns.winfo_reqheight() + 90, d.winfo_screenheight() - 120) d.geometry(f"{win_w}x{int(win_h)}") d.minsize(min(win_w, 820), 420) info_lbl.configure(wraplength=win_w - 24) def dlg_firma(self): d = tk.Toplevel(self) d.title("Firmendaten & Texte") d.transient(self) d.grab_set() self._style_dialog(d) breite = min(900, max(700, d.winfo_screenwidth() - 200)) hoehe = min(760, max(560, d.winfo_screenheight() - 140)) d.geometry(f"{breite}x{hoehe}") d.minsize(700, 560) frm = ttk.Frame(d, padding=12) frm.pack(fill="both", expand=True) felder = [ ("name", "Firmenname"), ("untertitel", "Untertitel"), ("absender", "Absenderzeile"), ("inhaber", "Inhaber"), ("bank", "Bank"), ("iban", "IBAN"), ("bic", "SWIFT/BIC"), ("steuernummer", "Steuernummer"), ("homepage", "Homepage"), ("email", "E-Mail"), ("telefon", "Telefon"), ] vars_ = {} for i, (key, lbl) in enumerate(felder): ttk.Label(frm, text=lbl).grid(row=i, column=0, sticky="w", pady=2) v = tk.StringVar(value=self.cfg["firma"].get(key, "")) ttk.Entry(frm, textvariable=v, width=44).grid(row=i, column=1, sticky="ew", padx=(8, 0), pady=2) vars_[key] = v frm.columnconfigure(1, weight=1) base = len(felder) # Bilder auf der Rechnung: Kopf (oben links) und Fuss (Streifen unten). # Beide funktionieren gleich, deshalb baut EINE Funktion beide Zeilen - # sonst laufen sie beim naechsten Umbau auseinander. bild_vars = {} def bild_zeile(zeile, key, beschriftung): ttk.Label(frm, text=beschriftung).grid(row=zeile, column=0, sticky="nw", pady=2) kasten = ttk.Frame(frm) kasten.grid(row=zeile, column=1, sticky="ew", padx=(8, 0), pady=2) v = tk.StringVar(value=self.cfg["firma"].get(key, "")) reihe = ttk.Frame(kasten) reihe.pack(fill="x") ttk.Entry(reihe, textvariable=v).pack(side="left", fill="x", expand=True) def waehlen(): p = filedialog.askopenfilename( title=beschriftung + " auswählen", filetypes=[("Bilder", "*.png *.jpg *.jpeg *.gif"), ("Alle Dateien", "*.*")], parent=d) if p: # Das Bild wird in den Ordner "bilder" neben das Programm # KOPIERT. Der Pfad zum Desktop waere morgen tot - und das # faellt erst beim Drucken auf. v.set(bilder.uebernehmen(p)) ttk.Button(reihe, text="Durchsuchen…", command=waehlen).pack(side="left", padx=(6, 0)) ttk.Button(reihe, text="Entfernen", command=lambda: v.set("")).pack(side="left", padx=(4, 0)) bild_vars[key] = v # Ausgetauscht wird nur das BILD. Firmenname, Anschrift, Bank und # Steuernummer stehen weiter gezeichnet auf dem Blatt - sie kommen aus den # Feldern darueber und duerfen in keinem Bild verschwinden. bild_zeile(base, "logo", "Bild oben (Logo)") bild_zeile(base + 1, "fussbild", "Bild unten (Streifen)") # Farbe der Rechnung: Ueberschriften, Linie unter dem Kopf, Tabellenkopf. # EINE Farbe - die helle Flaeche wird im Renderer daraus gemischt. ttk.Label(frm, text="Farbe der Rechnung").grid(row=base + 2, column=0, sticky="w", pady=2) farb_reihe = ttk.Frame(frm) farb_reihe.grid(row=base + 2, column=1, sticky="ew", padx=(8, 0), pady=2) v_farbe = tk.StringVar(value=(self.cfg.get("design", {}) or {}).get( "akzent", config.DEFAULT_DESIGN["akzent"])) probe = tk.Label(farb_reihe, width=6, relief="solid", borderwidth=1) def probe_zeigen(*_): wert = v_farbe.get().strip() try: probe.configure(background=wert) except tk.TclError: # Unlesbare Farbe: das Feld bleibt grau, gespeichert wird sie nicht. probe.configure(background=self.col_field) def farbe_waehlen(): _, hexwert = colorchooser.askcolor(color=v_farbe.get().strip() or None, title="Farbe der Rechnung", parent=d) if hexwert: v_farbe.set(hexwert) probe.pack(side="left") ttk.Entry(farb_reihe, textvariable=v_farbe, width=12).pack(side="left", padx=(6, 0)) ttk.Button(farb_reihe, text="Wählen…", command=farbe_waehlen).pack(side="left", padx=(6, 0)) ttk.Button(farb_reihe, text="Zurücksetzen", command=lambda: v_farbe.set(config.DEFAULT_DESIGN["akzent"])).pack(side="left", padx=(4, 0)) v_farbe.trace_add("write", probe_zeigen) probe_zeigen() ttk.Label(frm, text="Danktext 1").grid(row=base + 3, column=0, sticky="nw", pady=2) t1 = tk.Text(frm, width=44, height=3, wrap="word", **self.feld_optionen(mehrzeilig=True)) t1.insert("1.0", self.cfg["texte"].get("dank1", "")) t1.grid(row=base + 3, column=1, sticky="ew", padx=(8, 0), pady=2) ttk.Label(frm, text="Danktext 2").grid(row=base + 4, column=0, sticky="nw", pady=2) t2 = tk.Text(frm, width=44, height=3, wrap="word", **self.feld_optionen(mehrzeilig=True)) t2.insert("1.0", self.cfg["texte"].get("dank2", "")) t2.grid(row=base + 4, column=1, sticky="ew", padx=(8, 0), pady=2) ttk.Label(frm, text="Hinweiszeile").grid(row=base + 5, column=0, sticky="w", pady=2) vh = tk.StringVar(value=self.cfg["texte"].get("hinweis", "")) ttk.Entry(frm, textvariable=vh, width=44).grid(row=base + 5, column=1, sticky="ew", padx=(8, 0), pady=2) def speichern(): for key, v in vars_.items(): self.cfg["firma"][key] = v.get() for key, v in bild_vars.items(): wert = v.get() self.cfg["firma"][key] = wert.strip() if isinstance(wert, str) else bool(wert) self.cfg.setdefault("design", {})["akzent"] = v_farbe.get().strip() or config.DEFAULT_DESIGN["akzent"] self.cfg["texte"]["dank1"] = t1.get("1.0", "end").strip() self.cfg["texte"]["dank2"] = t2.get("1.0", "end").strip() self.cfg["texte"]["hinweis"] = vh.get().strip() config.save_config(self.cfg) self.title(config.PROGRAMM) d.destroy() btns = ttk.Frame(frm) btns.grid(row=base + 6, column=0, columnspan=2, pady=(12, 0), sticky="e") ttk.Button(btns, text="Speichern", command=speichern).pack(side="right", padx=4) ttk.Button(btns, text="Abbrechen", command=d.destroy).pack(side="right") def dlg_nummer(self): # Einmal eingerichtet, dann zu: eine ausgestellte Rechnung wird nur noch # ueber Storno oder Berichtigung korrigiert (§ 14 Abs. 4 Nr. 4 UStG, # § 146 Abs. 4 AO). Angezeigt wird der Stand trotzdem - man will ja # nachsehen koennen. if self.cfg.get("nummer_eingerichtet"): jahr = self.cfg.get("rechnung_jahr") zaehler = int(self.cfg.get("rechnung_zaehler", 0) or 0) hoch = self.hoechster_stand(jahr) self.melden_info( "Rechnungsnummer", f"Zuletzt vergeben: {jahr}-{max(zaehler, hoch):03d}\n" f"Die nächste Rechnung bekommt {jahr}-{max(zaehler, hoch) + 1:03d}.\n\n" "Der Zähler wurde einmal eingerichtet und lässt sich nicht mehr ändern. " "Eine ausgestellte Rechnung wird über „Storno“ oder „Berichtigen“ " "korrigiert – jede Nummer darf es nur einmal geben " "(§ 14 Abs. 4 Nr. 4 UStG).") return return self._dlg_nummer_einrichten() def _dlg_nummer_einrichten(self): d = tk.Toplevel(self) d.title("Rechnungsnummer") d.transient(self) d.grab_set() self._style_dialog(d) frm = ttk.Frame(d, padding=12) frm.pack(fill="both", expand=True) v_jahr = tk.StringVar(value=str(self.cfg.get("rechnung_jahr", datetime.date.today().year))) v_z = tk.StringVar(value=str(self.cfg.get("rechnung_zaehler", 0))) ttk.Label(frm, text="Format: JJJJ-NNN (z. B. 2026-013). Der Zähler ist der zuletzt\n" "vergebene Wert – beim nächsten Beleg wird +1 vorgeschlagen.", justify="left").grid(row=0, column=0, columnspan=2, sticky="w", pady=(0, 8)) hoch_jetzt = self.hoechster_stand() ttk.Label(frm, foreground=self.col_muted, justify="left", text=f"Schon vergeben ist bis {self.cfg.get('rechnung_jahr')}-" f"{hoch_jetzt:03d} (aus den PDFs im Ordner und dem gemeinsamen\n" "Nummernbuch). Dahinter zurück geht nicht – nach vorn schon.").grid( row=4, column=0, columnspan=2, sticky="w", pady=(10, 0)) ttk.Label(frm, text="Jahr").grid(row=1, column=0, sticky="w", pady=3) ttk.Entry(frm, textvariable=v_jahr, width=10).grid(row=1, column=1, sticky="w", pady=3) ttk.Label(frm, text="Zuletzt vergebener Zähler").grid(row=2, column=0, sticky="w", pady=3) ttk.Entry(frm, textvariable=v_z, width=10).grid(row=2, column=1, sticky="w", pady=3) def speichern(): jahr = int(parse_num(v_jahr.get())) zaehler = int(parse_num(v_z.get())) # Zurueck geht nicht: was vergeben ist, ist vergeben. Geprueft wird # gegen die PDFs im Ordner UND das gemeinsame Nummernbuch - der # Zaehler in der config allein waere ja gerade das, was hier # geaendert wird. Vorwaerts ist erlaubt: eine lueckenlose Folge # verlangt das Gesetz nicht, jede Nummer aber nur einmal # (§ 14 Abs. 4 Nr. 4 UStG). hoch = self.hoechster_stand(jahr) if jahr == int(self.cfg.get("rechnung_jahr", jahr) or jahr) and zaehler < hoch: self.melden_warnung( "Nummer schon vergeben", f"Für {jahr} ist bereits bis {jahr}-{hoch:03d} vergeben.\n\n" f"Der Zähler kann nicht auf {zaehler} zurückgesetzt werden – sonst " "gäbe es eine Nummer zweimal, und jede Rechnungsnummer darf nur " "einmal vergeben werden (§ 14 Abs. 4 Nr. 4 UStG).\n\n" "Nach vorn ist er frei: Lücken sind erlaubt.", parent=d) v_z.set(str(hoch)) return self.cfg["rechnung_jahr"] = jahr self.cfg["rechnung_zaehler"] = zaehler config.save_config(self.cfg) self._neue_nummer_vorschlagen() d.destroy() btns = ttk.Frame(frm) btns.grid(row=3, column=0, columnspan=2, pady=(12, 0), sticky="e") ttk.Button(btns, text="Speichern", command=speichern).pack(side="right", padx=4) ttk.Button(btns, text="Abbrechen", command=d.destroy).pack(side="right")