rechnungstool/pruef_bilder.py
TheMockTv 142dddc647 Bilder und Farbe gehoeren der Firma, dazu ein Vorschau-Knopf
Logo und ein Fussstreifen sind austauschbar (bilder.py: das gewaehlte Bild wird
KOPIERT, ein Pfad auf den Desktop waere morgen tot), die Akzentfarbe kommt aus
den Einstellungen - Hex-Feld und Farbwaehler. Firmenname, Anschrift, Bank und
Steuernummer bleiben gezeichnet; kein Bild kann eine Pflichtangabe verdecken.

Der Vorschau-Knopf zeigt das Blatt, bevor es eine Rechnung wird: kein
Nummernverbrauch, nichts im Rechnungsordner, "VORSCHAU - keine Rechnung" quer
ueber jeder Seite. Sonst kostet jeder Tippfehler einen Storno.

Neue Pruefstaende: pruef_bilder.py, pruef_vorschau.py

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
2026-09-05 17:35:37 +02:00

145 lines
5.8 KiB
Python

# -*- coding: utf-8 -*-
"""Pruefstand: Kopf- und Fussbild auf der Rechnung.
Drei Dinge muessen stimmen, sonst faellt es erst beim Drucken auf:
1. Ein gewaehltes Bild wird KOPIERT (Ordner "bilder"), nicht nur verlinkt -
sonst ist das Logo weg, sobald der Nutzer seinen Desktop aufraeumt.
2. Das Fussbild steht auf der Kundenrechnung, aber NICHT auf dem Amt-Blatt:
ein Briefpapier-Band traegt Bank und Steuernummer, und die haben auf dem
Blatt fuers Amt nichts zu suchen.
3. Der Haken "Angaben stehen im Bild" laesst die gezeichneten Texte weg -
und zwar nur dort, wo er gilt.
"""
import json
import os
import shutil
import sys
import tempfile
QUELLE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, QUELLE)
import config # noqa: E402
tmp = tempfile.mkdtemp(prefix="bilder_pruef_")
config.BASE_DIR = tmp
config.CONFIG_PATH = os.path.join(tmp, "config.json")
shutil.copy(os.path.join(QUELLE, "config.json"), config.CONFIG_PATH)
import bilder # noqa: E402
import pdf_renderer # noqa: E402
from berechnung import berechne # noqa: E402
fehler = []
def pruefe(bedingung, text):
print(("OK | " if bedingung else "FEHL | ") + text)
if not bedingung:
fehler.append(text)
def testbild(pfad, farbe):
"""Kleines PNG erzeugen - der Pruefstand braucht keine echten Kundenbilder."""
from PIL import Image
Image.new("RGB", (600, 120), farbe).save(pfad)
return pfad
# ---- 1. Uebernehmen: kopieren, nicht verlinken -----------------------------
fremd = os.path.join(tmp, "vom_desktop.png")
testbild(fremd, (10, 90, 60))
gespeichert = bilder.uebernehmen(fremd)
pruefe(gespeichert == "bilder/vom_desktop.png", f"Pfad wird kurz gemerkt: {gespeichert}")
pruefe(os.path.exists(os.path.join(tmp, "bilder", "vom_desktop.png")), "Bild liegt im Ordner bilder/")
# Das Original loeschen - die Rechnung muss trotzdem ein Bild haben.
os.remove(fremd)
pruefe(bool(bilder.aufloesen(gespeichert)), "Bild bleibt da, wenn das Original geloescht wird")
# Dasselbe Bild zweimal waehlen -> kein zweites Exemplar.
shutil.copy(os.path.join(tmp, "bilder", "vom_desktop.png"), fremd)
pruefe(bilder.uebernehmen(fremd) == "bilder/vom_desktop.png", "gleiches Bild zweimal -> kein Duplikat")
# Anderer Inhalt, gleicher Name -> das alte Bild wird NICHT ueberschrieben.
testbild(fremd, (200, 30, 30))
zweit = bilder.uebernehmen(fremd)
pruefe(zweit == "bilder/vom_desktop-2.png", f"gleicher Name, anderes Bild -> {zweit}")
# ---- 2. Rendern: Kunde vs. Amt --------------------------------------------
cfg = config.load_config()
cfg["firma"]["logo"] = bilder.uebernehmen(testbild(os.path.join(tmp, "kopf.png"), (20, 60, 120)))
cfg["firma"]["fussbild"] = bilder.uebernehmen(testbild(os.path.join(tmp, "fuss.png"), (120, 60, 20)))
cfg["firma"]["bank"] = "Bank Testbank"
mengen = {1: {"anzahl": 3, "personen": 0}}
r = berechne(cfg["katalog"], mengen, cfg)
kopf = {"art": "rechnung", "rechnungsnummer": "2026-999", "datum": "05.09.2026",
"kunde_zeilen": ["Herr", "Max Muster", "Musterweg 1", "04416 Markkleeberg"],
"anreise": "01.09.2026", "abreise": "04.09.2026", "naechte": 3}
pfad = os.path.join(tmp, "probe.pdf")
pdf_renderer.render_rechnung(pfad, cfg, r, kopf)
pruefe(os.path.exists(pfad), "PDF mit beiden Bildern entsteht")
def seiten_bilder_und_text(pdf):
"""Je Seite: (Anzahl eingebetteter Bilder, Text)."""
from pypdf import PdfReader
raus = []
for seite in PdfReader(pdf).pages:
xo = seite.get("/Resources", {}).get("/XObject", {})
try:
xo = xo.get_object()
except AttributeError:
pass
anzahl = sum(1 for k in xo if xo[k].get_object().get("/Subtype") == "/Image")
raus.append((anzahl, seite.extract_text() or ""))
return raus
seiten = seiten_bilder_und_text(pfad)
pruefe(len(seiten) >= 2, f"Kundenblatt + Amt-Blatt: {len(seiten)} Seiten")
kunde_bilder, kunde_text = seiten[0]
amt_bilder, amt_text = seiten[-1]
pruefe(kunde_bilder == 2, f"Kundenblatt traegt Kopf- UND Fussbild ({kunde_bilder})")
pruefe(amt_bilder == 1, f"Amt-Blatt traegt NUR das Kopfbild ({amt_bilder})")
pruefe("Testbank" in kunde_text, "Bankverbindung steht auf der Kundenrechnung")
pruefe("Testbank" not in amt_text, "Bankverbindung steht NICHT auf dem Amt-Blatt")
# ---- 3. Die Firmenfarbe faerbt das Blatt ----------------------------------
# Jede Firma faerbt ihre Rechnung selbst. Geprueft wird, dass die gewaehlte
# Farbe wirklich im PDF landet - und dass eine kaputte Eingabe das Schreiben
# NICHT verhindert, sondern still auf die Vorgabe zurueckfaellt.
cfg["design"] = {"akzent": "#8a1f1f"}
pfad2 = os.path.join(tmp, "probe_rot.pdf")
pdf_renderer.render_rechnung(pfad2, cfg, r, kopf)
pruefe(pdf_renderer.ACCENT.hexval().endswith("8a1f1f"),
f"gewaehlte Farbe steht im Blatt: {pdf_renderer.ACCENT.hexval()}")
pruefe(pdf_renderer.GREY_BG.red > 0.9,
"helle Flaeche wird aus der Farbe gemischt (bleibt hell)")
cfg["design"] = {"akzent": "gruen bitte"}
pfad3 = os.path.join(tmp, "probe_kaputt.pdf")
pdf_renderer.render_rechnung(pfad3, cfg, r, kopf)
pruefe(os.path.exists(pfad3), "kaputte Farbe verhindert die Rechnung NICHT")
pruefe(pdf_renderer.ACCENT.hexval().endswith("1f5c3d"), "kaputte Farbe faellt auf die Vorgabe zurueck")
cfg["design"] = {"akzent": pdf_renderer.AKZENT_VORGABE}
# Ohne Bilder muss das Blatt aussehen wie immer.
cfg["firma"]["logo"] = ""
cfg["firma"]["fussbild"] = ""
pfad4 = os.path.join(tmp, "probe_ohne_bild.pdf")
pdf_renderer.render_rechnung(pfad4, cfg, r, kopf)
seiten3 = seiten_bilder_und_text(pfad4)
pruefe(seiten3[0][0] == 0, "ohne Bilder: keine Bilder im PDF")
pruefe(cfg["firma"]["name"] in seiten3[0][1], "ohne Bild steht der Firmenname gezeichnet oben")
print()
print(f"Ordner: {tmp}")
if fehler:
print(f"FEHLGESCHLAGEN: {len(fehler)}")
for f in fehler:
print(" -", f)
sys.exit(1)
print("ALLES GRUEN")