Erklaerung fuers Amt / die Steuerberaterin als PDF

Auf seine Bitte: ein Blatt, das erklaert, WAS an Rechnungsschreibung und
Steuerbuch geaendert wurde, WARUM, und auf welche Paragraphen es sich
stuetzt. Drei Seiten, im Briefkopf des Betriebs.

Inhalt: Ausgangslage (dieselbe Leistung mehrfach im Ordner, unvollstaendige
Empfaengerangaben), die zwei festen Wege (Berichtigung / Storno), die drei
Storno-Faelle A/B/C, Pflichtfelder, Umgang mit doppelten Nummern, wie ein
Storno in die Steueranmeldung eingeht - dazu eine Tabelle mit elf
Fundstellen (UStG, UStDV, AO, SaechsKAG, Beherbergungssteuersatzung
Markkleeberg) und was im Programm daraus folgt.

Am Ende die zwei Punkte, die noch offen sind: steuerbefreite
Uebernachtungen (§ 4 Abs. 1 Nr. 1 der Satzung - Kinder bis 18, im Katalog
gibt es eine eigene Position dafuer) und die Abrundung je Uebernachtung
(§ 3 Abs. 3). Beide wirken zugunsten der Stadt, sind aber ungebaut - das
gehoert offen aufs Blatt und nicht verschwiegen.

Als Skript und nicht von Hand geschrieben, weil der Text Paragraphen
nennt: aendert sich das Programm, aendert man den Text hier und erzeugt
das Blatt neu.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017bEsoFUk16DfnNA7MjY36H
This commit is contained in:
TheMockTv 2026-09-04 01:57:20 +02:00
parent ef3eef2852
commit 965278020a

267
doku_aenderungen_pdf.py Normal file
View file

@ -0,0 +1,267 @@
# -*- coding: utf-8 -*-
"""Erzeugt die Erklaerung fuers Amt / die Steuerberaterin als PDF.
python doku_aenderungen_pdf.py [Zielpfad.pdf]
Warum als eigenes Skript und nicht von Hand geschrieben: der Text nennt
Paragraphen und Betraege. Wenn sich etwas am Programm aendert, aendert man den
Text hier und erzeugt das Blatt neu - statt eine Word-Datei zu suchen, die
niemand mehr findet.
Briefkopf und Farben kommen aus derselben Konfiguration wie die Rechnungen
(firma.lade()), damit das Blatt erkennbar zum Betrieb gehoert.
"""
import os
import sys
from reportlab.lib import colors
from reportlab.lib.enums import TA_JUSTIFY
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.units import mm
from reportlab.pdfbase.pdfmetrics import stringWidth
from reportlab.platypus import (BaseDocTemplate, Frame, KeepTogether, PageTemplate,
Paragraph, Spacer, Table, TableStyle)
HIER = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HIER)
import firma as firma_lib # noqa: E402
ACCENT = colors.HexColor("#1f5c3d")
GREY_BG = colors.HexColor("#eef1ee")
LINE = colors.HexColor("#c8d0c8")
MUTED = colors.HexColor("#555555")
PAGE_W, PAGE_H = A4
MARGIN_L = MARGIN_R = 20 * mm
CONTENT_W = PAGE_W - MARGIN_L - MARGIN_R
def stile():
return {
"titel": ParagraphStyle("titel", fontName="Helvetica-Bold", fontSize=17,
textColor=ACCENT, leading=21, spaceAfter=2),
"untertitel": ParagraphStyle("untertitel", fontName="Helvetica", fontSize=9.5,
textColor=MUTED, leading=13, spaceAfter=10),
"h": ParagraphStyle("h", fontName="Helvetica-Bold", fontSize=11.5,
textColor=ACCENT, leading=15, spaceBefore=9, spaceAfter=4),
"t": ParagraphStyle("t", fontName="Helvetica", fontSize=9.5, leading=13.5,
alignment=TA_JUSTIFY, spaceAfter=5),
"klein": ParagraphStyle("klein", fontName="Helvetica", fontSize=8, leading=11,
textColor=MUTED, spaceAfter=4),
"th": ParagraphStyle("th", fontName="Helvetica-Bold", fontSize=8.5, leading=11),
"z": ParagraphStyle("z", fontName="Helvetica", fontSize=8.5, leading=11.5),
}
def kopf_fuss(canvas, doc, cfg):
canvas.saveState()
f = cfg
canvas.setFont("Helvetica-Bold", 12)
canvas.setFillColor(ACCENT)
canvas.drawRightString(PAGE_W - MARGIN_R, PAGE_H - 15 * mm, f.get("name", ""))
canvas.setFont("Helvetica-Oblique", 8)
canvas.setFillColor(MUTED)
canvas.drawRightString(PAGE_W - MARGIN_R, PAGE_H - 20 * mm, f.get("untertitel", ""))
canvas.setStrokeColor(ACCENT)
canvas.setLineWidth(0.8)
canvas.line(MARGIN_L, PAGE_H - 23 * mm, PAGE_W - MARGIN_R, PAGE_H - 23 * mm)
canvas.setFont("Helvetica", 7.5)
canvas.setFillColor(MUTED)
canvas.drawString(MARGIN_L, 12 * mm, f.get("absender", ""))
canvas.drawRightString(PAGE_W - MARGIN_R, 12 * mm, f"Seite {canvas.getPageNumber()}")
canvas.restoreState()
def tabelle(daten, st, breiten):
zeilen = [[Paragraph(z, st["th"] if i == 0 else st["z"]) for z in reihe]
for i, reihe in enumerate(daten)]
t = Table(zeilen, colWidths=breiten, repeatRows=1, hAlign="LEFT")
t.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), GREY_BG),
("LINEBELOW", (0, 0), (-1, 0), 0.8, ACCENT),
("LINEBELOW", (0, 1), (-1, -1), 0.3, LINE),
("VALIGN", (0, 0), (-1, -1), "TOP"),
("TOPPADDING", (0, 0), (-1, -1), 4),
("BOTTOMPADDING", (0, 0), (-1, -1), 4),
("LEFTPADDING", (0, 0), (-1, -1), 4),
("RIGHTPADDING", (0, 0), (-1, -1), 4),
]))
return t
def bauen(pfad, cfg):
st = stile()
s = []
P = lambda text, art="t": s.append(Paragraph(text, st[art])) # noqa: E731
P("Änderungen an Rechnungsschreibung und Steuerbuch", "titel")
P("Campinghof Bartl · Stand 4. September 2026 · Rechnungstool v1.5.5, "
"Steuerbuch (Beherbergungssteuer) v1.6.7", "untertitel")
P("Warum überhaupt geändert wurde", "h")
P("Im laufenden Betrieb sind zwei Dinge aufgefallen, die beide dieselbe Ursache haben – "
"es gab keine feste Vorgabe, wie eine bereits geschriebene Rechnung korrigiert wird:")
P("<b>1. Dieselbe Leistung stand mehrfach im Rechnungsordner.</b> Wenn an einer Rechnung etwas "
"nicht stimmte, wurde mit dem alten Programm einfach eine neue geschrieben – mit einer neuen "
"Nummer. Die alte blieb liegen. Damit stand derselbe Aufenthalt zweimal im Ordner und wäre "
"zweimal in die Beherbergungssteuer-Anmeldung eingegangen.")
P("<b>2. Beim Empfänger stand teilweise nur der Vor- oder nur der Nachname.</b> Die "
"Anmeldekarten der Gäste enthalten alle Angaben; auf der Rechnung landete davon nicht immer "
"alles.")
P("Was jetzt gilt – feste Wege statt freier Hand", "h")
P("Eine geschriebene Rechnung lässt sich nicht mehr formlos ändern. Es gibt genau zwei Wege, "
"und beide erzeugen einen eigenen, nachvollziehbaren Beleg:")
s.append(tabelle([
["Fall", "Weg im Programm", "Was der Beleg sagt"],
["Angaben waren falsch, Leistung und Beträge stimmen "
"(z. B. Name, Anschrift)",
"<b>Berichtigung</b>",
"Eigenes Blatt, das sich auf die Rechnung bezieht. Die Rechnung <b>behält ihre "
"Nummer</b>, Beträge bleiben unverändert."],
["Die Rechnung selbst muss weg (falsche Leistung, "
"Doppelung, geplatzte Buchung)",
"<b>Storno</b> mit drei Auswahlmöglichkeiten",
"Eigene Stornorechnung mit Minusbeträgen, die die Rechnung nach Nummer, Datum und "
"Empfänger benennt."],
], st, [42 * mm, 38 * mm, CONTENT_W - 80 * mm]))
s.append(Spacer(1, 4 * mm))
P("Die drei Wege im Storno", "h")
P("Nach dem Storno muss auf dem Blatt stehen, wie es weitergeht – sonst wartet der Gast auf "
"eine Rechnung, die nie kommt, oder es bleibt eine Nummer unnötig reserviert. Deshalb fragt "
"das Programm zuerst:")
s.append(tabelle([
["", "Fall", "Folge auf dem Beleg"],
["A", "Es folgt eine neue, richtige Rechnung",
"Die Nummer der berichtigten Rechnung steht bereits auf dem Storno: "
"alte Nummer → Storno → neue Nummer."],
["B", "Die Leistung ist schon unter einer anderen Nummer abgerechnet "
"(der Doppelungsfall)",
"Das Storno verweist auf die <b>bleibende</b> Rechnung. Das Programm prüft, ob es diese "
"Nummer gibt und ob Gast, Zeitraum und Betrag dazu passen. Es wird keine Nummer "
"reserviert und kein Vorgang bleibt offen."],
["C", "Es wird gar nichts abgerechnet (Buchung geplatzt, Rechnung irrtümlich)",
"Auf dem Blatt steht, dass keine weitere Rechnung folgt. Es wird ebenfalls keine Nummer "
"reserviert."],
], st, [8 * mm, 52 * mm, CONTENT_W - 60 * mm]))
s.append(Spacer(1, 4 * mm))
P("Pflichtfelder beim Empfänger", "h")
P("Die Felder für den Empfänger sind jetzt <b>Pflicht</b>. Das ist keine Verschärfung durch "
"das Programm, sondern geltendes Recht: eine Rechnung muss den vollständigen Namen und die "
"vollständige Anschrift des Leistungsempfängers enthalten. Für die Beherbergungssteuer kommt "
"hinzu, dass die Meldescheine ohnehin Name, Wohnanschrift, Geburtsdatum sowie An- und "
"Abreise tragen und zusammen mit den Rechnungskopien aufzubewahren sind – die Angaben liegen "
"also vor und gehören auf den Beleg. Nur so lässt sich später zweifelsfrei sagen, "
"<b>wer zu welcher Rechnung gehört</b>.")
P("Doppelte Rechnungsnummern werden angezeigt, nicht weggerechnet", "h")
P("Das Steuerbuch meldet jede Nummer, die mehr als einmal vergeben wurde – rot in der Liste, "
"im Monatsreiter markiert und in einer eigenen Übersicht mit allen betroffenen Belegen. "
"Gerechnet wird über das ganze Jahr, weil dieselbe Nummer in zwei Monaten liegen kann. "
"<b>Die Beträge bleiben dabei in allen Summen stehen</b>: das Buch soll mit dem "
"Rechnungsordner übereinstimmen. Was zu tun ist, entscheidet der Betrieb – der vorgesehene "
"Weg steht im Programm: eine der beiden Rechnungen stornieren und, falls nötig, mit freier "
"Nummer neu ausstellen. Sobald das geschehen ist, verschwindet die Meldung von selbst, weil "
"eine stornierte Rechnung keine offene mehr ist.")
P("Wie ein Storno in die Steueranmeldung eingeht", "h")
P("Die aufgehobene Rechnung bleibt mit ihrem Plus im Monat der Rechnung stehen, die "
"Stornorechnung steht mit ihrem Minus im Monat des Stornos. Über das Jahr gleicht sich das "
"aus. Bewusst wurde der sichere Weg gewählt: im ersten Monat wird eher zu viel gemeldet und "
"im zweiten entsprechend weniger – nie zuerst zu wenig. Fällt eine Korrektur in einen Monat, "
"der bereits angemeldet ist, ist eine berichtigte Anmeldung vorgesehen; das amtliche Formular "
"hat dafür ein eigenes Ankreuzfeld, und die Anmeldung steht ohnehin unter dem Vorbehalt der "
"Nachprüfung.")
P("Damit das Blatt fürs Amt aus sich heraus verständlich ist, trägt jede betroffene Zeile eine "
"<b>Begründung</b>: „storniert mit 2026-901“, „Storno zu 2026-900“, „Neuausstellung“. "
"So ist auch dann nachvollziehbar, was zusammengehört, wenn Rechnung und Storno in "
"verschiedenen Monaten liegen und gar nicht auf demselben Blatt stehen.")
P("Die Rechtsgrundlagen im Einzelnen", "h")
P("„Satzung“ meint die Satzung über die Erhebung einer Beherbergungssteuer in der Stadt "
"Markkleeberg vom 6. Februar 2024, anzuwenden auf Beherbergungsleistungen ab dem "
"1. Januar 2025.", "klein")
s.append(tabelle([
["Vorschrift", "Inhalt", "Was im Programm daraus folgt"],
["§ 14 Abs. 4 Nr. 1 UStG", "Vollständiger Name und vollständige Anschrift des "
"Leistungsempfängers", "Empfängerfelder sind Pflichtfelder"],
["§ 14 Abs. 4 Nr. 4 UStG", "Fortlaufende Nummer, vom Aussteller <b>einmalig</b> vergeben",
"Doppelte Nummern werden gemeldet; Stornos und Neuausstellungen bekommen eigene Nummern, "
"es werden keine reserviert, die nicht gebraucht werden"],
["§ 31 Abs. 5 UStDV", "Eine Rechnung kann berichtigt werden; das Dokument muss sich "
"<b>spezifisch und eindeutig</b> auf die Rechnung beziehen",
"Berichtigungsblatt bezieht sich auf Nummer und Datum; das Storno nennt zusätzlich den "
"Empfänger – bei einer doppelt vergebenen Nummer wäre die Nummer allein kein "
"eindeutiger Bezug"],
["§ 14c Abs. 1 UStG", "Wer in einer Rechnung mehr Steuer ausweist, als er schuldet, "
"schuldet auch den Mehrbetrag",
"Deshalb wird eine doppelt geschriebene Rechnung storniert und nicht einfach liegen "
"gelassen; das Berichtigungsblatt sagt ausdrücklich, dass es keine zweite Leistung ist"],
["§ 146 Abs. 4 AO", "Eine Buchung darf nicht so verändert werden, dass der ursprüngliche "
"Inhalt nicht mehr feststellbar ist",
"Kein freies Bearbeiten von Buchungen mehr; jede Änderung entsteht über einen eigenen "
"Beleg (Storno oder Berichtigung), gelöscht wird nichts"],
["§ 147 AO (über § 7 Abs. 4 der Satzung)", "Aufbewahrung der Rechnungskopien und "
"Meldescheine, Vorlage auf Anforderung", "Rechnungen bleiben als PDF im Ordner; das "
"Steuerbuch liest sie und legt sie nicht neu an"],
["§ 3 Abs. 1 und 3 der Satzung",
"Bemessungsgrundlage ist das Übernachtungsentgelt einschließlich Umsatzsteuer; "
"5 % je Übernachtung, abgerundet auf volle Cent; höchstens 21 Tage",
"Die Steuer wird auf die Zwischensumme der Rechnung gerechnet und auf dem Beleg "
"gesondert ausgewiesen"],
["§ 6 der Satzung", "Der Steueranspruch entsteht mit Beginn der Beherbergung; ohne Entgelt "
"entsteht keine Steuer", "Ein Angebot löst nichts aus; erst die Rechnung wird gebucht"],
["§ 7 Abs. 3 der Satzung", "Meldescheine mit Name, Wohnanschrift, Geburtsdatum sowie "
"An- und Abreise", "Die Angaben liegen vor und gehören auf die Rechnung"],
["§ 7 Abs. 5 der Satzung", "Die im Kalendermonat <b>vereinnahmte</b> Steuer ist bis zum "
"10. des Folgemonats selbst zu berechnen, anzumelden und abzuführen",
"Monatsbericht als Aufstellung zur Anmeldung; Nullmonate bleiben nachvollziehbar"],
["§§ 164, 168 AO (über § 3 Abs. 1 SächsKAG)", "Die Steueranmeldung steht einer Festsetzung "
"unter Vorbehalt der Nachprüfung gleich",
"Ein bereits angemeldeter Monat kann berichtigt werden – deshalb ist der sichere Weg, "
"im Zweifel eher zu viel zu melden"],
], st, [42 * mm, 55 * mm, CONTENT_W - 97 * mm]))
s.append(Spacer(1, 4 * mm))
P("Zwei Punkte, die noch offen sind", "h")
P("<b>1. Steuerbefreite Übernachtungen.</b> Nach § 4 Abs. 1 Nr. 1 der Satzung sind Kinder und "
"Jugendliche bis zum vollendeten 18. Lebensjahr von der Beherbergungssteuer befreit. Im "
"Leistungskatalog gibt es eine eigene Position „Personengebühr Kinder ab 2 Jahre bis 14 "
"Jahre“. Dieser Umsatz wird derzeit mitversteuert. Das amtliche Anmeldeformular sieht dafür "
"eine eigene Zeile vor („abzüglich Umsätze aus steuerbefreiten Übernachtungen“). Bis zur "
"Umstellung wird also eher zu viel als zu wenig gemeldet.")
P("<b>2. Abrundung je Übernachtung.</b> Nach § 3 Abs. 3 der Satzung wird der Steueranteil je "
"einzelner Übernachtung auf volle Cent abgerundet. Derzeit werden 5 % auf die Summe "
"gerechnet. Das amtliche Formular trägt dem mit einer eigenen Zeile Rechnung "
"(„tatsächlich einbehaltene Beherbergungssteuer“).")
P("Beide Punkte wirken zugunsten der Stadt und nicht zulasten – sie führen zu einer eher zu "
"hohen, nicht zu einer zu niedrigen Anmeldung.", "klein")
P("Zum Schluss", "h")
P("Die Programme ändern nichts rückwirkend und löschen nichts. Jede Korrektur ist ein eigener "
"Beleg mit eigener Nummer, der sagt, worauf er sich bezieht. Damit lässt sich zu jedem "
"Zeitpunkt nachvollziehen, welche Rechnung zu welchem Gast gehört, welche aufgehoben wurde "
"und womit – im Rechnungsordner, im Steuerbuch und auf dem Blatt für die Stadt.")
doc = BaseDocTemplate(pfad, pagesize=A4,
leftMargin=MARGIN_L, rightMargin=MARGIN_R,
topMargin=30 * mm, bottomMargin=18 * mm,
title="Änderungen an Rechnungsschreibung und Steuerbuch",
author=cfg.get("name", ""))
rahmen = Frame(MARGIN_L, 18 * mm, CONTENT_W, PAGE_H - 30 * mm - 18 * mm, id="f")
doc.addPageTemplates([PageTemplate(id="seite", frames=[rahmen],
onPage=lambda c, d: kopf_fuss(c, d, cfg))])
doc.build(s)
return pfad
if __name__ == "__main__":
ziel = sys.argv[1] if len(sys.argv) > 1 else os.path.join(
os.path.expanduser("~"), "Desktop",
"Campinghof_Aenderungen_Rechnungswesen_2026-09-04.pdf")
cfg = firma_lib.lade()
bauen(ziel, cfg)
print("geschrieben:", ziel, os.path.getsize(ziel), "Bytes")