Skip to content

Konzept: Parameter-Katalog im Report-Paket — vom Berichtsentwickler zur Variablen-Einrichtung

Kontext und Problem

JasperReports-Vorlagen tragen ihre Anpassungsparameter bereits in sich: jede Vorlage deklariert <parameter>-Elemente ($P{…}), über die Fußzeilen, Logos, Labor-Identität, Textbausteine oder Schaltjahre des Layouts gesteuert werden. Konfiguriert werden diese Parameter in calServer über Berichtsvariablen (master_report_variable), die der Admin unter Administration → Berichtsverwaltung → Berichtsvariablen anlegt.

Der Weg vom Berichtsentwickler zum konfigurierten System ist heute jedoch ein reiner Handarbeits- und Zuruf-Prozess:

  • Der Entwickler muss die Parameternamen, ihre Bedeutung, erwartete Formate und sinnvolle Standardwerte außerhalb des Pakets kommunizieren (README, E-Mail, Doku). Das Paket-Inspect-Feature (ReportPackageInspector) zeigt zwar eine gebündelte README an — maschinenlesbar ist davon nichts.
  • Der Admin tippt im Dialog „Berichtsvariable erstellen" den Variablennamen frei ein. Tippfehler oder falsche Groß-/Kleinschreibung fallen erst auf, wenn der Bericht leer oder falsch rendert.
  • Welche Parameter eine Vorlage überhaupt hat, ist im System nirgends sichtbar — weder beim Hochladen noch bei der Variablenpflege.

Ziel dieses Konzepts: Die Parameter samt Beschreibung reisen im Report-ZIP mit. Nach Auswahl des Berichts im Dialog „Berichtsvariable hinzufügen" bietet das System die im Paket deklarierten Parameter mit Beschreibung, Typ und Standardwert zur Auswahl an — statt eines leeren Freitextfelds.

Ist-Zustand (Kurzfassung)

Baustein Heute
Report-Paket ZIP mit {Pkg}/main_reports/{Pkg}.jrxml + subreports/, Upload über DMS (AdminReportSettingController::upload()), Inspektion über GET …/report-settings/{id}/readme (ReportPackageInspector)
Berichtsvariablen master_report_variable (V1-geteilte Tabelle): variable_name, variable_label, content, Geltungsbereich global (all) / Berichtstyp / einzelner Bericht; Pflege im Tab „Berichtsvariablen" (frontend-v2/pages/admin/reports.vue)
Injektion in Jasper ReportParameterBuilder::build() flacht Variablen zu Jasper-Parametern ab: $P{ucfirst(variable_name)} — Variable company_name füllt Parameter Company_name
Laufzeit-Prompt ReportParameterPromptService parst das Haupt-JRXML und fragt isForPrompting-Parameter beim Generieren ab (Nutzer-Eingabe, kein Admin-Setup)
System-Parameter Reportpath, PrefixTable, Entity-Parameter (CTAG, InventoryId, Auftragsnummer, …) werden automatisch gesetzt und dürfen nie konfigurierbar sein

Wichtig für alles Folgende: master_report_variable wird mit V1 geteilt — neue Spalten (z. B. „Beschreibung") sind tabu. Der Parameter-Katalog muss also aus dem Paket kommen, nicht aus der Datenbank. Das ist kein Nachteil, sondern der Kern des Konzepts: das Paket ist die einzige Quelle der Wahrheit über seine eigenen Parameter.

Leitidee: Das selbstbeschreibende Report-Paket

Das Report-Paket liefert einen Parameter-Katalog mit: eine maschinenlesbare Beschreibung aller konfigurierbaren Parameter. calServer liest den Katalog direkt aus dem hochgeladenen ZIP (wie heute schon die README) und bietet ihn überall dort an, wo Parameter konfiguriert werden.

Es gibt zwei Quellen, mit klarer Vorrangregel:

  1. Manifest parameters.json im Paketwurzelverzeichnis (kanonisch, empfohlen für alle neuen Pakete): volle Metadaten inkl. Beschreibung, Eingabetyp, Auswahloptionen, Gruppierung, Mehrsprachigkeit.
  2. JRXML-Fallback (für Bestandspakete ohne Manifest): calServer leitet den Katalog aus den <parameter>-Deklarationen des Haupt-JRXML ab — <parameterDescription> und optionale calserver.*-Properties werden ausgewertet.

Entscheidungsregel: Liegt ein Manifest bei, gilt nur das Manifest (beim Upload gegen das JRXML validiert, Abweichungen als Warnung). Ohne Manifest wird der Katalog aus dem JRXML abgeleitet. Kein Merge zweier Quellen — das hält das Verhalten vorhersagbar und die Implementierung schlank.

Berichtsentwickler (Jaspersoft Studio)
    |
    |  deklariert Parameter im JRXML
    |  beschreibt sie in parameters.json (oder <parameterDescription>)
    v
Report-ZIP  ──upload──>  calServer DMS
    |
    |  GET /admin/report-settings/{id}/parameters
    v
ReportParameterCatalogService  (liest ZIP, kein Extract auf Platte)
    |
    v
Dialog "Berichtsvariable hinzufügen"
    → Parameter-Auswahl mit Beschreibung, Typ, Default
    → Variable wird vorbefüllt angelegt

Das Manifest: parameters.json

Liegt im Paket neben main_reports/ (Wurzel des Bundles, gleiche Ebene wie die README). Beispiel:

{
  "$schema": "https://calhelp.github.io/calServer-reports/schema/report-parameters.schema.json",
  "version": 1,
  "parameters": [
    {
      "name": "Company_footer",
      "label": { "de": "Fußzeile", "en": "Footer" },
      "description": {
        "de": "Text am unteren Rand jeder Seite, z. B. Firmenname und Akkreditierungshinweis.",
        "en": "Text at the bottom of every page, e.g. company name and accreditation notice."
      },
      "role": "variable",
      "scope": "report",
      "input": "textarea",
      "default": "calHelp – Kalibrierlaboratorium",
      "required": false,
      "group": "Layout",
      "example": "Musterfirma GmbH · D-K-12345-01-00"
    },
    {
      "name": "Show_uncertainty",
      "label": { "de": "Messunsicherheit anzeigen" },
      "role": "variable",
      "scope": "type",
      "input": "boolean",
      "default": "true"
    },
    {
      "name": "Start_Date",
      "label": { "de": "Startdatum" },
      "description": { "de": "Beginn des Auswertungszeitraums." },
      "role": "prompt",
      "input": "date"
    }
  ]
}

Felder

Feld Pflicht Bedeutung
name ja Exakter JRXML-Parametername ($P{…}). Muss im Haupt-JRXML deklariert sein (Validierung, s. u.)
label ja Anzeigename; String oder Objekt je Sprache (de, en, …). Vorbefüllt variable_label
description empfohlen Erklärtext für den Admin; String oder Sprachobjekt. Wird im Dialog unter dem Feld angezeigt, nicht gespeichert (keine Spalte in der V1-Tabelle nötig)
role nein (Default variable) variable = Admin-Konfiguration (Berichtsvariable) · prompt = Nutzerabfrage beim Generieren (Paket-7-Dialog) · system = wird automatisch versorgt, nie anbieten
scope nein (Default report) Empfohlener Geltungsbereich: report, type, global. Nur Vorauswahl im Dialog — der Admin bleibt frei
input nein (Default text) UI-Eingabetyp: text, textarea, select, boolean, number, date, color, image
options bei select Liste erlaubter Werte: ["A4", "Letter"] oder [{"value": "A4", "label": {"de": "DIN A4"}}]
default nein Standardwert als String (befüllt content vor)
required nein (Default false) Kennzeichnung im Dialog; der Bulk-Import (s. u.) legt Pflichtparameter ohne Default nicht stillschweigend leer an
group / order nein Gruppierung und Reihenfolge in Auswahllisten (z. B. „Layout", „Labor-Identität")
example nein Beispielwert als Platzhaltertext im Eingabefeld

Das JSON-Schema wird im Repository calhelp/calServer-reports veröffentlicht und versioniert ("version": 1 als Format-Kennung, Erweiterungen bleiben abwärtskompatibel).

Namenskonvention und die ucfirst-Stolperfalle

ReportParameterBuilder injiziert Variablen als $P{ucfirst(variable_name)}. Damit ein Parameter überhaupt per Berichtsvariable erreichbar ist, muss sein JRXML-Name mit einem Großbuchstaben beginnen (Company_footer, nicht company_footer — Letzteres kann von keiner Variable getroffen werden).

Daraus folgen zwei Regeln:

  1. Ableitung: Der vorgeschlagene variable_name ist lcfirst(name) — Parameter Company_footer → Variable company_footer. So stimmt die Auflösung zur Laufzeit garantiert (Round-Trip über ucfirst).
  2. Validierung: Parameter mit role: variable, deren Name mit einem Kleinbuchstaben beginnt, erzeugen beim Upload und im CI-Check eine Warnung („von der Variablenauflösung nicht erreichbar").

Reservierte Namen (data_contract, Reportpath, PrefixTable, die REPORT_*-Standardparameter sowie die Entity-Parameter des jeweiligen Grids wie CTAG, MTAG, InventoryId, Auftragsnummer, Auftragsid) werden vom Katalog immer als system behandelt — egal, was das Manifest behauptet.

JRXML-Fallback (Pakete ohne Manifest)

Für Bestandspakete leitet calServer den Katalog aus dem Haupt-JRXML ab. Das nutzt ausschließlich Bordmittel von JasperReports, die in Jaspersoft Studio direkt editierbar sind:

<parameter name="Company_footer" class="java.lang.String">
  <property name="calserver.role" value="variable"/>
  <property name="calserver.scope" value="report"/>
  <property name="calserver.input" value="textarea"/>
  <parameterDescription><![CDATA[Text am unteren Rand jeder Seite.]]></parameterDescription>
  <defaultValueExpression><![CDATA["calHelp – Kalibrierlaboratorium"]]></defaultValueExpression>
</parameter>

Abgeleitet wird:

Katalogfeld Quelle im JRXML
name name-Attribut
label calserver.label-Property, sonst der Parametername
description <parameterDescription>
Typ/input class-Attribut (Mapping wie in ReportParameterPromptService::TYPE_MAP), übersteuerbar per calserver.input
default Literal aus <defaultValueExpression> (nur einfache String-/Zahl-Literale, wie heute im Prompt-Service)
role calserver.role-Property; ohne Property greift die Heuristik: reservierte/Entity-Namen → system, isForPrompting="true"prompt, alle übrigen → variable

Die JRXML-Parsing-Logik existiert in ReportParameterPromptService bereits zur Hälfte (XPath über <parameter>, Typ-Mapping, Default-Extraktion, Ausschlussliste). Sie wird in einen gemeinsamen Baustein extrahiert (JrxmlParameterParser in app/Services/Report/), den Katalog- und Prompt-Service teilen — keine zweite Parser-Implementierung.

Backend-Umsetzung

Neuer Service: ReportParameterCatalogService

app/Services/Report/ReportParameterCatalogService.php, nach dem Muster von ReportPackageInspector (liest die DMS-Kopie des ZIPs direkt, kein Entpacken auf Platte, harte Größenlimits — Manifest max. 256 KB, JRXML wie bisher 4 MB):

  1. parameters.json im Bundle suchen (Wurzel, ersatzweise main_reports/), parsen, gegen das Schema validieren.
  2. Ohne Manifest: Haupt-JRXML (Konvention main_reports/, wie im Inspector) über den gemeinsamen Parser auswerten.
  3. Reservierte/System-/Entity-Parameter herausfiltern (gemeinsame Konstante mit ReportParameterPromptService::IGNORED_PARAMETERS + Entity-Parameternamen je Grid).
  4. Normalisierte Katalogeinträge zurückgeben und mit dem Ist-Zustand der Variablen anreichern.

Ergebnisform je Eintrag:

[
  'name' => 'Company_footer',            // JRXML-Parametername
  'variable_name' => 'company_footer',   // abgeleiteter Vorschlag (lcfirst)
  'label' => 'Fußzeile',                 // in Anfragesprache aufgelöst
  'description' => 'Text am unteren Rand jeder Seite …',
  'role' => 'variable',
  'scope_suggestion' => 'report',
  'input' => 'textarea',
  'options' => null,
  'default' => 'calHelp – Kalibrierlaboratorium',
  'required' => false,
  'group' => 'Layout',
  'source' => 'manifest',                // 'manifest' | 'jrxml'
  'configured' => [                      // existierende master_report_variable-Zeilen
    'report' => false, 'type' => true, 'global' => false,
  ],
]

Neuer Endpoint

GET /api/v2/admin/report-settings/{id}/parameters

Neben dem bestehenden …/readme im AdminReportSettingController (gleiche Permission admin_settings-Route-Gruppe, gleiche 404-Semantik ohne Paket). Antwort: data.parameters (Liste wie oben), data.source (manifest/jrxml/none) und data.warnings (Validierungsbefunde, s. u.).

Kein neuer Datenbankstand: Der Katalog wird bei Abruf aus dem Paket gelesen (wie der README-Endpoint heute — die Zugriffsfrequenz ist ein Admin-Dialog, kein Hot Path). Falls nötig, später Cache::remember je DMS-Dateiversion — kein Bestandteil der ersten Ausbaustufe.

Validierung beim Upload

uploadReportPackage() ruft den Katalog-Service nach erfolgreichem Upload einmal auf und gibt Warnungen (nicht blockierend!) in der Upload-Antwort zurück:

  • Manifest-Parameter, der im Haupt-JRXML nicht deklariert ist (Tippfehler, Drift nach Umbenennung).
  • JRXML-Parameter mit role: variable, der kleingeschrieben beginnt (ucfirst-Falle).
  • Nicht parsebares Manifest (→ Fallback auf JRXML, mit Hinweis).

Ein fehlerhaftes Manifest darf niemals Upload oder Berichtsgenerierung blockieren — schlimmstenfalls verhält sich das System wie heute (kein Katalog).

Tests

  • Unit: ReportParameterCatalogServiceTest (Manifest-Parsing, JRXML-Fallback, Vorrangregel, Filterung, lcfirst-Ableitung, i18n-Auflösung, defekte Manifeste).
  • Feature: AdminReportParameterCatalogTest (Endpoint, Permissions, 404 ohne Paket, configured-Anreicherung) — analog AdminReportPackageInspectTest.

Frontend-Umsetzung (pages/admin/reports.vue)

Dialog „Berichtsvariable erstellen"

Der bestehende Dialog bleibt; er wird um eine Auswahlstufe ergänzt:

  1. Der Admin wählt wie bisher den Bericht (Geltungsbereich). Sobald ein konkreter Bericht gewählt ist, lädt das Frontend GET …/report-settings/{id}/parameters (useReportAdmin-Composable, neben fetchReportPackageInfo).
  2. Über dem Feld „Variablenname" erscheint ein neues Select „Parameter aus dem Report-Paket": die Katalogeinträge mit role: variable, gruppiert nach group, mit Label + technischem Namen; bereits konfigurierte Einträge sind markiert (✓ und Geltungsbereich, z. B. „bereits als Typ-Variable gesetzt — hier anlegen überschreibt für diesen Bericht").
  3. Auswahl eines Parameters befüllt das Formular vor: variable_name (abgeleitet), variable_label (Label), content (Default), Geltungsbereich (Scope-Empfehlung). Die Beschreibung wird als Hilfetext unter dem Inhaltsfeld angezeigt; input steuert das Eingabe-Widget (Textarea, Select mit options, DatePicker, ToggleSwitch, …).
  4. Freitext bleibt möglich — Pakete ohne Katalog, Alt-Bundles und Sonderfälle funktionieren unverändert. Das Select ist ein Angebot, kein Zwang. Ist kein Katalog vorhanden (source: none), erscheint das Select gar nicht — der Dialog sieht aus wie heute.

Paket-Inspektion

Der bestehende „Welche Jasper ist das?"-Dialog (README + Dateiliste) erhält einen Abschnitt „Parameter": tabellarisch Name, Label, Typ, Default, Beschreibung, Status (konfiguriert/fehlend) sowie die Upload-Warnungen. Damit sieht der Admin unmittelbar nach dem Hochladen, was das Paket erwartet.

Bulk-Import (Ausbaustufe)

Aktion „Fehlende Variablen aus Paket anlegen" am Report: legt für alle Katalogeinträge mit role: variable, Default und ohne existierende Variable die report-bezogenen Variablen in einem Schritt an (Bestätigungsdialog mit Checkbox-Liste). Pflichtparameter ohne Default werden gelistet, aber nicht automatisch angelegt — sie brauchen eine bewusste Eingabe.

Workflow des Berichtsentwicklers (calServer-reports)

  1. Parameter wie gewohnt im JRXML deklarieren (Jaspersoft Studio); Namen für konfigurierbare Parameter mit Großbuchstaben beginnen.
  2. parameters.json im Bundle-Verzeichnis pflegen. Einstiegshürde senken: ein Generator-Skript (scripts/generate_parameters_manifest.py im calServer-reports-Repo) erzeugt ein Manifest-Gerüst aus den JRXML-Deklarationen — der Entwickler ergänzt nur Beschreibungen und Rollen.
  3. CI-Check im calServer-reports-Repo (analog check_jasper_version.sh): validiert Manifest gegen Schema und JRXML (Namensabgleich, ucfirst-Regel, options/input-Konsistenz). Drift zwischen Manifest und JRXML wird so im Pull Request sichtbar, nicht erst beim Kunden.
  4. Der bestehende Paket-Build nimmt das Manifest automatisch mit (es liegt im Bundle-Verzeichnis); an Build und Download-Seite ändert sich nichts.
  5. Autoren-Doku: neue Seite in docs-v2/admin/berichte/ („Parameter im Report-Paket beschreiben") + Verweis aus v2-json-reports.md und der Vorlagen-Doku im calServer-reports-Repo.

Kompatibilität

System Verhalten
V1 (Yii) Ignoriert parameters.json vollständig — das ZIP bleibt V1-tauglich. Keine Yii-Migration nötig (keine Schemaänderung)
report-runner Unverändert. ReportPackage.locateMain() sucht nur .jrxml; eine zusätzliche JSON-Datei stört weder Extraktion noch Kompilierung
Alt-Pakete Ohne Manifest greift der JRXML-Fallback; ohne auswertbare Deklarationen verhält sich alles wie heute
Datenbank Keine Änderung. master_report_variable bleibt unangetastet (V1-geteilt); Beschreibungen werden nie persistiert, sondern stets live aus dem Paket gelesen
Laufzeit-Prompt (Paket-7) Unverändert; profitiert später von derselben Quelle: Einträge mit role: prompt können den Prompt-Dialog um Label/Beschreibung anreichern (Ausbaustufe)

Sicherheit und Robustheit

  • Manifest ist untrusted input: Größenlimit (256 KB), striktes JSON-Parsing, Schema-Validierung; Label/Beschreibung werden im Frontend als reiner Text gerendert (kein HTML/Markdown), Längen serverseitig gekappt.
  • JRXML-Parsing wie bisher mit LIBXML_NONET (kein XXE, kein Netzzugriff).
  • ZIP-Zugriff nur lesend über ZipArchive auf die DMS-Kopie mit Byte-Limits — identisches Härtungsniveau wie ReportPackageInspector.
  • Der Katalog ist rein beratend: er beeinflusst die Generierung nicht. ReportParameterBuilder und die Vorrangkette der Variablen bleiben unverändert; ein manipuliertes Manifest kann keine Systemparameter (CTAG, Reportpath, …) überschreiben, weil diese Namen serverseitig immer als system gefiltert werden und die Injektionsreihenfolge im Builder ohnehin zuletzt greift.

Umsetzungsphasen

Phase Inhalt Abhängigkeiten
1 — Backend-Katalog JrxmlParameterParser extrahieren, ReportParameterCatalogService, Endpoint …/parameters, Upload-Warnungen, Tests keine
2 — Frontend-Dialog Parameter-Select im Variablen-Dialog, Vorbefüllung, Beschreibungsanzeige, Inspect-Dialog-Abschnitt Phase 1
3 — Authoring-Kette JSON-Schema veröffentlichen, Generator-Skript, CI-Validator, Manifeste für die bestehenden Bundles, Autoren-/Admin-Doku Phase 1 (Schema-Festlegung)
4 — Ausbaustufen Bulk-Import „fehlende Variablen anlegen", Beschreibungs-Spalte im Variablen-Grid (live aus Katalog), Prompt-Dialog-Anreicherung (role: prompt) Phasen 1–3

Phasen 1 und 2 liefern den Kern des gewünschten Verhaltens; Phase 3 macht ihn für Berichtsentwickler nachhaltig nutzbar. Jede Phase ist einzeln shipbar.

Nicht-Ziele

  • Kein neues Speichermodell für Variablen — die V1-geteilte Tabelle und die bestehende Vorrangkette bleiben, wie sie sind.
  • Keine automatische, ungefragte Anlage von Variablen beim Upload — der Admin entscheidet (Bulk-Import ist explizit und bestätigungspflichtig).
  • Kein Ersatz des Laufzeit-Prompts — Konfigurationsvariablen (Admin) und Generierungs-Prompts (Nutzer) bleiben getrennte Konzepte mit gemeinsamer Metadatenquelle.

Offene Punkte

  1. variable_name-Override im Manifest? Aktuell bewusst weggelassen (Ableitung per lcfirst deckt die Konvention ab). Bei Bedarf als optionales Feld nachrüstbar, ohne Format-Bruch.
  2. Sprachauflösung: Anfragesprache (Accept-Language/User-Locale) mit Fallback-Kette de → en → erste vorhandene — abzustimmen mit der bestehenden i18n-Strategie des Admin-Frontends.
  3. Manifest-Ort bei Einzel-JRXML-Uploads (ohne ZIP): erste Ausbaustufe unterstützt nur den JRXML-Fallback; ein Sidecar-Manifest für Einzeldateien lohnt erst bei nachgewiesenem Bedarf.