Statt einer Zustandsmaschine ("offener Storno" ueber den Neustart retten) traegt
das Storno jetzt einfach beide Nummern:
Storno zu Rechnung: 2026-002
Neue Rechnung: 2026-004
und im Text "2026-002 -> 2026-003 -> 2026-004". Damit ist die Kette aus dem
Beleg allein lesbar. Wird das Programm zwischendurch geschlossen, sagt der
Storno, unter welcher Nummer die berichtigte Rechnung gehoert; der Dialog
nennt sie ebenfalls, statt nur "bereits storniert" zu melden.
naechste_freie_nummer_nach() bestimmt die Folgenummer, damit sie nicht von der
Reihenfolge abhaengt, in der der Zaehler mitzieht.
Ausserdem: die Pruefstaende raeumen ihren Wegwerf-Ordner wieder weg, wenn sie
gruen sind. Ein Testlauf hatte 398 Ordner im Temp-Verzeichnis hinterlassen.
Bei Fehlern bleibt der Ordner stehen, damit man hineinsehen kann.
Weitere Funde des Code-Agenten behoben:
- "Kopie von Rechnung_...pdf" und klein geschriebene Dateinamen zaehlen jetzt
bei der Nummernvergabe mit (vorher galt die Nummer als frei).
- Strg+P feuerte auch aus einem offenen Dialog heraus (bind_all -> bind).
167 Pruefungen gruen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKvGkdNW1vKdnMPAM9Bp3X
432 lines
21 KiB
Python
432 lines
21 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""Storno- und Berichtigungsweg.
|
||
|
||
Zwei Wege, ein Popup-Bauplan:
|
||
Storno - falsche Betraege. Stornorechnung mit eigener neuer Nummer,
|
||
danach die berichtigte Rechnung. Zwei Nummern verbraucht.
|
||
Berichtigung - nur die Kundenangaben stimmen nicht. Die Rechnung behaelt
|
||
Nummer, Datum und Betraege (§ 31 Abs. 5 UStDV), es wird nur
|
||
ein berichtigtes Blatt nachgereicht. Keine Nummer verbraucht.
|
||
|
||
Die alte Rechnung wird in beiden Faellen NIE angefasst. § 146 Abs. 4 AO verbietet
|
||
genau genommen nur Aenderungen, bei denen der urspruengliche Inhalt nicht mehr
|
||
feststellbar ist - ein protokollierter Storno-Vermerk waere also erlaubt. Dieses
|
||
Programm verzichtet trotzdem darauf: ein unberuehrter Ursprungsbeleg ist im
|
||
Pruefungsfall die einfachere Geschichte. Korrigiert wird ueber ein eigenes
|
||
Dokument - so sieht es das Gesetz selbst vor (§ 14 Abs. 6 Nr. 5 UStG,
|
||
§ 31 Abs. 5 UStDV).
|
||
"""
|
||
|
||
import datetime
|
||
import os
|
||
import tkinter as tk
|
||
from tkinter import ttk, messagebox
|
||
|
||
import berechnung
|
||
import config
|
||
import pdf_renderer
|
||
from bestand import (kundendaten_ermitteln, naechste_freie_nummer,
|
||
naechste_freie_nummer_nach, rechnungen_im_ordner)
|
||
from helfer import kunde_maengel, nummer_normalisieren, safe_filename
|
||
from storno import storno_rechnung_bauen
|
||
|
||
|
||
class KorrekturMixin:
|
||
"""Storno und Berichtigung. Erwartet die Felder der RechnungsApp."""
|
||
|
||
# ------------------------------------------------------------- Storno
|
||
|
||
|
||
|
||
def _storno_anzeigen(self):
|
||
"""Hinweiszeile und Sperre der Rechnungsnummer an den Zwischenspeicher anpassen."""
|
||
stand = self.storno_stand
|
||
if stand:
|
||
self.storno_hinweis.set(
|
||
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 ""),
|
||
"vorgang": str(meta.get("vorgang") or nummer),
|
||
"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.
|
||
|
||
Zulaessig ist das nur hier: die Storno-PDF ist Sekunden alt und hat das
|
||
Haus nie verlassen. Die stornierte Rechnung selbst wird nie angefasst.
|
||
"""
|
||
stand = self.storno_stand
|
||
if not stand:
|
||
return
|
||
try:
|
||
if os.path.exists(stand["pfad"]):
|
||
os.remove(stand["pfad"])
|
||
print(f"[storno] verworfen, PDF gelöscht -> {stand['pfad']}")
|
||
except OSError as e: # noqa: BLE001
|
||
# Abbrechen, NICHT weiterlaufen: sonst liegt das Storno noch im Ordner,
|
||
# der Zähler ist zurückgedreht und die Rechnung gilt als storniert,
|
||
# obwohl der Nutzer sie behalten wollte.
|
||
messagebox.showerror(
|
||
"Storno konnte nicht gelöscht werden",
|
||
f"Die Datei\n{stand['pfad']}\nließ sich nicht löschen:\n{e}\n\n"
|
||
"Ist sie noch in einem PDF-Betrachter offen? Bitte dort schließen "
|
||
"und noch einmal versuchen. Es wurde nichts verändert.")
|
||
return
|
||
self.cfg["rechnung_jahr"] = stand["jahr_vorher"]
|
||
self.cfg["rechnung_zaehler"] = stand["zaehler_vorher"]
|
||
try:
|
||
config.save_config(self.cfg)
|
||
except OSError as e: # noqa: BLE001
|
||
messagebox.showwarning(
|
||
"Zähler nicht gespeichert",
|
||
f"Die Einstellungen ließen sich nicht schreiben:\n{e}\n\n"
|
||
"Bitte im Menü unter „Rechnungsnummer…“ prüfen.")
|
||
self.storno_stand = None
|
||
self._storno_anzeigen()
|
||
self._neue_nummer_vorschlagen()
|
||
if grund:
|
||
print(f"[storno] {grund}")
|
||
|
||
def dlg_storno(self):
|
||
"""Knopf „Storno“ – setzt den Weg auf Storno und öffnet das Popup."""
|
||
self._dlg_korrektur("storno")
|
||
|
||
def dlg_berichtigen(self):
|
||
"""Knopf „Berichtigen“ – setzt den Weg auf Berichtigung und öffnet das Popup."""
|
||
self._dlg_korrektur("berichtigung")
|
||
|
||
def _dlg_korrektur(self, modus):
|
||
"""Popup für beide Wege. Welcher gilt, steht schon beim Öffnen fest.
|
||
|
||
"storno" -> Storno-PDF schreiben, danach die berichtigte Rechnung
|
||
"berichtigung" -> nur die Kundenangaben, Rechnung behält ihre Nummer
|
||
"""
|
||
storno = modus == "storno"
|
||
if self.storno_stand:
|
||
messagebox.showinfo(
|
||
"Storno liegt schon bereit",
|
||
f"Der Storno {self.storno_stand['nummer']} zu Rechnung "
|
||
f"{self.storno_stand['zu']} ist noch offen.\n\n"
|
||
"Erst die berichtigte Rechnung erstellen oder den Storno dort verwerfen.")
|
||
return
|
||
if self.berichtigung_stand:
|
||
messagebox.showinfo(
|
||
"Berichtigung läuft schon",
|
||
f"Die Rechnung {self.berichtigung_stand['zu']} liegt gerade zum Berichtigen "
|
||
"im Formular.\n\nErst „PDF erstellen“ drücken oder das Formular zurücksetzen.")
|
||
return
|
||
|
||
out_dir = config.ensure_output_dir(self.cfg)
|
||
alle = rechnungen_im_ordner(out_dir)
|
||
stornos = {nummer_normalisieren(d.get("storno_zu")): d
|
||
for d in alle if d.get("art") == "storno"}
|
||
stornierte = set(stornos)
|
||
# Zu welchen Rechnungen gibt es schon eine berichtigte Rechnung?
|
||
ersetzte = {nummer_normalisieren(d.get("ersetzt"))
|
||
for d in alle if d.get("art") == "korrektur"}
|
||
offen = [d for d in alle
|
||
if d.get("art") not in ("storno", "berichtigung")
|
||
and nummer_normalisieren(d.get("rechnungsnummer")) not in stornierte]
|
||
if not offen:
|
||
messagebox.showwarning(
|
||
"Nichts zu korrigieren",
|
||
f"Im Ordner\n{out_dir}\nliegt keine Rechnung, die dafür in Frage kommt.")
|
||
return
|
||
|
||
d = tk.Toplevel(self)
|
||
d.title("Rechnung stornieren" if storno else "Rechnung berichtigen")
|
||
d.transient(self)
|
||
d.grab_set()
|
||
self._style_dialog(d)
|
||
frm = ttk.Frame(d, padding=12)
|
||
frm.pack(fill="both", expand=True)
|
||
|
||
ttk.Label(frm, justify="left", wraplength=470, text=(
|
||
"Für falsche Beträge oder Leistungen. Die alte Rechnung bleibt unverändert "
|
||
"liegen – sie darf weder geändert noch gelöscht werden. Es entsteht eine "
|
||
"Stornorechnung mit eigener neuer Nummer, die sich auf sie bezieht, und danach "
|
||
"die berichtigte Rechnung."
|
||
if storno else
|
||
"Wenn nur die Angaben zum Kunden nicht stimmen. Die Rechnung behält Nummer, "
|
||
"Datum und Beträge und bleibt gültig – es wird nur ein berichtigtes Blatt "
|
||
"nachgereicht (§ 31 Abs. 5 UStDV). Für falsche Beträge ist das der falsche Weg, "
|
||
"dafür gibt es den Storno.")).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)
|
||
# Bewusst OHNE textvariable: eine StringVar, die nur an einer lokalen
|
||
# Variable haengt, raeumt Python nach dem Dialogaufbau weg - Tk loescht
|
||
# dann die Tcl-Variable und das Feld steht leer da.
|
||
ent = ttk.Entry(frm, width=20)
|
||
ent.grid(row=1, column=1, sticky="w", pady=3, padx=(8, 0))
|
||
ent.focus_set()
|
||
|
||
v_info = tk.StringVar(value="Nummer eintragen – das Programm sucht die Rechnung.")
|
||
ttk.Label(frm, textvariable=v_info, justify="left", wraplength=430).grid(
|
||
row=2, column=0, columnspan=2, sticky="w", pady=(10, 0))
|
||
|
||
# Wird nur sichtbar, wenn eine Nummer MEHRFACH im Ordner liegt. Genau das
|
||
# ist der Altbestand: solange zwei Fenster gleichzeitig laufen konnten,
|
||
# wurde dieselbe Nummer zweimal vergeben, teils als Windows-Kopie mit
|
||
# "(1)" im Namen. Dann darf das Programm nicht raten, welche gemeint ist.
|
||
self._auswahl = tk.Listbox(frm, height=4, exportselection=False,
|
||
bg=self.col_field, fg=self.col_fg,
|
||
selectbackground=self.col_sel, selectforeground=self.col_fg,
|
||
relief="flat", font=("Consolas", 9))
|
||
self._auswahl.grid(row=3, column=0, columnspan=2, sticky="ew", pady=(8, 0))
|
||
self._auswahl.grid_remove()
|
||
self._kandidaten = []
|
||
|
||
def kandidaten_zur_nummer(nr):
|
||
return [x for x in alle
|
||
if nummer_normalisieren(x.get("rechnungsnummer")) == nr
|
||
and x.get("art") != "berichtigung"]
|
||
|
||
def suche():
|
||
"""(Rechnung, Meldung) zur eingetippten Nummer. Rechnung=None -> Meldung sagt warum."""
|
||
nr = nummer_normalisieren(ent.get())
|
||
if not nr:
|
||
self._kandidaten = []
|
||
self._auswahl.grid_remove()
|
||
return None, "Nummer eintragen – das Programm sucht die Rechnung."
|
||
gefunden = kandidaten_zur_nummer(nr)
|
||
if not gefunden:
|
||
self._kandidaten = []
|
||
self._auswahl.grid_remove()
|
||
return None, f"Zu {nr} liegt in\n{out_dir}\nkeine Rechnung."
|
||
|
||
# Mehrere Belege unter derselben Nummer -> auswählen lassen.
|
||
if len(gefunden) > 1:
|
||
if gefunden != self._kandidaten:
|
||
self._kandidaten = gefunden
|
||
self._auswahl.delete(0, "end")
|
||
for x in gefunden:
|
||
self._auswahl.insert("end",
|
||
f"{x.get('datum',''):11s} {str(x.get('nachname') or '?'):16s} "
|
||
f"{berechnung.eur(x.get('gesamt') or 0):>11s} "
|
||
f"{os.path.basename(x.get('_pfad',''))}")
|
||
self._auswahl.grid()
|
||
self._auswahl.selection_clear(0, "end")
|
||
auswahl = self._auswahl.curselection()
|
||
if not auswahl:
|
||
return None, (f"⚠ Die Nummer {nr} liegt {len(gefunden)}× im Ordner – "
|
||
"das darf es nicht geben.\nBitte unten auswählen, "
|
||
"welcher Beleg gemeint ist.")
|
||
treffer = gefunden[auswahl[0]]
|
||
else:
|
||
self._kandidaten = []
|
||
self._auswahl.grid_remove()
|
||
treffer = gefunden[0]
|
||
|
||
if treffer.get("art") == "storno":
|
||
return None, f"{nr} ist selbst eine Stornorechnung."
|
||
nr_norm = nummer_normalisieren(treffer.get("rechnungsnummer"))
|
||
self._offener_storno = None
|
||
if nr_norm in stornierte:
|
||
st = stornos.get(nr_norm) or {}
|
||
folgt = st.get("korrektur_nummer") or ""
|
||
if nr_norm in ersetzte or not folgt:
|
||
return None, (f"{nr} wurde bereits mit {st.get('rechnungsnummer','?')} "
|
||
"storniert – ein zweites Mal geht nicht.")
|
||
# Auf dem Storno steht, welche Nummer die berichtigte Rechnung
|
||
# bekommt. Damit braucht es keinen gemerkten Zustand: fehlt sie
|
||
# noch, wird sie einfach unter dieser Nummer geschrieben.
|
||
return None, (f"{nr} ist mit {st.get('rechnungsnummer','?')} storniert. "
|
||
f"Die berichtigte Rechnung gehört unter die Nummer {folgt} – "
|
||
"die steht auch auf dem Storno-Blatt.")
|
||
gesamt = float(treffer.get("gesamt") or 0)
|
||
mehrfach = (f"⚠ Achtung: die Nummer liegt {len(gefunden)}× im Ordner.\n"
|
||
if len(gefunden) > 1 else "")
|
||
return treffer, (
|
||
mehrfach +
|
||
f"Rechnung {treffer.get('rechnungsnummer')} vom {treffer.get('datum','')}"
|
||
f" {treffer.get('nachname','')}"
|
||
+ (f" ({os.path.basename(treffer.get('_pfad',''))})" if mehrfach else "") + "\n"
|
||
f"Rechnungsbetrag {berechnung.eur(treffer.get('zwischensumme') or 0)}, "
|
||
f"Gesamt {berechnung.eur(gesamt)}\n"
|
||
f"Der Storno lautet über {berechnung.eur(-abs(gesamt))}.")
|
||
|
||
def vorschau(*_a):
|
||
v_info.set(suche()[1])
|
||
ent.bind("<KeyRelease>", vorschau)
|
||
self._auswahl.bind("<<ListboxSelect>>", vorschau)
|
||
|
||
def anlegen(*_a):
|
||
meta, meldung = suche()
|
||
if not meta:
|
||
v_info.set(meldung)
|
||
return
|
||
d.destroy()
|
||
if storno and getattr(self, "_offener_storno", None):
|
||
self._storno_fortsetzen(self._offener_storno, meta, out_dir)
|
||
elif storno:
|
||
self._storno_anlegen(meta, out_dir)
|
||
else:
|
||
self._berichtigung_starten(meta, out_dir)
|
||
ent.bind("<Return>", anlegen)
|
||
|
||
btns = ttk.Frame(frm)
|
||
btns.grid(row=4, column=0, columnspan=2, pady=(14, 0), sticky="e")
|
||
ttk.Button(btns, text="Storno erstellen" if storno else "Rechnung laden",
|
||
command=anlegen).pack(side="right", padx=4)
|
||
ttk.Button(btns, text="Abbrechen", command=d.destroy).pack(side="right")
|
||
frm.columnconfigure(1, weight=1)
|
||
|
||
|
||
def _storno_anlegen(self, meta, out_dir):
|
||
"""Schreibt die Storno-PDF und füllt das Formular für die berichtigte Rechnung."""
|
||
alt_nummer = str(meta.get("rechnungsnummer", ""))
|
||
nummer = naechste_freie_nummer(out_dir, self.cfg)
|
||
if not nummer:
|
||
messagebox.showerror("Keine freie Nummer",
|
||
"Es wurde keine freie Rechnungsnummer gefunden.")
|
||
return
|
||
|
||
r = storno_rechnung_bauen(meta, self.cfg)
|
||
heute = datetime.date.today().strftime("%d.%m.%Y")
|
||
# Welche Nummer die berichtigte Rechnung bekommt, steht schon jetzt fest -
|
||
# die naechste freie NACH dem Storno. Sie kommt aufs Storno-Blatt, damit
|
||
# die Kette aus dem Beleg allein lesbar ist und kein Zustand gemerkt
|
||
# werden muss: alte Nummer -> Storno -> neue Nummer, alles auf einem Blatt.
|
||
folge_nummer = naechste_freie_nummer_nach(out_dir, self.cfg, nummer)
|
||
# Der Storno geht an denselben Empfaenger wie die alte Rechnung - was in
|
||
# deren Kenndaten fehlt, wird aus ihrem Adressblock geholt.
|
||
kunde, woher = kundendaten_ermitteln(meta)
|
||
namenszeile = " ".join(t for t in (kunde.get("vorname", ""),
|
||
kunde.get("nachname", "")) if t)
|
||
kunde_zeilen = [kunde.get("anrede", ""), namenszeile, kunde.get("zusatz", ""),
|
||
kunde.get("strasse", ""), kunde.get("plzort", "")]
|
||
|
||
# Der Storno ist der Beleg, der die Rechnung aufhebt - er geht an den Gast
|
||
# und braucht dieselben Angaben wie sie. Steht in der alten Rechnung nichts,
|
||
# kann das Programm nichts erfinden; dann muss der Nutzer entscheiden.
|
||
maengel = kunde_maengel(kunde)
|
||
if maengel:
|
||
if not messagebox.askyesno(
|
||
"Angaben zum Kunden unvollständig",
|
||
f"In Rechnung {alt_nummer} steht nicht alles:\n\n- "
|
||
+ "\n- ".join(maengel) +
|
||
"\n\nDas Storno geht an den Gast und trägt dieselben Angaben. "
|
||
"Was fehlt, fehlt auch dort.\n\nTrotzdem erstellen?",
|
||
icon="warning", default="no"):
|
||
return
|
||
|
||
kopf = {
|
||
"rechnungsnummer": nummer,
|
||
"datum": heute,
|
||
"anreise": "", "abreise": "", "naechte": "",
|
||
"kunde_zeilen": kunde_zeilen,
|
||
"nachname": kunde.get("nachname", ""),
|
||
"vorname": kunde.get("vorname", ""),
|
||
"art": "storno",
|
||
# Vorgang der alten Rechnung uebernehmen, nicht deren Nummer: wird eine
|
||
# schon berichtigte Rechnung noch einmal storniert, bleibt die ganze
|
||
# Kette unter EINEM Schluessel.
|
||
"vorgang": str(meta.get("vorgang") or alt_nummer),
|
||
"storno_zu": alt_nummer,
|
||
# alte Nummer -> diese Nummer. Steht auf dem Blatt, damit die Kette
|
||
# aus dem Storno allein lesbar ist.
|
||
"korrektur_nummer": folge_nummer or "",
|
||
"storno_datum": str(meta.get("datum") or ""),
|
||
"kunde": kunde,
|
||
}
|
||
name = f"Storno_{safe_filename(nummer)}"
|
||
if kopf["nachname"]:
|
||
name += "_" + safe_filename(kopf["nachname"])
|
||
pfad = os.path.join(out_dir, name + ".pdf")
|
||
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"Storno-PDF konnte nicht erstellt werden:\n{e}")
|
||
print("[storno] FEHLER:", e)
|
||
return
|
||
|
||
self.storno_stand = {
|
||
"pfad": pfad, "nummer": nummer, "zu": alt_nummer,
|
||
"vorgang": str(meta.get("vorgang") or alt_nummer),
|
||
"zu_datum": str(meta.get("datum") or ""),
|
||
"jahr_vorher": self.cfg.get("rechnung_jahr", datetime.date.today().year),
|
||
"zaehler_vorher": self.cfg.get("rechnung_zaehler", 0),
|
||
}
|
||
self._zaehler_aus_nummer_speichern(nummer)
|
||
print(f"[storno] erstellt -> {pfad}")
|
||
|
||
fehlend = self._formular_aus_meta(meta)
|
||
self.r_nummer.set(folge_nummer or "")
|
||
self._storno_anzeigen()
|
||
|
||
hinweis = ""
|
||
if fehlend:
|
||
hinweis = (f"\n\nZum Kunden: {fehlend}.\nBitte oben prüfen und ergänzen – "
|
||
"ohne vollständige Anschrift wird die Rechnung nicht geschrieben.")
|
||
messagebox.showinfo(
|
||
"Storno erstellt",
|
||
f"Stornorechnung {nummer} zu Rechnung {alt_nummer} liegt hier:\n{pfad}\n\n"
|
||
f"Jetzt die berichtigte Rechnung {self.r_nummer.get()} anpassen und "
|
||
f"\"PDF erstellen\" drücken.{hinweis}")
|
||
|
||
def _formular_aus_meta(self, meta):
|
||
"""Formular mit den Daten der alten Rechnung füllen.
|
||
|
||
Gibt zurück, was NICHT übernommen werden konnte - aeltere PDFs (v1/v2)
|
||
tragen weder Anschrift noch Positionen in den Kenndaten.
|
||
"""
|
||
kunde, woher = kundendaten_ermitteln(meta)
|
||
self.k_vorname.set(kunde.get("vorname", ""))
|
||
self.k_name.set(kunde.get("nachname", ""))
|
||
self.k_anrede.set(str(kunde.get("anrede", "") or ""))
|
||
self.k_zusatz.set(str(kunde.get("zusatz", "") or ""))
|
||
self.k_strasse.set(str(kunde.get("strasse", "") or ""))
|
||
self.k_plzort.set(str(kunde.get("plzort", "") or ""))
|
||
self.r_anreise.set(str(meta.get("anreise", "") or ""))
|
||
self.r_abreise.set(str(meta.get("abreise", "") or ""))
|
||
self.r_datum.set(datetime.date.today().strftime("%d.%m.%Y"))
|
||
|
||
for nr, var in self.anzahl_vars.items():
|
||
var.set("")
|
||
self.personen_vars[nr].set("")
|
||
for p in meta.get("positionen") or []:
|
||
nr = p.get("nr")
|
||
if nr in self.anzahl_vars:
|
||
self.anzahl_vars[nr].set(str(p.get("anzahl", "") or ""))
|
||
self.personen_vars[nr].set(str(p.get("personen", "") or ""))
|
||
self.recalc()
|
||
|
||
hinweise = []
|
||
if woher:
|
||
hinweise.append("aus dem Rechnungstext gelesen: " + ", ".join(woher))
|
||
fehlt = [feld for feld, wert in (("Straße", self.k_strasse.get()),
|
||
("PLZ / Ort", self.k_plzort.get()),
|
||
("Nachname", self.k_name.get()))
|
||
if not wert]
|
||
if fehlt:
|
||
hinweise.append("steht in der alten Rechnung nicht drin: " + ", ".join(fehlt))
|
||
if not (meta.get("positionen") or []):
|
||
hinweise.append("Leistungen konnten nicht übernommen werden")
|
||
return "; ".join(hinweise)
|