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:
- Dual-Track-Realität: Kundensysteme laufen in Produktion ausschließlich V1 (Yii + MySQL) — und das noch auf Jahre. Die V1-Berichtswelt muss stabil weiterlaufen.
- Multi-DB-Gebot: V2 unterstützt MySQL, PostgreSQL und MSSQL gleichberechtigt (siehe
CLAUDE.md,DATABASE_ABSTRACTION_PLAN.md). Jede Berichtsanbindung muss DB-agnostisch funktionieren. - DAkkS-Relevanz: Kalibrierscheine sind akkreditierungsrelevante Dokumente. Layout-Änderungen erzeugen Verifikationsaufwand beim Kunden — die Migration muss layouterhaltend validierbar sein.
- 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 übers1.C2430). - Subreports erhalten die Live-Connection (
<connectionExpression>$P{REPORT_CONNECTION}</connectionExpression>) und führen eigenes SQL aus, gekeyt über die ParameterP_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 (rendertfield_configuration+messagesals 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 perPOST /api/v1/reports/generateentgegen. 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+ReportEngineManagerschalten per Feature-Flag zwischen Legacy-Engine (JasperStarter-CLI) und Runner um; eine Dual-Run-Vergleichsinfrastruktur (beide Engines rendern, Ergebnis wird verglichen, Log inreport_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, …) undPrefixTabledurch — 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_numberstattI4201,next_calibration_datestattC2303); Überlauf-/Custom-Felder liegen konsolidiert in einercustom_fields-JSON-Spalte. - Die Codes existieren in V2 nur noch als Registry:
api_field_mappings(Code→api_name),field_definitions(mitv1_name-Alias),field_configuration(mit V1 geteilt) sowie als JSON-Keys synchronisierter Felder incustom_fields. - Rohes SQL sieht das Mapping nicht: Die Übersetzung Code↔lesbarer Name lebt an der Eloquent-/API-Grenze (
HasDynamicFields,FieldDefinitionService). Der Zugriff aufcustom_fields-Inhalte ist zudem dialektspezifisch (->>in PostgreSQL,JSON_EXTRACTin MySQL,JSON_VALUEin 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:
- 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). - Runner-Erweiterung: Der
JobDescriptorerhält einen Datasource-Typ (json|jdbc). Beijsonfüllt der Runner das Template überJsonDataSourceaus den mitgelieferten Daten; die JDBC-Felder entfallen für diese Jobs. Der JDBC-Pfad bleibt unverändert für V1 erhalten (siehe Anhang C). - 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")). - 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. - 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 Basisfield_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 (NamespaceApp\Services\Report\), Endpoints/Erweiterung der bestehenden Report-Controller;ReportServicesendet Datasets statt JDBC-Credentials (für V2-Bundles).report-runner/:JobDescriptorum Datasource-Typ + Datenfeld erweitern; JSON-Fill-Pfad inReportProcessor; 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¶
- 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. - Contract-Granularität: Ein Dataset je Report-Familie vs. komponierbare Teil-Datasets (z. B.
device+calibration+results) — im Pilot evaluieren. - Versionierungsschema des Contracts (
schema_versionsemver vs. Datumsversion) und Deprecation-Politik. - 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.
- 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)