Zum Inhalt

V2-Berichte mit JSON-Datenquelle erstellen

calServer V2 kann JasperReports-Vorlagen aus einem JSON-Datensatz mit lesbaren API-Feldnamen fuellen -- statt aus in die Vorlage eingebettetem SQL gegen die alten Metrologie-Codespalten (I4201, C2303, ...). Solche Vorlagen enthalten kein SQL und funktionieren dadurch unabhaengig vom Datenbank-Backend (MySQL, PostgreSQL, MS SQL Server).

Hintergrund und Grundsatzentscheidung: ADR-009.

Wann JSON, wann SQL?

  • Bestehende V1-Vorlagen (JDBC + Codespalten) laufen unveraendert weiter -- sie muessen nicht umgestellt werden.
  • Neue V2-Vorlagen sollten die JSON-Datenquelle nutzen: eine Namenswelt (Vorlagenfelder = API-Felder), DB-unabhaengig, Custom Fields inklusive.

Klassische SQL-Vorlage + reine V2-Daten = leerer Bericht

Eine V1-SQL-Vorlage liest die V1-Tabellen (calibration, inventory, ...). Datensaetze, die direkt in V2 angelegt wurden (oder aus dem Demo-Seeder stammen), existieren nur in den V2-Tabellen -- die SQL-Query findet dann keine Zeile und JasperReports rendert trotzdem "erfolgreich" ein mehrseitiges, aber leeres PDF. calServer erkennt diese Situation inzwischen vor dem Rendern und bricht mit einer verstaendlichen Fehlermeldung ab (auch im Berichts-Laufprotokoll sichtbar), statt ein leeres PDF auszuliefern. Abhilfe: die V2-JSON-Variante der Vorlage verwenden (z. B. DAKKS-JSON-SAMPLE statt DAKKS-SAMPLE).

Eine Vorlage als V2-JSON-Bericht aktivieren

Das geschieht automatisch: Vorlage wie gewohnt unter Administration > Berichtsverwaltung anlegen (Ordner, Dateiname, Format) und das Bundle hochladen. Hat das Haupt-JRXML kein eingebettetes SQL (kein <queryString> -- das Kennzeichen aller V2-Bundles), erkennt calServer das beim Generieren und baut den JSON-Datensatz mit dem Standard-Contract des Grids:

Grid Standard-Contract
calibration calibration-certificate
inventory inventory-datasheet
booking order-document
repair repair-report
location location-report

Vorlagen mit eingebettetem SQL (klassische V1-Bundles) laufen unveraendert ueber JDBC.

Override (optional): Eine report-bezogene Report-Variable mit dem Namen data_contract uebersteuert die Erkennung -- z. B. mit einem anderen Contract-Namen, oder mit einem Nicht-Contract-Wert wie jdbc, um fuer diese Vorlage explizit den klassischen SQL/JDBC-Pfad zu erzwingen.

Etiketten: dieselbe Vorlage druckt eines und vierzig

Eine Etikettenvorlage ist fuer einen Datensatz geschrieben -- ihre Felder lesen device.asset_number oder calibration.calibration_date direkt von der Dokumentwurzel, genau wie das Datenblatt oder das Zertifikat, dessen Contract sie mitbenutzt. Ein Etikett bekommt also keinen eigenen Contract.

Fuer den Stapeldruck aus dem Grid schickt calServer trotzdem nur einen Lauf. Der Datensatz sieht dann so aus:

{
  "meta": { "contract": "inventory-datasheet", "schema_version": "1.1", "count": 3 },
  "stickers": [ { "meta": {}, "device": {}, "customer": {} }, "… je Zeile ein vollstaendiges Dokument" ]
}

Dazu bekommt der Runner die Angabe, dass er stickers durchlaufen soll. Weil jedes Element ein vollstaendiges Dokument ist, loesen alle Feldpfade der Vorlage unveraendert auf: Dieselbe Datei druckt ein Etikett, wenn sie einen Datensatz an der Wurzel bekommt, und vierzig, wenn sie das Array bekommt. Eine Stapel-Fassung der Vorlage ist nicht noetig und waere ein Wartungsfall mehr.

Fuer den Entwurf heisst das: Die Vorlage gegen sample-data.json bauen (ein Datensatz, wie bisher). Der Stapel ergibt sich von selbst.

Den Stapel gibt es nur auf diesem Weg: Er ist das Array, das der Runner durchlaeuft. Ein klassisches SQL-Etikett fuellt sich aus der eigenen Abfrage und liest das Array gar nicht, weshalb calServer den Stapeldruck fuer einen solchen Bericht mit einer Meldung abweist, statt einen Stapel leerer Etiketten zu drucken. Einzeln druckt er unveraendert weiter; wer stapeln will, laedt ein V2-Buendel auf den Platzhalter.

Der Barcode kommt aus dem Datensatz, nicht aus der Vorlage

inventory-datasheet und calibration-certificate tragen einen Block barcode:

{ "type": "datamatrix", "field": "asset_number", "value": "INV-9001" }

type und field kommen aus den Grundeinstellungen (Barcode-Typ, Barcode-Feld), value ist der bereits aufgeloeste Feldwert des Geraets.

Eine Etikettenvorlage sollte barcode.value codieren, nicht device.asset_number. Der Unterschied faellt erst beim zweiten Kunden auf: Wer eine Hausnummer aus einem Zusatzfeld codiert, braucht sonst eine eigene Vorlage. Mit dem Block passt dieselbe Datei ueberall.

Fuer die Symbologie schaltet man die Barcode-Komponenten ueber printWhenExpression gegen barcode.type — so macht es STICKER-CAL-INV-ZEBRA-JSON-SAMPLE. Verfuegbar sind die Encoder, die der Report-Runner mitbringt: qrcode (ZXing) sowie datamatrix, code128, code39, codabar, ean13, ean8, upc, int25 (barcode4j).

In dasselbe printWhenExpression gehoert der Leertest. barcode.value ist null, wenn das konfigurierte Feld am Geraet leer ist — ein Etikett ohne Barcode, kein gescheiterter Bericht. Die barcode4j-Symbologien sehen das anders: Sie verweigern eine leere Nachricht mit NullPointerException: Parameter msg must not be empty, und ein Fuellfehler bricht in JasperReports den ganzen Lauf ab, nicht die einzelne Seite. Im Stapeldruck nimmt ein Geraet ohne Barcode-Wert damit alle anderen mit, und im Druckdialog steht nur die Java-Meldung. Also:

<printWhenExpression><![CDATA[$V{symbology}.equals("datamatrix") && $V{hasCode}.booleanValue()]]></printWhenExpression>

qrcode braucht den Test nicht (ZXing rendert eine leere Nachricht klaglos) — genau deshalb faellt der Fehler nur bei Mandanten auf, die auf DataMatrix (Vorgabe), Code128 oder Code39 stehen. In calServer-reports haelt scripts/check_barcode_guards.py die Regel fest.

Der QR-Code zeigt auf die Geraeteseite, nicht auf eine Nummer

calibration-certificate (ab v1.6) und inventory-datasheet (ab v1.4) tragen zusaetzlich einen Block qr:

{ "enabled": true, "target": "inventory", "url": "https://calserver.example.com/v2/qr/8f14e45f-…" }

url ist der oeffentliche Deep-Link auf die Geraeteseite der QR-Code-Rolle: scannen fuehrt ohne Anmeldung auf Stammdaten, Kalibrierstatus und Dokumente des Geraets. Das ist der Unterschied zu barcode.value — der codiert eine Inventarnummer, die ein Scanner als Text liest, waehrend qr.url eine Adresse ist, die ein Telefon oeffnet.

Der Link steht je Datensatz im Dokument, nicht als Jasper-Parameter. Der alte V1-Parameter QR_Code_Value gilt je Druckauftrag; auf der Titelseite eines Kalibrierscheins reicht das, in einem Etikettenstapel zeigten damit alle vierzig Aufkleber auf dasselbe Geraet.

url ist null, solange die QR-Links abgeschaltet sind (Administration > Grundeinstellungen > Portal, Schalter QR-Links) oder die Kalibrierung kein Geraet hat; enabled sagt, welcher der beiden Faelle vorliegt. Eine Vorlage schaltet die QR-Komponente per printWhenExpression gegen qr.url — ein Aufkleber ohne QR ist besser als einer, der ins Leere zeigt.

Die URL-Laenge entscheidet ueber die Etikettengroesse

Ein Deep-Link mit uuid ist rund 74 Zeichen lang, das ergibt QR-Version 5 mit 37 Modulen. Auf einem 203-dpi-Etikettendrucker braucht das Symbol dadurch etwa 17 mm Kantenlaenge, um zuverlaessig gescannt zu werden. Auf ein 18-mm-DAkkS-Etikett passt daneben nichts mehr — deshalb liegt der QR im Bundle STICKER-DAKKS-40X30MM-QR-JSON-SAMPLE (Kalibrieraufkleber) bzw. STICKER-INV-40X30MM-QR-JSON-SAMPLE (Geraeteetikett) und nicht in den 12/18-mm- oder Zebra-Bundles.

Den erzeugten Datensatz einer konkreten Kalibrierung kann man direkt abrufen (nuetzlich als Beispieldatei fuer den Entwurf):

GET /api/v2/calibrations/{ctag}/report-dataset

Verfuegbare Contracts

Contract Berichtsfamilie Beispiel-Datensatz
calibration-certificate (v1.10) Kalibrierschein DAKKS-JSON-SAMPLE/main_reports/sample-data.json
inventory-datasheet (v1.4) Geraete-Datenblatt INVENTORY-JSON-SAMPLE/main_reports/sample-data.json
order-document (v1.0) Auftrag / Lieferschein ORDER-JSON-SAMPLE bzw. DELIVERY-JSON-SAMPLE (main_reports/sample-data.json)
repair-report (v1.0) Reparaturbericht REPAIR-JSON-SAMPLE/main_reports/sample-data.json
location-report (v1.2) Standort-/Leihbericht (Leihschein als Versanddokument) LOCATION-JSON-SAMPLE/main_reports/sample-data.json
free-delivery-note (v1.0) Freier Versandschein (Lieferpapier ohne Auftrag) FREE-DELIVERY-JSON-SAMPLE/main_reports/sample-data.json
price-list (v1.1) Kundenpreisliste PRICE-LIST-JSON-SAMPLE/main_reports/sample-data.json

Der Contract calibration-certificate liefert ein Wurzelobjekt mit den Bloecken meta, laboratory, accreditation, calibration, device, barcode, qr, customer, procedure, environment, report_variant, standards[], results[], decision_rule, signatures[] und texts. Jeder Entitaetsblock enthaelt zusaetzlich ein custom_fields-Objekt mit den benutzerdefinierten Feldern unter ihrem api_name.

Jede Zeile in results[] traegt die Bewertung dreifach:

  • pass_fail -- die Entscheidung in MET/TEAM-Schreibweise (Pass, Fail, Pass Indeterminate, Fail Indeterminate). Das Backend normalisiert die Spalte fuer den Datensatz und liest dafuer zuerst test_status: die gespeicherte Spalte ist binaer, weil sie unveraendert an MET/TEAM zurueckgeht, der Datensatz traegt die Nuance. Zeilen aus MET/TRACK oder der V1-Laufzeit, die die Entscheidung nur in test_status haben, kommen dadurch ueberhaupt erst mit Bewertung an. Unbekannte, kundeneigene Werte gehen unveraendert durch.
  • test_status -- Klartext inkl. der Guard-Band-Zonen Conditional / ConditionalFail.
  • conformity -- Legendensymbol des Kalibrierscheins (leer, ?, !?, !).

Fuer die Konformitaetsspalte einer neuen Vorlage gehoert conformity gebunden -- der Klartext passt weder in die Spaltenbreite noch in die Legende. Die aus V1 uebernommenen Bundles (DAKKS-JSON-SAMPLE) binden pass_fail, weil sie byte-genaue Kopien des akkreditierten Originals sind; genau dafuer ist die Normalisierung da. guardband_meth nennt die angewandte Entscheidungsregel.

Ab v1.8 traegt jede Zeile ausserdem das Budget der Messunsicherheit (Messunsicherheit nach GUM): uncert_method, cov_fac und deg_free als Text wie die uebrigen Spalten, dazu budget mit Methode, k und seiner Herkunft (dof, plan, fixed), den effektiven Freiheitsgraden, u, U, einer Anhebung auf die CMC, einem Ersatz aus einem Unsicherheitsmodell, dem Grund eines fehlenden U und einer Zeile je Beitrag. Damit laesst sich das Budget als Anhang drucken. budget ist null fuer Zeilen ohne Methode (importiert oder vor GUM erfasst). texts.uncertainty folgt dem tatsaechlichen k; bei k = 2 bleibt es der bisherige Satz. Jede Beitragszeile nennt ihre Freiheitsgrade (degrees_of_freedom, null fuer unendlich): N − 1 bei S1 und, wo das Unsicherheitsmodell sie angibt, die eines Modellbeitrags (Nachtrag 6.2 des ADR, gleiche Form, kein neues Feld).

Ab v1.9 nennt der Block decision_rule die Entscheidungsregel der Kalibrierung (Entscheidungsregel, ISO/IEC 17025:2017 7.8.6.2): key (non_binary, binary_guarded, simple), reference und section in ILAC-G8:09/2019, guard_band (w = U oder w = 0), binary, origin (lab, customer, manual, points), agreement (wo die Regel vereinbart ist), points_decided und points_without_uncertainty, dazu die drei Texte des Abschnitts „Konformitaet“ in der Sprache des Scheins: statement, symbols, legend. Dieselben Texte bekommen die JRXML-Vorlagen als Parameter Conformity_description_1 bis _3, sobald calServer Punkte der Kalibrierung bewertet hat; eine Berichtsvariable desselben Namens gewinnt. Berichtigt ist der Vorgabewert von texts.conformity_legend: * nicht im Akkreditierungsumfang. Vorher stand dort * im Akkreditierungsumfang, also das Gegenteil dessen, was das Sternchen markiert; ein eigener Text des Labors bleibt unangetastet.

Ab v1.10 nennt decision_rule.guard_band auch ein anderes Schutzband als w = U, etwa w = 1,5 · U oder w für ein Risiko von höchstens 2,5 %, in der Sprache des Scheins; decision_rule.guard traegt das Band als Daten: mode (u, factor, risk), factor und risk in Prozent (Das Schutzband w). Jede Zeile in results[] traegt ausserdem accred_source, die Herkunft ihrer Aussage zum Akkreditierungsumfang: derived (aus der Anlage, bei der Erfassung eingefroren), plan (calScript accred no), manual, import (MET/TEAM) oder leer fuer Zeilen von vor dieser Spalte (Akkreditierungsumfang). Geaendert hat sich die Bedeutung von results[].accred, nicht seine Form: Den Wert schreibt jetzt die Erfassung aus der Anlage in den Qualitaetsvorgaben. Vorher trug jede in calServer erfasste Zeile den Vorgabewert 1 der Datenbank und behauptete damit, im Umfang zu liegen; ohne gepflegte Anlage bleibt die Spalte jetzt leer (keine Aussage).

Vorlagen in Jaspersoft Studio entwerfen

  1. JSON-Data-Adapter anlegen und auf die Beispieldatei (sample-data.json bzw. die Ausgabe des report-dataset-Endpoints) zeigen lassen.
  2. Felder ueber ihren JSON-Pfad binden, nicht ueber SQL. In der JRXML ist das die fieldDescription:
<field name="serial_number" class="java.lang.String">
  <fieldDescription><![CDATA[device.serial_number]]></fieldDescription>
</field>
  1. Wiederkehrende Listen (Messergebnisse, verwendete Normale) ueber ein Subreport mit subDataSource rendern -- keine Connection, kein eigenes SQL:
<subreport>
  <dataSourceExpression><![CDATA[
    ((net.sf.jasperreports.engine.data.JsonDataSource)$P{REPORT_DATA_SOURCE})
      .subDataSource("results")
  ]]></dataSourceExpression>
  <subreportExpression><![CDATA[$P{Reportpath} + "/subreports/results.jasper"]]></subreportExpression>
</subreport>
  1. Custom Fields stehen unter ihrem api_name im custom_fields-Block bereit, z. B. fieldDescription = device.custom_fields.customer_tag.

JasperReports-Version

Verbindlich bleibt JasperReports 6.20.6 (siehe robots.md im Repository). Keine Features einsetzen, die eine neuere Version voraussetzen.

Parameter beschreiben

Ein V2-JSON-Bundle kann einen Parameter-Katalog mitliefern, damit calServer die konfigurierbaren Parameter beim Anlegen von Berichtsvariablen mit Beschreibung, Typ und Standardwert anbietet. Details: Parameter im Report-Paket beschreiben.

Referenzvorlage

Das Pilot-Bundle DAKKS-JSON-SAMPLE im Repository calhelp/calServer-reports zeigt Haupt- und Subreports (Ergebnisse, Normale) vollstaendig auf JSON-Basis und enthaelt die passende sample-data.json.

Code-zu-API-Namen-Zuordnung

Die Zuordnung der alten Codespalten zu den lesbaren api_names ist maszgeblich in der Tabelle api_field_mappings hinterlegt und ueber den FIELD-NAMES-Report je Sprache abrufbar. Auszug der Kernfelder:

V1-Code api_name Bedeutung
I4201 asset_number Inventar-/Geraetenummer
I4202 serial_number Seriennummer
C2301 calibration_date Kalibrierdatum
C2303 next_calibration_date Naechster Kalibriertermin
K4601 name Kundenname