In PDF Javascript gespeicherte Feldoptionen verwenden

Die Kosten-pro-Tag-Berechnung und die Drop-Down-Optionen im Anwesenheitsfeld werden per Javsacript gemacht. JS funktioniert aber in pyumpdf nicht, darum werden die korrekten Optionen aus dem JS ausgelesen und dieses 1:1 in den Feldern gesetzt. Mittels Schalter in der config können auch wieder die Rohwerte aus dem Excel gesetzt werden. (PDF muss dann von Hand geöffnet und gespeichert werden damit die entgültigen passenden Werte enthalten sind).
This commit is contained in:
Rob committed 2026-04-16 17:04:11 +02:00
1 parent c51eddcb73
commit 2433478680
4 files changed
+77 -5

No files matched your search

+2 -1
View File
@@ -29,5 +29,6 @@
"pdf_field_ort": "unterschrift.ort", "pdf_field_ort": "unterschrift.ort",
"pdf_field_erstellungsdatum": "unterschrift.datum", "pdf_field_erstellungsdatum": "unterschrift.datum",
"pdf_pauschale_prefix": "pauschale_5", "pdf_pauschale_prefix": "pauschale_5",
"pauschale_present_value": "1" "pauschale_present_value": "1",
"pdf_use_display_values": true
} }
+5
View File
@@ -80,6 +80,11 @@
"pauschale_value": { "pauschale_value": {
"type": "string", "type": "string",
"description": "Wert für das pauschale.N Feld wenn anwesend" "description": "Wert für das pauschale.N Feld wenn anwesend"
},
"pdf_use_display_values": {
"type": "boolean",
"description": "true = Dropdown-Anzeigewerte ins PDF schreiben (z.B. 'X = anwesend'); false = rohe Export-Werte (z.B. 'X')",
"default": true
} }
} }
} }
+66
View File
@@ -91,6 +91,51 @@ def build_field_dict(
return fields return fields
def _load_opt_mappings(reader: PdfReader) -> Dict[str, Dict[str, str]]:
"""Return export→display maps for every choice field in the template.
For a field named ``anwesend.0`` with /Opt ``[["X", "X = anwesend"], ...]``
the result contains ``{"anwesend": {"X": "X = anwesend", ...}}``.
Multiple indexed fields (e.g. ``anwesend.0``…``anwesend.22``) share the
same base name and therefore the same mapping.
"""
opt_maps: Dict[str, Dict[str, str]] = {}
fields = reader.get_fields() or {}
for name, field in fields.items():
if str(field.get("/FT", "")) != "/Ch":
continue
base = name.split(".")[0]
if base in opt_maps:
continue # already recorded from an earlier indexed copy
raw_opts = field.get("/Opt", [])
mapping: Dict[str, str] = {}
for opt in raw_opts:
if isinstance(opt, list) and len(opt) == 2:
mapping[str(opt[0])] = str(opt[1])
else:
s = str(opt)
mapping[s] = s
if mapping:
opt_maps[base] = mapping
return opt_maps
def _apply_opt_mappings(
fields: Dict[str, str], opt_maps: Dict[str, Dict[str, str]]
) -> None:
"""Replace export values with display values in *fields* in-place.
Iterates over every field whose base name exists in *opt_maps* and
substitutes the stored export value for its display counterpart.
Fields whose value is not found in the map are left unchanged.
"""
for field_name, value in list(fields.items()):
base = field_name.split(".")[0]
mapping = opt_maps.get(base)
if mapping and value in mapping:
fields[field_name] = mapping[value]
def _update_widget_visibility(writer: PdfWriter, fields: Dict[str, str]) -> None: def _update_widget_visibility(writer: PdfWriter, fields: Dict[str, str]) -> None:
"""Un-hide widget annotations for ist.N and plan.N fields that have a value. """Un-hide widget annotations for ist.N and plan.N fields that have a value.
@@ -175,6 +220,27 @@ def fill_pdf(
f"PDF-Template konnte nicht gelesen werden: {exc}" f"PDF-Template konnte nicht gelesen werden: {exc}"
) from exc ) from exc
# Optionally replace export values with display values for choice fields.
# Controlled by the config key ``pdf_use_display_values`` (default: True).
# When True, pymupdf/viewers render e.g. "X = anwesend" instead of "X".
if config.get("pdf_use_display_values", True):
opt_maps = _load_opt_mappings(reader)
# Prefill every choice field that our data logic didn't touch so that
# its template-default export value also gets mapped to a display value.
# This covers e.g. pauschale_25.N (never set by us, always "0" in template)
# and anwesend.N / pauschale_5.N for rows beyond the data entries.
for field_name, field in (reader.get_fields() or {}).items():
if field_name in fields:
continue
if str(field.get("/FT", "")) != "/Ch":
continue
export_val = str(field.get("/V", ""))
if export_val:
fields[field_name] = export_val
_apply_opt_mappings(fields, opt_maps)
writer = PdfWriter() writer = PdfWriter()
writer.clone_document_from_reader(reader) writer.clone_document_from_reader(reader)
+4 -4
View File
@@ -15,12 +15,12 @@ A fixture is discovered automatically when:
Rendering Rendering
--------- ---------
Both reference and generated PDFs are rendered to images using pymupdf Both reference and generated PDFs are rendered to images using pymupdf.
(no browser required). The images are then compared pixel-by-pixel. The images are then compared pixel-by-pixel.
Similarity threshold Similarity threshold
-------------------- --------------------
SIMILARITY_THRESHOLD (default 95 %) — fraction of pixels whose R, G, B channels SIMILARITY_THRESHOLD (100%) — fraction of pixels whose R, G, B channels
each differ by at most PIXEL_TOLERANCE (default 5) between reference and generated PDF. each differ by at most PIXEL_TOLERANCE (default 5) between reference and generated PDF.
Diff images Diff images
@@ -47,7 +47,7 @@ from PIL import Image
# Constants # Constants
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
FIXTURES_DIR = Path(__file__).parent / "fixtures" FIXTURES_DIR = Path(__file__).parent / "fixtures"
SIMILARITY_THRESHOLD = 0.95 # minimum fraction of matching pixels per page SIMILARITY_THRESHOLD = 1.00 # minimum fraction of matching pixels per page
PIXEL_TOLERANCE = 5 # max per-channel delta still counted as "same" PIXEL_TOLERANCE = 5 # max per-channel delta still counted as "same"
RENDER_DPI = 150 # rendering resolution for PDF → image RENDER_DPI = 150 # rendering resolution for PDF → image