rechnungstool/storno.py
TheMockTv 2277a12e0f Zwei getrennte Knoepfe und app.py in Module aufgeteilt
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
2026-09-02 19:41:00 +02:00

57 lines
2.8 KiB
Python

# -*- coding: utf-8 -*-
"""Den spiegelbildlichen Storno zu einer Rechnung rechnen."""
import berechnung
def storno_rechnung_bauen(meta, cfg):
"""Baut aus den Kenndaten der alten Rechnung den spiegelbildlichen Storno.
Alle Betraege negativ. Je USt-Satz eine Zeile - die einzelnen Leistungen der
alten Rechnung werden NICHT wiederholt: aufgehoben wird der Beleg als Ganzes,
und die Positionen stehen in aelteren PDFs gar nicht in den Kenndaten.
"""
r = berechnung.Rechnung()
nummer = meta.get("rechnungsnummer", "")
bloecke = meta.get("ust_bloecke") or []
if not bloecke:
# v1-PDF ohne Aufteilung: alles auf den ermaessigten Satz, sonst faellt
# die USt unter den Tisch.
brutto = float(meta.get("zwischensumme") or 0)
satz = int((cfg.get("steuersaetze") or [{"satz": 7}])[0].get("satz", 7))
netto = berechnung.runde(brutto * 100 / (100 + satz)) if satz else brutto
bloecke = [{"satz": satz, "netto": netto, "ust": berechnung.runde(brutto - netto),
"brutto": brutto}]
saetze = {int(s.get("satz", -1)): s for s in cfg.get("steuersaetze", [])}
for i, b in enumerate(bloecke, start=1):
satz = int(float(b.get("satz", 0) or 0))
brutto = -abs(berechnung.runde(b.get("brutto") or 0))
netto = -abs(berechnung.runde(b.get("netto") or 0))
ust = -abs(berechnung.runde(b.get("ust") or 0))
r.zeilen.append(berechnung.PositionsZeile(
nr=i, leistung=f"Storno der Rechnung {nummer} ({satz} % USt)",
satz=satz, preis=brutto, anzahl=1, personen=0, pro_person=False,
brutto=brutto, netto=netto))
info = saetze.get(satz, {})
r.bloecke.append(berechnung.Steuerblock(
satz=satz, netto=netto, ust=ust,
bezeichnung=str(info.get("bezeichnung", "") or "Mehrwertsteuer"),
extra_blatt=bool(info.get("extra_blatt", False))))
r.netto_gesamt = berechnung.runde(sum(b.netto for b in r.bloecke))
r.ust_gesamt = berechnung.runde(sum(b.ust for b in r.bloecke))
r.brutto = berechnung.runde(r.netto_gesamt + r.ust_gesamt)
# Beherbergungssteuer wird mitstorniert, sonst stimmt die Meldung ans Amt nicht.
# Die Bezeichnung kommt aus dem Satz mit "extra_blatt" - genau dem, aus dem
# berechne() den Aufschlag baut.
steuer = berechnung.runde(meta.get("steuer_betrag") or 0)
if steuer:
extra = next((s for s in cfg.get("steuersaetze", []) if s.get("extra_blatt")), {})
r.aufschlaege.append(berechnung.Aufschlag(
satz=float(meta.get("steuer_satz") or extra.get("satz", 0) or 0),
bezeichnung=str(extra.get("bezeichnung", "") or "Beherbergungssteuer"),
basis=r.brutto, betrag=-abs(steuer), extra_blatt=True))
r.gesamt = berechnung.runde(r.brutto + sum(a.betrag for a in r.aufschlaege))
return r