Skip to content

Evaluierung: JasperReports-Strategie für calServer V2 — Feldnamen, Datenanbindung und Template-Migration

Kontext

calServer V2 hat eine Grundsatzfrage zu klären: Wie kommen die JasperReports-Berichte an ihre Daten, wenn das V2-Schema keine Metrologie-Codespalten mehr hat?

Die bestehenden Jasper-Templates (Community-Repo calhelp/calServer-reports sowie ~250 Templates unter httpdocs/reports/) führen eingebettetes SQL direkt gegen die V1-MySQL-Datenbank aus und referenzieren dabei die alten Codespalten (I4201, C2303, K4601, …). Das V2-Schema verwendet dagegen — per ADR-008 und Evaluierung DB-Migration entschieden und bereits umgesetzt — lesbare Spaltennamen (asset_number, next_calibration_date) und konsolidiert Überlauf-Felder in einer custom_fields-JSON-Spalte.

Zur Diskussion stehen drei Optionen:

  • Option A: Felder durchgängig im alten, bewährten Code-Format (I42xx, C23xx, …) weiterpflegen, damit die Berichte unverändert laufen.
  • Option B: Konsequent auf die neuen Felder setzen und die Berichte auf die API-Felder lenken.
  • Option C: Alle Berichte für V2 anpassen (Template-SQL auf das neue Schema umschreiben).

Diese Evaluierung bewertet alle drei Optionen und leitet daraus eine langfristig tragfähige Empfehlung ab. Die formale Entscheidung ist in ADR-009 (Report-Data-Contract statt SQL-in-Templates) festgehalten.

Randbedingungen, die jede Lösung erfüllen muss:

  1. Dual-Track-Realität: Kundensysteme laufen in Produktion ausschließlich V1 (Yii + MySQL) — und das noch auf Jahre. Die V1-Berichtswelt muss stabil weiterlaufen.
  2. Multi-DB-Gebot: V2 unterstützt MySQL, PostgreSQL und MSSQL gleichberechtigt (siehe CLAUDE.md, DATABASE_ABSTRACTION_PLAN.md). Jede Berichtsanbindung muss DB-agnostisch funktionieren.
  3. DAkkS-Relevanz: Kalibrierscheine sind akkreditierungsrelevante Dokumente. Layout-Änderungen erzeugen Verifikationsaufwand beim Kunden — die Migration muss layouterhaltend validierbar sein.
  4. Community-Autoren: Kunden und Partner erstellen eigene Templates in Jaspersoft Studio. Der Autoren-Workflow muss dokumentiert und zumutbar bleiben.

Ist-Analyse

Template-Bestand (calServer-reports)

  • 23 JRXML-Templates in 10 Bundles (DAKKS-SAMPLE, DCC, INVENTORY-SAMPLE, ORDER-SAMPLE, DELIVERY-STANDALONE, KALIBRIERKONTROLLBLATT, TRACE-FORWARD, TRACE-BACKWARD, STICKERS, FIELD-NAMES), gepinnt auf JasperReports 6.20.6 (scripts/check_jasper_version.sh).
  • Alle Templates beziehen ihre Daten über eingebettetes SQL (<queryString language="SQL">), das zur Laufzeit über die JDBC-Connection $P{REPORT_CONNECTION} ausgeführt wird. Es gibt keine JSON-, XML- oder Bean-Datasources.
  • ~2.000 Referenzen auf V1-Codespalten in 20 von 23 Dateien: C-Codes ≈ 849, I-Codes ≈ 625, K-Codes ≈ 329, L-Codes ≈ 204, W-Codes ≈ 52. Auch Business-Logik hängt an Codes (z. B. "DAkkS-K-" + $F{I4201}, Joins über s1.C2430).
  • Subreports erhalten die Live-Connection (<connectionExpression>$P{REPORT_CONNECTION}</connectionExpression>) und führen eigenes SQL aus, gekeyt über die Parameter P_CTAG/P_MTAG; Mandanten-/Präfix-Trennung per Literalersetzung $P!{PrefixTable}.
  • Nur drei Dateien sind codefrei: die Results-Subreports von DAKKS und DCC (lesen die bereits lesbar benannte results-Tabelle) und der FIELD-NAMES-Report (rendert field_configuration + messages als Code→Label-Übersicht pro Sprache).

Ausführungspfad (report-runner + V1 + V2)

  • report-runner/ (Java 21, Javalin, JasperReports 6.20.6 — JR 7 kann die alten JRXML-Schemata nicht parsen): nimmt Jobs per POST /api/v1/reports/generate entgegen. Jeder Job trägt seine eigenen JDBC-Zugangsdaten (jdbcUrl, dbDriver, dbUser, dbPassword); ReportProcessor.fillAndExport() öffnet die Connection und lässt das Template-SQL selbst laufen. Treiber für MySQL, PostgreSQL und MSSQL sind gebündelt.
  • V1 (Yii): KPJasper + ReportEngineManager schalten per Feature-Flag zwischen Legacy-Engine (JasperStarter-CLI) und Runner um; eine Dual-Run-Vergleichsinfrastruktur (beide Engines rendern, Ergebnis wird verglichen, Log in report_run_log) existiert bereits.
  • V2 (Laravel): Der Reportpfad existiert bereits (app/Services/ReportService.php, Report-Controller für Calibration/Inventory/Location/Repair/Booking, ReportParameterBuilder). Er reicht aber nur Entity-IDs (CTAG, InventoryId, …) und PrefixTable durch — das Template-SQL erwartet weiterhin V1-Codespalten in der Datenbank, auf die der Runner zeigt. Gegen eine V2-Datenbank mit lesbarem Schema laufen die bestehenden Templates ins Leere.
  • Kontrastbeispiel: Der DCC-XML-Generator (app/Services/Report/DccXmlGeneratorService.php) liest seine Daten bereits über Eloquent/lesbare Namen und zeigt damit das Zielmuster.

Feldnamen-Landschaft in V2

  • Die Haupttabellen sind bereits auf lesbare snake_case-Spalten migriert (asset_number statt I4201, next_calibration_date statt C2303); Überlauf-/Custom-Felder liegen konsolidiert in einer custom_fields-JSON-Spalte.
  • Die Codes existieren in V2 nur noch als Registry: api_field_mappings (Code→api_name), field_definitions (mit v1_name-Alias), field_configuration (mit V1 geteilt) sowie als JSON-Keys synchronisierter Felder in custom_fields.
  • Rohes SQL sieht das Mapping nicht: Die Übersetzung Code↔lesbarer Name lebt an der Eloquent-/API-Grenze (HasDynamicFields, FieldDefinitionService). Der Zugriff auf custom_fields-Inhalte ist zudem dialektspezifisch (->> in PostgreSQL, JSON_EXTRACT in MySQL, JSON_VALUE in MSSQL) — für portable Template-SQLs praktisch unerreichbar.

Optionen

Option A: V1-Codespalten in V2 weiterpflegen

Das V2-Schema würde die Codespalten (wieder) führen — entweder physisch oder als dauerhaft gepflegte Alias-Schicht —, damit die bestehenden Templates unverändert laufen.

Pro Contra
Alle bestehenden Templates (23 Community + ~250 interne + Kunden-Templates) laufen unverändert Revidiert die bereits abgeschlossene V2-Schema-Migration und ADR-008
Kein Umlernaufwand für Report-Autoren API, Frontend, Feldverwaltung und Doku sprechen bereits durchgängig lesbare Namen — es entstünden dauerhaft zwei Namenswelten
V1→V2-Umzug von Report-Paketen trivial Die custom_fields-Konsolidierung müsste rückgebaut werden (zurück zu 25+ starren Spalten pro Tabelle)
Neue V2-Felder bräuchten neu erfundene Kunstcodes — das Codesystem skaliert nicht und ist für neue Entwickler/Kunden unlesbar
Der Wartbarkeitsgewinn der V2-Migration (Hauptmotiv von ADR-008) ginge dauerhaft verloren

Bewertung: nicht tragfähig. „Bewährt" ist an den Codes vor allem die Gewohnheit langjähriger Autoren — nicht die Wartbarkeit. Diese Gewohnheit lässt sich deutlich billiger bedienen (Mapping-Doku, FIELD-NAMES-Report, v1_name-Aliase in field_definitions) als durch die Konservierung des gesamten Codesystems im neuen Schema. Option A hieße faktisch: die V2-Datenbankstrategie für die Berichte rückabwickeln.

Option B: Konsequent neue Felder — Berichte auf API-Felder lenken

Die lesbaren api_name-Felder sind der einzige Feld-Contract. Berichte beziehen ihre Daten nicht mehr per eigenem SQL, sondern aus vom Backend bereitgestellten, berichtsförmigen Datensätzen (JSON), die exakt die API-Feldnamen tragen — inklusive Custom Fields.

Pro Contra
Eine Namenswelt: Template-Felder = API-Felder = Doku = Feldverwaltung Alle Templates müssen einmalig auf JSON-Datasource umgestellt werden (Rewrite der <queryString>- und <field>-Schicht)
DB-agnostisch per Konstruktion — erfüllt das Multi-DB-Gebot ohne drei SQL-Dialekte Backend muss Report-Daten-Endpoints je Report-Familie bereitstellen (bounded: ~8 Familien)
Custom Fields stehen unter ihren api_names bereit — per SQL wären sie nicht portabel erreichbar Rekursive Abfragen (TRACE-FORWARD/-BACKWARD) müssen serverseitig berechnet werden (per Repository-Pattern je Treiber — nach Abstraktionsregel 4 ohnehin der vorgesehene Weg)
Sicherheit/Mandanten: keine DB-Credentials mehr im Report-Job; Berechtigungen und Tenant-Scoping greifen in der API Autoren-Workflow ändert sich: Jaspersoft Studio mit JSON-Beispieldatei statt DB-Connection (Schulung/Doku nötig)
Stabiler, versionierbarer Contract — Schema-Evolution bricht Templates nicht mehr Sehr große Massenberichte brauchen ggf. Streaming/serverseitige Aggregation statt „ein SELECT"
Berichte werden reine Präsentationsschicht (Muster: DCC-Generator liest bereits via Eloquent)

Bewertung: strategisch richtig. Option B ist die einzige Option, die alle vier Randbedingungen gleichzeitig erfüllt und mit den verbindlichen Database-Abstraction-Regeln (insbesondere Regel 6: „API-Responses sind das einzige Interface") konsistent ist. Der Umstellungsaufwand für Templates fällt in jeder Option außer A an — nur liefert er hier einen dauerhaft stabilen Contract statt einer erneuten Schema-Kopplung.

Option C: Alle Templates auf V2-SQL umschreiben

Die Templates behalten ihr eingebettetes SQL, fragen aber das neue V2-Schema mit lesbaren Spaltennamen ab.

Pro Contra
Kleinster konzeptioneller Eingriff: Runner, Connection- und Subreport-Mechanik bleiben unverändert Verletzt das Multi-DB-Gebot: ein Template kann nicht portabel MySQL, PostgreSQL und MSSQL bedienen (Datums-/Stringfunktionen, JSON-Zugriff auf custom_fields sind dialektspezifisch) → drei Template-Varianten je Bericht oder kleinster gemeinsamer Nenner
Vertrauter SQL-Workflow für Autoren bleibt erhalten Custom Fields (custom_fields-JSON) sind per portablem SQL praktisch nicht erreichbar — gerade für Sticker/Etiketten mit Kundenfeldern kritisch
Templates werden durch lesbare Spaltennamen verständlicher Per-Job-DB-Credentials und SQL-Ausführung aus Templates bleiben als Sicherheits- und Mandanten-Schwäche bestehen
Kein stabiler Contract: jede Schema-Evolution bricht Templates erneut — dieselbe Kopplung wie heute, nur an ein neues Schema

Bewertung: kurzfristig plausibel, langfristig eine Sackgasse. Option C tauscht die Kopplung an das V1-Schema gegen eine Kopplung an das V2-Schema und multipliziert den Pflegeaufwand mit der Anzahl der DB-Backends. Sie ist als Dauerstrategie nicht mit dem Multi-DB-Versprechen vereinbar.

Brückenoption: Kompatibilitäts-Views (zeitlich begrenzt)

Für einzelne, schwer migrierbare Kunden-Templates kann übergangsweise eine View-Schicht die lesbaren V2-Spalten unter den alten Codenamen bereitstellen (CREATE VIEW ... SELECT asset_number AS I4201 ...).

  • Geeignet als: individuelle, zeitlich begrenzte Migrationshilfe pro Kunde/Template während des V1→V2-Umzugs.
  • Nicht geeignet als: Strategie. Views müssten je Treiber gepflegt werden, Custom Fields ließen sich nicht statisch abbilden (kundenspezifisch dynamisch), und das Codesystem würde durch die Hintertür konserviert.

Alternative Engines (der Vollständigkeit halber)

Ein Wechsel weg von JasperReports (HTML/CSS-zu-PDF, BIRT, o. ä.) wurde geprüft und verworfen: Der Bestand an validierten Layouts (DAkkS-Scheine, Zebra-Etiketten), die Community-Basis und der etablierte Jaspersoft-Studio-Workflow wiegen schwerer als die Modernisierungsvorteile. Die Modernisierung findet innerhalb von Jasper statt — über den Datasource-Typ, nicht über die Engine. JasperReports 6.20.6 unterstützt JSON-Datasources vollständig.


Empfehlung

Option B, konkretisiert als versionierter Report-Data-Contract.

Die Grundsatzentscheidung lautet: Lesbare api_name-Felder sind der einzige Feld-Contract von calServer V2 — auch für Berichte. Die alten Codes werden nicht weitergepflegt, sondern nur noch als Migrations-Aliase (v1_name, api_field_mappings, FIELD-NAMES-Report) mitgeführt. Die Berichte werden nicht auf das V2-SQL-Schema, sondern auf berichtsförmige JSON-Datensätze aus dem Backend gelenkt.

Formal festgehalten in ADR-009: Report-Data-Contract statt SQL-in-Templates.


Zielarchitektur

V1 (unverändert, eingefroren):
  Yii → report-runner → JDBC (MySQL) → Template-SQL mit I42xx/C23xx

V2 (Zielbild):
  Laravel Report-Data-Builder (Eloquent + FieldDefinitionService)
      │  baut berichtsförmiges JSON-Dataset (api_name-Felder, inkl. custom_fields)
  report-runner  ──  JsonDataSource-Fill-Pfad (neu, neben JDBC)
      │  Template referenziert $F{serial_number} statt $F{I4202}
  PDF / DOCX / XLSX

Bausteine:

  1. Report-Data-Contract (Backend): Pro Report-Familie (Kalibrierschein, Inventarblatt, Auftrag/Lieferschein, Sticker, Trace, …) definiert Laravel ein versioniertes JSON-Dataset (schema_version). Aufgebaut über Eloquent + FieldDefinitionService — dem Muster des DCC-Generators folgend. Rekursionen (Trace) und Aggregationen werden serverseitig berechnet (Repository-Pattern je Treiber, Abstraktionsregel 4).
  2. Runner-Erweiterung: Der JobDescriptor erhält einen Datasource-Typ (json | jdbc). Bei json füllt der Runner das Template über JsonDataSource aus den mitgelieferten Daten; die JDBC-Felder entfallen für diese Jobs. Der JDBC-Pfad bleibt unverändert für V1 erhalten (siehe Anhang C).
  3. Template-Migration bundle-weise: Neue V2-Bundles (JSON-Datasource, api_name-Felder) entstehen neben den eingefrorenen V1-Bundles. Subreports erhalten statt der Connection einen Sub-Datasource (((JsonDataSource)$P{REPORT_DATA}).subDataSource("results")).
  4. Layout-Validierung: Die vorhandene Dual-Run-/Vergleichsinfrastruktur (V1: ReportEngineManager, report_run_log) wird wiederverwendet, um V1- und V2-Rendering desselben Vorgangs gegenüberzustellen — zentral für die DAkkS-Abnahme.
  5. Autoren-Unterstützung: Beispiel-JSON je Contract zum Download (für Jaspersoft Studio als JSON-Data-Adapter), Mapping-Referenz Code→api_name (Anhang A, api_field_mappings), FIELD-NAMES-Äquivalent auf Basis field_definitions.

Migrations-Phasenplan

Phase 0 — Contract-Definition + Pilot (DAkkS-Zertifikat): Report-Data-Contract für die Familie „Kalibrierschein" definieren (Anhang B als Ausgangspunkt); Laravel-Endpoint baut das Dataset; Runner erhält den JSON-Fill-Pfad; DAKKS-SAMPLE als JSON-Variante nachgebaut. Exit-Kriterium: DAkkS-Muster rendert aus JSON pixelgleich (Dual-Run-Vergleich) zum V1-Original.

Phase 1 — Kern-Bundles: INVENTORY, STICKERS (Custom-Fields-Nachweis!), KALIBRIERKONTROLLBLATT, DCC auf JSON-Contract migrieren; Beispiel-JSONs veröffentlichen; Autoren-Doku (Studio mit JSON-Adapter). Exit-Kriterium: Alle Kern-Bundles als V2-Variante verfügbar; Community-Doku online.

Phase 2 — Rest-Bundles + serverseitige Rekursion: ORDER, DELIVERY, TRACE-FORWARD/-BACKWARD (Trace-Berechnung serverseitig); FIELD-NAMES-Nachfolger auf field_definitions-Basis. Exit-Kriterium: Jedes V1-Bundle hat eine V2-Entsprechung oder einen dokumentierten Nachfolger.

Phase 3 — Kunden-Templates: Migrationsleitfaden + Mapping-Werkzeuge für individuelle Kundentemplates; Kompat-Views nur als dokumentierte, zeitlich begrenzte Ausnahme je Kunde. Exit-Kriterium: Migrationspfad je Kundentemplate-Typ beschrieben und mindestens einmal real durchlaufen.

Phase 4 — V1-Bundles archivieren: V1-Bundles im Reports-Repo als eingefroren markieren (nur noch Fehlerkorrekturen); Neuentwicklung ausschließlich als V2-Bundle. Exit-Kriterium: robots.md/README des Reports-Repos weisen V2 als Standard aus.


Risiken und Gegenmaßnahmen

Risiko Gegenmaßnahme
DAkkS-Re-Verifikation: Zertifikatslayouts gelten als validiert; jede Abweichung erzeugt Abnahmeaufwand Layout unangetastet lassen, nur die Datenbindung tauschen; Dual-Run-/Sichtvergleich V1↔V2 als Abnahme-Artefakt je Template
Performance großer Datasets (Massen-Inventarlisten) Serverseitige Aggregation im Contract; Streaming/Chunking im Builder; Grenzwerte messen statt raten (Pilotphase)
Community-/Kunden-Autoren verlieren den gewohnten SQL-Workflow Beispiel-JSONs je Contract, Schritt-für-Schritt-Doku für Jaspersoft Studio, Mapping-Referenz Code→api_name, Übergangszeit mit beiden Welten
Subreport-Datenweitergabe im JSON-Modus (andere Mechanik als Connection-Passing) Muster einmalig im Pilot etablieren (subDataSource()), als Vorlage/Snippet dokumentieren
JR-6.20.6-Verhalten mit JSON-Datasources (Altversion) Pilotphase verifiziert die konkreten Features; Version bleibt bis zum expliziten Policy-Wechsel gepinnt; neue V2-Templates vermeiden 7.x-Features
Doppelpflege während der Übergangszeit (V1- und V2-Bundles) V1-Bundles einfrieren (nur Bugfixes); klare Repo-Struktur und robots.md-Regeln

Auswirkungen auf die Repositories

calServer-yii:

  • laravel/: Report-Data-Builder je Report-Familie (Namespace App\Services\Report\), Endpoints/Erweiterung der bestehenden Report-Controller; ReportService sendet Datasets statt JDBC-Credentials (für V2-Bundles).
  • report-runner/: JobDescriptor um Datasource-Typ + Datenfeld erweitern; JSON-Fill-Pfad in ReportProcessor; JDBC-Pfad unverändert.
  • docs-v2/: ADR-009; später Autoren-Doku (Contract-Referenz, Studio-Anleitung).

calServer-reports:

  • Bestehende Bundles = stabiler V1-Stand (JDBC + Codespalten), nicht auf V2-SQL umschreiben.
  • Neue V2-Bundles als JSON-Datasource-Varianten mit api_name-Feldern (Struktur-/Packaging-Regeln in robots.md ergänzen).
  • Beispiel-JSONs je Contract als Teil der Bundles ausliefern (Packaging-Allowlist erweitern, wenn Phase 1 startet).

Offene Punkte

  1. Transportweg der Daten zum Runner: Inline im Job (base64/JSON, analog reportArchive) vs. Abruf-URL mit kurzlebigem Token — Entscheidung in Phase 0 nach Messung typischer Dataset-Größen.
  2. Contract-Granularität: Ein Dataset je Report-Familie vs. komponierbare Teil-Datasets (z. B. device + calibration + results) — im Pilot evaluieren.
  3. Versionierungsschema des Contracts (schema_version semver vs. Datumsversion) und Deprecation-Politik.
  4. Zeitpunkt des JR-Versions-Sprungs: Neue V2-Templates sind nicht an das alte JRXML-Schema gebunden; ein späterer Wechsel auf JR 7.x für V2-Bundles wird separat entschieden.
  5. Benennung/Ablage der V2-Bundles im Reports-Repo (<BUNDLE>-V2/ vs. v2/<BUNDLE>/) — vor Phase 1 festlegen.

Anhang A: Code→api_name-Mapping (Auszug)

Maßgebliche Quellen: Migration laravel/database/migrations/2026_03_12_000001_create_api_field_mappings_table.php, Tabelle field_definitions (Spalte v1_name) sowie der FIELD-NAMES-Report im Reports-Repo (sprachabhängige Labels). Auszug der Kernfelder:

V1-Code api_name Bedeutung
I4201 asset_number Inventar-/Gerätenummer
I4202 serial_number Seriennummer
I4203 description Bezeichnung
I4206 manufacturer Hersteller
I4207 model Typ/Modell
C2301 calibration_date Kalibrierdatum
C2303 next_calibration_date Nächster Kalibriertermin
K4601 name Kundenname
K4609 email Kunden-E-Mail
L2801 location_1 Standort (Ebene 1)
R3201 description Reparatur-Beschreibung
R3230 repair_date Reparaturdatum
MTAG/KTAG/CTAG/LTAG id/Fremdschlüssel Datensatz-Identität (V1-Tags)

Anhang B: Beispiel Report-Data-Contract — Kalibrierschein (Skizze)

{
  "meta": {
    "contract": "calibration-certificate",
    "schema_version": "1.0",
    "generated_at": "2026-07-15T10:00:00Z",
    "locale": "de-DE"
  },
  "calibration": {
    "id": "…",
    "calibration_date": "2026-06-30",
    "next_calibration_date": "2027-06-30",
    "procedure_name": "…",
    "calibration_method": "…",
    "pass_fail": "pass",
    "custom_fields": { "lab_reference": "…" }
  },
  "device": {
    "asset_number": "…",
    "serial_number": "…",
    "description": "…",
    "manufacturer": "…",
    "model": "…",
    "custom_fields": { "customer_tag": "…" }
  },
  "customer": {
    "name": "…",
    "street": "…",
    "zip": "…",
    "city": "…"
  },
  "standards": [
    { "asset_number": "…", "description": "…", "certificate_number": "…", "valid_until": "…" }
  ],
  "results": [
    {
      "row_num": 1,
      "test_desc": "…",
      "sys_actual": "…",
      "lower_limit": "…",
      "upper_limit": "…",
      "exp_uncert": "…",
      "pass_fail": "pass",
      "remark": ""
    }
  ]
}

Die results-Struktur entspricht bewusst den bereits lesbaren Feldern der heutigen Results-Subreports — diese Templates sind nahezu unverändert übernehmbar.

Anhang C: JobDescriptor-Erweiterung (Skizze)

{
  "runId": "…",
  "jrxmlPath": "…",
  "reportArchive": "<base64>",
  "format": "pdf",
  "locale": "de",
  "parameters": { "…": "…" },

  // NEU: Datasource-Auswahl
  "dataSourceType": "json",        // "jdbc" (Default, V1-kompatibel) | "json"
  "dataJson": "<base64 JSON>",     // nur bei dataSourceType=json

  // Nur bei dataSourceType=jdbc (V1-Pfad, unverändert):
  "jdbcUrl": "…",
  "dbDriver": "…",
  "dbUser": "…",
  "dbPassword": "…"
}

Abwärtskompatibilität: Fehlt dataSourceType, gilt jdbc — bestehende V1-Aufrufer bleiben unberührt.


Erstellt: 2026-07-15 · Entscheidung: ADR-009 (Report-Data-Contract statt SQL-in-Templates)