Aus der Proforma-Rechnung wird ein unverbindliches Angebot
Seine Frage war: "was ist rechtssicher, kostenvoranschlag oder proforma - ich bin kein jurist, deswegen sage ich ja § schauen". Nachgesehen: * Kostenanschlag ist ein Begriff des WERKvertrags (§ 632 Abs. 3 BGB "Ein Kostenanschlag ist im Zweifel nicht zu verguten", § 649 BGB) - das ist der Handwerker-Fall. Bartl vermietet, das passt nicht. * "Proforma-Rechnung" steht in keinem Gesetz (Zoll- und Exportbegriff) und traegt als Einziges das Wort "Rechnung". * Das Angebot ist § 145 BGB: gebunden ist man daran, "es sei denn, dass er die Gebundenheit ausgeschlossen hat" - deshalb steht "unverbindlich" drauf. * Im Camping- und Hotelgeschaeft ist "unverbindliches Angebot" der uebliche Begriff (auch in den Muster-AGB des Branchenverbands BVCD). Seine Entscheidung: "dann mach angebot draus, ist das gaengigste" - Begruendung "dann ist der name rechnung weg und erweckt nicht den anschein das man eine bekommen hat". Deshalb heisst es jetzt ueberall Angebot: Ueberschrift "Unverbindliches Angebot", "Angebot an:", "Angebot-Nr.: 2026-A001" und "Datum:" statt "Rechnungsdatum:" - das Wort Rechnung kommt im ganzen Kopf nicht mehr vor, und genau das prueft pruef_angebot.py auch nach. Eigene Reihe 2026-A001, Dateiname Angebot_..., eigener Zaehler (angebot_jahr/angebot_zaehler). Steuerlich aendert der Name nichts (§ 14 Abs. 1 S. 1 UStG: die Bezeichnung des Dokuments ist gleichgueltig) - die Sicherheit kommt weiter vom fehlenden Steuerausweis, siehe UStAE 14c.1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYeAfLtccFrbU3MTj1MMTx
This commit is contained in:
parent
c190887621
commit
82d84f7aea
4 changed files with 96 additions and 90 deletions
|
|
@ -278,7 +278,7 @@ def _seite_inhalt(story, st, cfg, rechnung, kopf, gesamtrechnung_betont):
|
|||
story.append(Spacer(1, 5 * mm))
|
||||
|
||||
# Kundenadresse (links) | Rechnungsinfos (rechts)
|
||||
kunde_flow = [Paragraph("Proforma an:" if kopf.get("art") == "proforma"
|
||||
kunde_flow = [Paragraph("Angebot an:" if kopf.get("art") == "angebot"
|
||||
else "Rechnung an:", st["addr_label"])]
|
||||
for z in kopf.get("kunde_zeilen", []):
|
||||
if str(z).strip():
|
||||
|
|
@ -291,12 +291,14 @@ def _seite_inhalt(story, st, cfg, rechnung, kopf, gesamtrechnung_betont):
|
|||
# Keine Positionstabelle, keine Naechte im Kopf - der Zeitraum steht in der
|
||||
# einen Textzeile, so wie Bartl das Blatt bisher von Hand geschrieben hat.
|
||||
montage = kopf.get("vorlage") == "montage"
|
||||
proforma = art == "proforma"
|
||||
angebot = art == "angebot"
|
||||
info_rows = [
|
||||
("Stornonummer:" if art == "storno" else
|
||||
"Proforma-Nr.:" if proforma else "Rechnungsnummer:",
|
||||
"Angebot-Nr.:" if angebot else "Rechnungsnummer:",
|
||||
kopf.get("rechnungsnummer", "")),
|
||||
("Rechnungsdatum:", kopf.get("datum", "")),
|
||||
# Auf dem Angebot faellt das Wort "Rechnung" ueberall weg: es soll gar
|
||||
# nicht erst der Eindruck entstehen, der Gast haette schon eine.
|
||||
("Datum:" if angebot else "Rechnungsdatum:", kopf.get("datum", "")),
|
||||
]
|
||||
# Der Bezug auf die alte Rechnung ist Pflicht (§ 31 Abs. 5 UStDV): Nummer UND Datum.
|
||||
if art == "storno" and kopf.get("storno_zu"):
|
||||
|
|
@ -360,39 +362,38 @@ def _seite_inhalt(story, st, cfg, rechnung, kopf, gesamtrechnung_betont):
|
|||
# eindeutig als Aufhebung erkennbar ist.
|
||||
titel = {"storno": "Stornorechnung",
|
||||
"berichtigung": "Berichtigte Rechnung",
|
||||
"proforma": "Proforma-Rechnung (Kostenvoranschlag)"}.get(art, "Rechnungsübersicht")
|
||||
"angebot": "Unverbindliches Angebot"}.get(art, "Rechnungsübersicht")
|
||||
titel += " (für das Amt)" if gesamtrechnung_betont else ""
|
||||
story.append(Paragraph(titel, st["h"]))
|
||||
if proforma:
|
||||
if angebot:
|
||||
# Ohne diesen Satz sieht das Blatt aus wie eine Rechnung. Es ist keine -
|
||||
# und ohne die deutliche Kennzeichnung kann das Finanzamt es als eine
|
||||
# werten. Ein Steuersatz oder Steuerbetrag steht bewusst NIRGENDS auf
|
||||
# dem Blatt: wer Steuer ausweist, schuldet sie (§ 14c UStG).
|
||||
story.append(Paragraph(
|
||||
"<b>Dies ist ein Kostenvoranschlag</b> – eine unverbindliche Vorab-"
|
||||
"Aufstellung der Kosten. Sie ist <b>keine Rechnung im Sinne des § 14 "
|
||||
"UStG</b>, berechtigt nicht zum Vorsteuerabzug und ist nicht zu bezahlen. "
|
||||
"Die Nummer oben ist <b>keine Rechnungsnummer</b>. Alle Beträge in Euro "
|
||||
"inkl. gesetzlicher Umsatzsteuer; ausgewiesen wird sie erst in der "
|
||||
"Rechnung nach dem Aufenthalt.", st["dank"]))
|
||||
"<b>Dies ist ein unverbindliches Angebot</b> und <b>keine Rechnung im "
|
||||
"Sinne des § 14 UStG</b>: es berechtigt nicht zum Vorsteuerabzug und ist "
|
||||
"nicht zu bezahlen. Die Nummer oben ist <b>keine Rechnungsnummer</b>. "
|
||||
"Alle Beträge in Euro inkl. gesetzlicher Umsatzsteuer; ausgewiesen wird "
|
||||
"sie erst in der Rechnung nach dem Aufenthalt.", st["dank"]))
|
||||
story.append(Spacer(1, 3 * mm))
|
||||
|
||||
if montage:
|
||||
_montage_zeile(story, st, rechnung, kopf)
|
||||
else:
|
||||
_positionstabelle(story, st, rechnung, ohne_ust=proforma)
|
||||
_positionstabelle(story, st, rechnung, ohne_ust=angebot)
|
||||
story.append(Spacer(1, 3 * mm))
|
||||
|
||||
# ---- Summenblock: Netto -> USt je Satz -> Zwischensumme (brutto)
|
||||
# -> zzgl. Beherbergungssteuer -> Gesamtbetrag ----
|
||||
# Auf der Proforma steht weder Netto noch Umsatzsteuer: ausgewiesen wird
|
||||
# Auf dem Angebot steht weder Netto noch Umsatzsteuer: ausgewiesen wird
|
||||
# sie erst mit der Rechnung. Die Beherbergungssteuer ist keine Umsatzsteuer
|
||||
# und darf bleiben - der Gast soll sehen, was er zahlen wird.
|
||||
summen_rows = [] if proforma else [[
|
||||
summen_rows = [] if angebot else [[
|
||||
Paragraph("Nettobetrag:", st["info_l"]),
|
||||
Paragraph(eur(rechnung.netto_gesamt), st["cell_r"]),
|
||||
]]
|
||||
for b in ([] if proforma else rechnung.bloecke):
|
||||
for b in ([] if angebot else rechnung.bloecke):
|
||||
name = b.bezeichnung or "USt"
|
||||
summen_rows.append([
|
||||
Paragraph(f"{name} {b.satz} %:", st["info_l"]),
|
||||
|
|
@ -408,10 +409,10 @@ def _seite_inhalt(story, st, cfg, rechnung, kopf, gesamtrechnung_betont):
|
|||
#
|
||||
# Auf der Kundenseite bleibt alles wie gehabt - dort MUSS die Steuer ausgewiesen
|
||||
# sein und im Gesamtbetrag stecken, das ist der Betrag, den der Gast zahlt.
|
||||
# Auf der Proforma wird gar keine Steuer ausgewiesen - weder Umsatzsteuer
|
||||
# Auf dem Angebot wird gar keine Steuer ausgewiesen - weder Umsatzsteuer
|
||||
# noch Beherbergungssteuer. Es steht nur, was der Gast voraussichtlich
|
||||
# zahlt; aufgeschluesselt wird erst in der Rechnung.
|
||||
if rechnung.aufschlaege and not gesamtrechnung_betont and not proforma:
|
||||
if rechnung.aufschlaege and not gesamtrechnung_betont and not angebot:
|
||||
summen_rows.append([
|
||||
Paragraph("Zwischensumme:", st["info_l"]),
|
||||
Paragraph(eur(rechnung.brutto), st["cell_r"]),
|
||||
|
|
@ -425,7 +426,7 @@ def _seite_inhalt(story, st, cfg, rechnung, kopf, gesamtrechnung_betont):
|
|||
|
||||
endsumme = rechnung.brutto if gesamtrechnung_betont else rechnung.gesamt
|
||||
summen_rows.append([
|
||||
Paragraph("Voraussichtlicher Gesamtbetrag:" if proforma else
|
||||
Paragraph("Voraussichtlicher Gesamtbetrag:" if angebot else
|
||||
"Rechnungsbetrag Insgesamt:" if montage else "Gesamtbetrag:",
|
||||
st["total_l"]),
|
||||
Paragraph(eur(endsumme), st["total_v"]),
|
||||
|
|
@ -444,7 +445,7 @@ def _seite_inhalt(story, st, cfg, rechnung, kopf, gesamtrechnung_betont):
|
|||
|
||||
# Hinweis zur Beherbergungssteuer - nur auf der Kundenseite. Auf dem Amt-Blatt
|
||||
# wird die Steuer gar nicht mehr ausgewiesen, dann waere der Satz sinnlos.
|
||||
if proforma:
|
||||
if angebot:
|
||||
story.append(Spacer(1, 1.5 * mm))
|
||||
story.append(Paragraph(
|
||||
"In diesem Betrag sind alle Steuern und Abgaben enthalten. Aufgeschlüsselt "
|
||||
|
|
@ -501,14 +502,15 @@ def _seite_inhalt(story, st, cfg, rechnung, kopf, gesamtrechnung_betont):
|
|||
"ihre Nummer.", st["dank"]))
|
||||
if texte.get("dank1"):
|
||||
block.append(Paragraph(texte["dank1"], st["dank"]))
|
||||
elif proforma:
|
||||
elif angebot:
|
||||
# Dank- und Zahlungstexte waeren hier falsch: der Aufenthalt liegt
|
||||
# noch vor uns, und bezahlt wird nichts. Der Hinweis "bitte die
|
||||
# Rechnungsnummer angeben" wuerde dem Blatt sogar widersprechen.
|
||||
block.append(Paragraph(
|
||||
"Diese Aufstellung ist unverbindlich und dient nur zur Information. "
|
||||
"Sie ist keine Zahlungsaufforderung – die Rechnung erhalten Sie nach "
|
||||
"dem Aufenthalt. Für Rückfragen stehen wir gern zur Verfügung.",
|
||||
"Dieses Angebot ist freibleibend und unverbindlich (§ 145 BGB) und "
|
||||
"keine Zahlungsaufforderung. Sagen Sie uns Bescheid, dann reservieren "
|
||||
"wir – die Rechnung erhalten Sie nach dem Aufenthalt. Für Rückfragen "
|
||||
"stehen wir gern zur Verfügung.",
|
||||
st["dank"]))
|
||||
else:
|
||||
if art == "korrektur" and kopf.get("ersetzt"):
|
||||
|
|
@ -550,12 +552,12 @@ def _kenndaten(rechnung, kopf):
|
|||
Steuerrechner summieren dadurch von allein richtig (+100 -100 +120 = 120).
|
||||
"""
|
||||
aufschlag = rechnung.aufschlaege[0] if rechnung.aufschlaege else None
|
||||
if kopf.get("art") == "proforma":
|
||||
# Eine Proforma ist kein Umsatz - sie traegt Nullen. Ein Programm, das
|
||||
if kopf.get("art") == "angebot":
|
||||
# Ein Angebot ist kein Umsatz - es traegt Nullen. Ein Programm, das
|
||||
# "art" nicht kennt, zaehlt dann 0,00 statt einer Uebernachtung, die nie
|
||||
# abgerechnet wurde.
|
||||
return json.dumps({
|
||||
"quelle": "rechnungstool", "version": 3, "art": "proforma",
|
||||
"quelle": "rechnungstool", "version": 3, "art": "angebot",
|
||||
"rechnungsnummer": kopf.get("rechnungsnummer", ""),
|
||||
"datum": kopf.get("datum", ""),
|
||||
"nachname": kopf.get("nachname", ""), "vorname": kopf.get("vorname", ""),
|
||||
|
|
@ -652,16 +654,16 @@ def render_rechnung(pfad, cfg, rechnung, kopf):
|
|||
leftMargin=MARGIN_L, rightMargin=MARGIN_R,
|
||||
topMargin=MARGIN_TOP, bottomMargin=MARGIN_BOT,
|
||||
title=({"storno": "Stornorechnung",
|
||||
"proforma": "Proforma-Rechnung"}.get(kopf.get("art"), "Rechnung"))
|
||||
"angebot": "Angebot"}.get(kopf.get("art"), "Rechnung"))
|
||||
+ f" {kopf.get('rechnungsnummer','')}",
|
||||
author=cfg["firma"].get("name", ""),
|
||||
subject=_kenndaten(rechnung, kopf), # maschinenlesbar fuers Steuerjournal
|
||||
)
|
||||
story = []
|
||||
_seite_inhalt(story, st, cfg, rechnung, kopf, gesamtrechnung_betont=False) # Kunde
|
||||
# Eine Proforma bekommt KEIN Blatt fuers Amt: gemeldet wird eine
|
||||
# Ein Angebot bekommt KEIN Blatt fuers Amt: gemeldet wird eine
|
||||
# Uebernachtung erst, wenn sie abgerechnet ist.
|
||||
if rechnung.aufschlaege and kopf.get("art") != "proforma":
|
||||
if rechnung.aufschlaege and kopf.get("art") != "angebot":
|
||||
story.append(PageBreak())
|
||||
story.append(_AmtMarke(marke))
|
||||
_seite_inhalt(story, st, cfg, rechnung, kopf, gesamtrechnung_betont=True) # Amt
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue