Amtsbericht: die Kette zeigt vorwaerts - Storno nennt die neue Rechnung
Sein Fund: "an der storno verweist du auf die alte rechnung, muss aber
lauten neu zu und neue rechnungs nummer. heisst kette ist so: rechnung
storniert weist auf die storno nummer, storno verweist auf neue rechnung.
wer soll denn sonst wissen wo er suchen muss."
Er hat recht: eine Zeile, die nur zurueckzeigt, laesst den Leser stehen.
Jetzt sagt jede Zeile, wo es WEITERGEHT:
Rechnung -> "storniert mit 2026-901"
Storno -> "neue Rechnung 2026-902" (Fall A)
-> "neu abgerechnet mit 2026-343" (Fall B, Doppelung)
-> "Storno zu 2026-900" (Fall C / alte Belege ohne Angabe)
neue R. -> "Neuausstellung"
Dafuer wandert die Folgenummer aus den PDF-Kenndaten bis ins Journal:
Buchung.folge_nummer + folge_art (neu | vorhanden | ""), gelesen aus
korrektur_nummer/ersatz_vorhanden/nur_storno, zwei neue DB-Spalten, alte
Journale bauen sich wie gehabt selbst um.
An seinen ECHTEN Belegen geprueft: Storno 2026-455 -> folge 2026-369
(vorhanden), 2026-456 -> 2026-343 (vorhanden). Die Angabe steht in den
Kenndaten schon drin, sie wurde bisher nur nicht gelesen.
Fenster und Blatt zeigen dieselben Worte. Erklaerungs-PDF um eine
Kettentabelle ergaenzt. Pruefkette gruen (storno_verrechnet inkl.
September-Blatt, doppelte_leistung, nur_storno).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017bEsoFUk16DfnNA7MjY36H
This commit is contained in:
parent
b4db3b3849
commit
b2d933cb7c
7 changed files with 69 additions and 13 deletions
|
|
@ -207,6 +207,17 @@ def _art_text(b, weg) -> str:
|
|||
setzen"*. Dieselben Worte wie in der Spalte "Art" im Fenster.
|
||||
"""
|
||||
if b.storno_zu:
|
||||
# Die Kette zeigt VORWAERTS: Rechnung -> Storno -> Rechnung, die nun gilt.
|
||||
# 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}"
|
||||
if b.folge_nummer:
|
||||
return f"neue Rechnung {b.folge_nummer}"
|
||||
if b.folge_art == "" and b.art == "storno" and not b.folge_nummer:
|
||||
# Entweder "nur Storno" oder ein alter Beleg ohne die Angabe. Dann
|
||||
# bleibt der Rueckbezug - besser als gar kein Hinweis.
|
||||
return f"Storno zu {b.storno_zu}"
|
||||
return f"Storno zu {b.storno_zu}"
|
||||
if b.id in weg:
|
||||
# Nicht nur "storniert": auch WOMIT. Sonst steht auf dem Blatt ein
|
||||
|
|
|
|||
17
db.py
17
db.py
|
|
@ -36,6 +36,8 @@ CREATE TABLE IF NOT EXISTS buchungen (
|
|||
storno_zu TEXT NOT NULL DEFAULT '',
|
||||
storno_datum TEXT NOT NULL DEFAULT '',
|
||||
vorgang TEXT NOT NULL DEFAULT '',
|
||||
folge_nummer TEXT NOT NULL DEFAULT '',
|
||||
folge_art TEXT NOT NULL DEFAULT '',
|
||||
pdf_pfad TEXT NOT NULL DEFAULT '',
|
||||
pdf_mtime REAL NOT NULL DEFAULT 0,
|
||||
pdf_groesse INTEGER NOT NULL DEFAULT 0
|
||||
|
|
@ -127,7 +129,8 @@ class Journal:
|
|||
"""
|
||||
da = {z["name"] for z in self.con.execute("PRAGMA table_info('buchungen')")}
|
||||
for name, vorgabe in (("art", "'rechnung'"), ("storno_zu", "''"),
|
||||
("storno_datum", "''"), ("vorgang", "''")):
|
||||
("storno_datum", "''"), ("vorgang", "''"),
|
||||
("folge_nummer", "''"), ("folge_art", "''")):
|
||||
if name not in da:
|
||||
self.con.execute(
|
||||
f"ALTER TABLE buchungen ADD COLUMN {name} TEXT NOT NULL DEFAULT {vorgabe}")
|
||||
|
|
@ -180,6 +183,8 @@ class Journal:
|
|||
storno_zu=(r["storno_zu"] if "storno_zu" in r.keys() else ""),
|
||||
storno_datum=(r["storno_datum"] if "storno_datum" in r.keys() else ""),
|
||||
vorgang=(r["vorgang"] if "vorgang" in r.keys() else ""),
|
||||
folge_nummer=(r["folge_nummer"] if "folge_nummer" in r.keys() else ""),
|
||||
folge_art=(r["folge_art"] if "folge_art" in r.keys() else ""),
|
||||
)
|
||||
|
||||
def _pdf_bekannt(self, pfad: str) -> bool:
|
||||
|
|
@ -204,8 +209,8 @@ class Journal:
|
|||
"INSERT INTO buchungen "
|
||||
" (jahr, rechnungsnummer, datum, nachname, naechte, gezahlt, satz, "
|
||||
" quelle, pdf_pfad, pdf_mtime, pdf_groesse, art, storno_zu, "
|
||||
" storno_datum, vorgang) "
|
||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) "
|
||||
" storno_datum, vorgang, folge_nummer, folge_art) "
|
||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) "
|
||||
"ON CONFLICT(pdf_pfad) WHERE pdf_pfad <> '' DO UPDATE SET "
|
||||
" jahr=excluded.jahr, rechnungsnummer=excluded.rechnungsnummer, "
|
||||
" datum=excluded.datum, nachname=excluded.nachname, naechte=excluded.naechte, "
|
||||
|
|
@ -213,10 +218,12 @@ class Journal:
|
|||
" pdf_pfad=excluded.pdf_pfad, pdf_mtime=excluded.pdf_mtime, "
|
||||
" pdf_groesse=excluded.pdf_groesse, art=excluded.art, "
|
||||
" storno_zu=excluded.storno_zu, storno_datum=excluded.storno_datum, "
|
||||
" vorgang=excluded.vorgang",
|
||||
" vorgang=excluded.vorgang, folge_nummer=excluded.folge_nummer, "
|
||||
" folge_art=excluded.folge_art",
|
||||
(b.jahr, b.rechnungsnummer, b.datum.isoformat(), b.nachname, int(b.naechte or 0),
|
||||
float(b.gezahlt or 0), float(b.satz or 0), b.quelle, b.pdf_pfad, mtime, groesse,
|
||||
b.art or "rechnung", b.storno_zu or "", b.storno_datum or "", b.vorgang or ""))
|
||||
b.art or "rechnung", b.storno_zu or "", b.storno_datum or "", b.vorgang or "",
|
||||
b.folge_nummer or "", b.folge_art or ""))
|
||||
self.con.commit()
|
||||
return cur.lastrowid
|
||||
|
||||
|
|
|
|||
|
|
@ -204,8 +204,21 @@ def bauen(pfad, cfg):
|
|||
"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 "
|
||||
"<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",
|
||||
"hier steht, welcher Beleg sie aufhebt"],
|
||||
["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",
|
||||
"die Leistung war bereits abgerechnet – das ist die Rechnung, die bleibt"],
|
||||
["die neue Rechnung", "Neuausstellung",
|
||||
"sie ersetzt die aufgehobene"],
|
||||
], st, [45 * mm, 47 * mm, CONTENT_W - 92 * mm]))
|
||||
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.")
|
||||
|
||||
P("Die Rechtsgrundlagen im Einzelnen", "h")
|
||||
|
|
|
|||
|
|
@ -153,7 +153,13 @@ class MonatsTab(ttk.Frame):
|
|||
else:
|
||||
marke = (b.quelle,)
|
||||
if b.storno_zu:
|
||||
art_text = f"Storno zu {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}"
|
||||
elif b.folge_nummer:
|
||||
art_text = f"neue Rechnung {b.folge_nummer}"
|
||||
else:
|
||||
art_text = f"Storno zu {b.storno_zu}"
|
||||
elif b.id in aufgehoben:
|
||||
art_text = f"storniert mit {aufgehoben[b.id].voll_nummer}"
|
||||
elif b.art == "korrektur":
|
||||
|
|
|
|||
|
|
@ -57,6 +57,11 @@ class Buchung:
|
|||
storno_zu: str = "" # Nummer der aufgehobenen Rechnung
|
||||
storno_datum: str = "" # deren Datum (TT.MM.JJJJ) - Teil des Bezugs
|
||||
vorgang: str = "" # Klammer um Rechnung, Storno und Neuausstellung
|
||||
# Worauf der Storno VORWAERTS zeigt. Ohne das endet die Kette am Storno und
|
||||
# niemand weiss, wo die Leistung nun abgerechnet ist (seine Ansage 04.09.2026:
|
||||
# "wer soll denn sonst wissen, wo er suchen muss").
|
||||
folge_nummer: str = "" # Nummer der Rechnung, die danach gilt
|
||||
folge_art: str = "" # neu | vorhanden | "" (es folgt nichts)
|
||||
|
||||
@property
|
||||
def steuer(self) -> float:
|
||||
|
|
|
|||
|
|
@ -239,6 +239,10 @@ def parse_pdf(pfad: str, standard_satz: float = STANDARD_SATZ) -> Buchung:
|
|||
storno_zu=str(meta.get("storno_zu") or ""),
|
||||
storno_datum=str(meta.get("storno_datum") or ""),
|
||||
vorgang=str(meta.get("vorgang") or ""),
|
||||
folge_nummer=str(meta.get("korrektur_nummer") or ""),
|
||||
folge_art=("" if meta.get("nur_storno")
|
||||
else "vorhanden" if meta.get("ersatz_vorhanden")
|
||||
else "neu" if meta.get("korrektur_nummer") else ""),
|
||||
)
|
||||
log.debug("aus Metadaten: %s | %s | %s | %s Nächte | %.2f €",
|
||||
os.path.basename(pfad), b.voll_nummer, b.nachname, b.naechte, b.gezahlt)
|
||||
|
|
|
|||
|
|
@ -73,12 +73,14 @@ JAHR = 2026
|
|||
FAKE = [
|
||||
Buchung(date(JAHR, 8, 22), "900", JAHR, "Muster", 4, 52.0, quelle="manuell"),
|
||||
Buchung(date(JAHR, 8, 23), "901", JAHR, "Muster", -4, -52.0, quelle="manuell",
|
||||
art="storno", storno_zu="2026-900", storno_datum="22.08.2026"),
|
||||
art="storno", storno_zu="2026-900", storno_datum="22.08.2026",
|
||||
folge_nummer="2026-902", folge_art="neu"),
|
||||
Buchung(date(JAHR, 8, 23), "902", JAHR, "Muster", 3, 39.0, quelle="manuell",
|
||||
art="korrektur"),
|
||||
Buchung(date(JAHR, 8, 22), "910", JAHR, "Spaet", 4, 52.0, quelle="manuell"),
|
||||
Buchung(date(JAHR, 9, 2), "911", JAHR, "Spaet", -4, -52.0, quelle="manuell",
|
||||
art="storno", storno_zu="2026-910", storno_datum="22.08.2026"),
|
||||
art="storno", storno_zu="2026-910", storno_datum="22.08.2026",
|
||||
folge_nummer="2026-905", folge_art="vorhanden"),
|
||||
Buchung(date(JAHR, 8, 5), "905", JAHR, "Sauber", 2, 26.0, quelle="manuell"),
|
||||
]
|
||||
|
||||
|
|
@ -149,7 +151,15 @@ pruefe("storniert mit 2026-901" in text,
|
|||
"die aufgehobene Rechnung nennt den Storno, der sie aufhebt")
|
||||
pruefe("storniert mit 2026-911" in text,
|
||||
"auch dann, wenn dieser Storno in einem ANDEREN Monat liegt")
|
||||
pruefe("Storno zu 2026-900" in text, "und der Storno nennt die Rechnung, die er aufhebt")
|
||||
pruefe("neue Rechnung 2026-902" in text,
|
||||
"der Storno zeigt VORWAERTS auf die Rechnung, die nun gilt")
|
||||
# Der zweite Storno steht im SEPTEMBER - also dort nachsehen, nicht im August.
|
||||
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,
|
||||
"und beim Doppelungsfall zeigt er auf die Rechnung, die bleibt")
|
||||
pruefe("-52,00" in text_sep, "der September traegt weiter seinen Minusbetrag")
|
||||
pruefe("Neuausstellung" in text, "die neue Rechnung ist als Neuausstellung beschriftet")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -164,8 +174,8 @@ pruefe(len(reihen) == 5, f"der August zeigt alle fuenf Zeilen ({len(reihen)})")
|
|||
pruefe(not any("aufgehoben" in t for _nr, _a, t in reihen),
|
||||
f"keine Zeile ist ausgegraut ({sorted({t for _n, _a, t in reihen})})")
|
||||
arten = {nr: art for nr, art, _t in reihen}
|
||||
pruefe(arten.get("2026-901") == "Storno zu 2026-900",
|
||||
f"der Storno ist als Storno getaggt ({arten.get('2026-901')!r})")
|
||||
pruefe(arten.get("2026-901") == "neue Rechnung 2026-902",
|
||||
f"der Storno zeigt auf die neue Rechnung ({arten.get('2026-901')!r})")
|
||||
pruefe(arten.get("2026-900") == "storniert mit 2026-901",
|
||||
f"die aufgehobene Rechnung nennt ihren Storno ({arten.get('2026-900')!r})")
|
||||
pruefe(arten.get("2026-902") == "Neuausstellung",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue