Amtsbericht: Spalte "Art" mit R/S/B und Legende unter der Tabelle

Sein Einwand: "du musst die markieren als rechnung und storno oder
berichtigung, und das kannst einfach mit buchstaben machen: r fuer
rechnung, s fuer storno, b fuer berichtigung. am footer kannst eine
erklaerung fuer die buchstaben machen."

Er hat recht - ohne die Marke muss man aus der Begruendung raten, was fuer
ein Beleg die Zeile ueberhaupt ist. Mit dem Buchstaben liest sich die
Kette von selbst:

  2026-900  R  storniert mit 2026-901
  2026-901  S  neue Rechnung 2026-902
  2026-902  B  Neuausstellung
  2026-911  S  abgerechnet mit 2026-905     (Doppelungsfall)

Unter der Tabelle steht die Legende: "R = Rechnung, S = Stornorechnung,
B = berichtigte Rechnung (Neuausstellung nach einem Storno)". Bewusst
direkt unter die Tabelle und nicht in die Fusszeile - wer eine einzelne
Zeile prueft, hat sie dann vor Augen und muss nicht blaettern.

Dazu "neu abgerechnet mit" auf "abgerechnet mit" gekuerzt: mit dem S
davor ist das "neu" ueberfluessig, und die Zeile bricht nicht mehr um.
Spaltenbreiten neu verteilt (18/18/9/42/Rest/12/18/9/18 mm).

Erklaerungs-PDF um die Art-Spalte ergaenzt. Pruefkette gruen.

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 02:29:48 +02:00
parent b2d933cb7c
commit 379afbd9c1
5 changed files with 50 additions and 15 deletions

Binary file not shown.

View file

@ -198,6 +198,23 @@ def _ort(firma):
# ------------------------------------------------------------- Monat --------
def _art_kuerzel(b) -> str:
"""Ein Buchstabe je Beleg - seine Ansage vom 04.09.2026: *"du musst die
markieren als rechnung und storno oder berichtigung, und das kannst einfach
mit buchstaben machen"*. Ohne die Marke muss man aus der Begruendung raten,
was fuer ein Beleg die Zeile ueberhaupt ist.
R = Rechnung
S = Stornorechnung
B = berichtigte Rechnung (die Neuausstellung nach einem Storno)
"""
if b.storno_zu or b.art == "storno":
return "S"
if b.art == "korrektur":
return "B"
return "R"
def _art_text(b, weg) -> str:
"""Was in der Rechnungsspalte unter der Nummer steht.
@ -211,7 +228,7 @@ def _art_text(b, weg) -> str:
# Ein Storno, der nur zurueckzeigt, laesst den Leser stehen ("wer soll
# denn sonst wissen, wo er suchen muss" - 04.09.2026).
if b.folge_nummer and b.folge_art == "vorhanden":
return f"neu abgerechnet mit {b.folge_nummer}"
return f"abgerechnet mit {b.folge_nummer}"
if b.folge_nummer:
return f"neue Rechnung {b.folge_nummer}"
if b.folge_art == "" and b.art == "storno" and not b.folge_nummer:
@ -250,13 +267,15 @@ def render_monat(pfad, firma, jahr: int, monat: int, buchungen):
# Eigene Spalte "Grund" statt einer zweiten Zeile unter der Nummer - seine
# Ansage vom 04.09.2026: "mach eine neue spalte mit grund oder so was".
kopf = ["Datum", "Rechnung", "Begründung", "Name", "Nächte", "Entgelt", "Satz", "Steuer"]
stile = ["th", "th", "th_l", "th_l", "th", "th", "th", "th"]
kopf = ["Datum", "Rechnung", "Art", "Begründung", "Name",
"Nächte", "Entgelt", "Satz", "Steuer"]
stile = ["th", "th", "th", "th_l", "th_l", "th", "th", "th", "th"]
daten = [[Paragraph(k, st[s_]) for k, s_ in zip(kopf, stile)]]
for b in liste:
daten.append([
Paragraph(b.datum.strftime("%d.%m.%Y"), st["zelle_c"]),
Paragraph(b.voll_nummer, st["zelle_c"]),
Paragraph(f'<b>{_art_kuerzel(b)}</b>', st["zelle_c"]),
Paragraph(_art_text(b, weg), st["zelle"]),
Paragraph(b.nachname, st["zelle"]),
Paragraph(str(b.naechte), st["zelle_c"]),
@ -267,7 +286,7 @@ def render_monat(pfad, firma, jahr: int, monat: int, buchungen):
if len(daten) == 1:
daten.append([Paragraph("", st["zelle_c"]),
Paragraph("<i>(keine Buchungen in diesem Monat)</i>", st["zelle"]),
"", "", "", "", "", ""])
"", "", "", "", "", "", ""])
# Namensspalte nur so breit wie der breiteste Name (Rest der Tabelle ist fest)
breiteste = max([stringWidth(b.nachname, "Helvetica", 8.5) for b in liste]
@ -278,11 +297,11 @@ def render_monat(pfad, firma, jahr: int, monat: int, buchungen):
# raum ein, da kannst locker zeile begruendung setzen"*. Also enger gestellt
# und der Begruendung so viel gegeben, dass "storniert mit 2026-901" in EINE
# Zeile passt - in normaler Schriftgroesse, nicht kleingedruckt.
name_w = min(max(breiteste + 6 * mm, 22 * mm), CONTENT_W - 142 * mm)
name_w = min(max(breiteste + 6 * mm, 22 * mm), CONTENT_W - 144 * mm)
tabelle = Table(daten, repeatRows=1, hAlign="LEFT",
colWidths=[19 * mm, 19 * mm, 44 * mm, name_w,
12 * mm, 19 * mm, 10 * mm, 19 * mm])
colWidths=[18 * mm, 18 * mm, 9 * mm, 42 * mm, name_w,
12 * mm, 18 * mm, 9 * mm, 18 * mm])
tabelle.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), GREY_BG),
("LINEBELOW", (0, 0), (-1, 0), 0.8, ACCENT),
@ -294,6 +313,14 @@ def render_monat(pfad, firma, jahr: int, monat: int, buchungen):
("RIGHTPADDING", (0, 0), (-1, -1), 3),
]))
story.append(tabelle)
# Legende zu den Buchstaben - direkt unter der Tabelle, damit sie beim Lesen
# da ist und nicht auf der naechsten Seite. Wer nur eine Zeile prueft, muss
# nicht raten, was fuer ein Beleg sie ist.
story.append(Spacer(1, 1.5 * mm))
story.append(Paragraph(
"<b>Art:</b> R = Rechnung · S = Stornorechnung · B = berichtigte Rechnung "
"(Neuausstellung nach einem Storno). Die Spalte Begründung nennt den Beleg, "
"bei dem es weitergeht.", st["klein"]))
_summenblock(story, st, s, satz_text)
story.append(Spacer(1, 3 * mm))

View file

@ -207,16 +207,20 @@ def bauen(pfad, cfg):
"<b>Begründung</b>, und zwar als <b>Kette, die vorwärts zeigt</b> – jede Zeile sagt, wo es "
"weitergeht:")
s.append(tabelle([
["Zeile im Bericht", "Begründung", "Bedeutung"],
["die aufgehobene Rechnung", "storniert mit 2026-901",
["Art", "Zeile im Bericht", "Begründung", "Bedeutung"],
["<b>R</b>", "die aufgehobene Rechnung", "storniert mit 2026-901",
"hier steht, welcher Beleg sie aufhebt"],
["die Stornorechnung", "neue Rechnung 2026-902",
["<b>S</b>", "die Stornorechnung", "neue Rechnung 2026-902",
"hier steht, unter welcher Nummer die Leistung jetzt abgerechnet ist"],
["die Stornorechnung im Doppelungsfall", "neu abgerechnet mit 2026-343",
["<b>S</b>", "Stornorechnung im Doppelungsfall", "abgerechnet mit 2026-343",
"die Leistung war bereits abgerechnet – das ist die Rechnung, die bleibt"],
["die neue Rechnung", "Neuausstellung",
["<b>B</b>", "die berichtigte Rechnung", "Neuausstellung",
"sie ersetzt die aufgehobene"],
], st, [45 * mm, 47 * mm, CONTENT_W - 92 * mm]))
], st, [9 * mm, 40 * mm, 40 * mm, CONTENT_W - 89 * mm]))
s.append(Spacer(1, 1 * mm))
P("Die Spalte <b>Art</b> markiert jeden Beleg mit einem Buchstaben – <b>R</b> Rechnung, "
"<b>S</b> Stornorechnung, <b>B</b> berichtigte Rechnung. Die Erklärung dazu steht auf jedem "
"Blatt unter der Tabelle.", "klein")
s.append(Spacer(1, 2 * mm))
P("So findet man von jedem Beleg aus den nächsten, auch wenn Rechnung und Storno in "
"verschiedenen Monaten liegen und gar nicht auf demselben Blatt stehen.")

View file

@ -155,7 +155,7 @@ class MonatsTab(ttk.Frame):
if b.storno_zu:
# Kette vorwaerts: Rechnung -> Storno -> die Rechnung, die gilt.
if b.folge_nummer and b.folge_art == "vorhanden":
art_text = f"neu abgerechnet mit {b.folge_nummer}"
art_text = f"abgerechnet mit {b.folge_nummer}"
elif b.folge_nummer:
art_text = f"neue Rechnung {b.folge_nummer}"
else:

View file

@ -157,9 +157,13 @@ pruefe("neue Rechnung 2026-902" in text,
pfad_sep = os.path.join(tmp, "September.pdf")
bericht_pdf.render_monat(pfad_sep, firma_lib.lade(), JAHR, 9, alle)
text_sep = " ".join((PdfReader(pfad_sep).pages[0].extract_text() or "").split())
pruefe("neu abgerechnet mit 2026-905" in text_sep,
pruefe("abgerechnet mit 2026-905" in text_sep,
"und beim Doppelungsfall zeigt er auf die Rechnung, die bleibt")
pruefe("-52,00" in text_sep, "der September traegt weiter seinen Minusbetrag")
# Die Buchstaben-Marke: ohne sie muss man aus der Begruendung raten, was fuer ein
# Beleg die Zeile ist ("du musst die markieren als rechnung und storno").
pruefe("R = Rechnung" in text and "S = Stornorechnung" in text,
"unter der Tabelle steht die Erklaerung der Buchstaben")
pruefe("Neuausstellung" in text, "die neue Rechnung ist als Neuausstellung beschriftet")
# ---------------------------------------------------------------------------