Skip to content

PDF Letterhead Templates

Manages the PDF templates calServer places underneath a report as stationery — Administration > Report management > PDF templates.

This page described something else until 2026-08

It listed "built-in reports" (standard calibration certificate, device overview, annual summary …), a variable mapping and an endpoint POST /api/v2/reports/generate. None of that exists. calServer ships no report templates, and that is deliberate — see Where report templates come from. What the menu entry actually manages is described here.


What a PDF template is

An uploaded PDF carrying the company layout — letterhead, footer, frame — which ReportOverlayService places underneath the report pages when rendering. The report itself remains the JasperReports template; the PDF only supplies the paper.

It is stored in master_report_template, the file itself lives versioned in the DMS. Each template carries:

Field Meaning
template_name display name in the picker
total_page page count of the uploaded PDF
first_page which PDF page goes underneath the first report page
other_page which one goes underneath all following pages
sort_order order in the picker

The first_page / other_page split is the entire point of the construction: stationery typically carries a full letterhead on page 1 and a leaner continuation sheet after that.

Assigning it to a report

In the report under Stationery:

Setting use_template Effect
No letterhead null the report renders without any underlay
Global letterhead html the centrally maintained details (address, phone, accreditation number) are rendered onto every page
PDF template pdf the PDF managed here is placed underneath (template_uID)

Recommendation: the global letterhead. It is maintained in one place and applies to every report; a PDF template has to be regenerated and re-uploaded whenever the address or accreditation number changes. The reasoning is in ADR-037.

PDF templates stay the right choice when the paper carries more than text — watermarks, perforation marks, a designed layout that cannot be derived from a header and a footer.

Replacing a template

New stationery replaces the file of the existing template — row menu Datei austauschen, POST /admin/report-templates/{id}/file. The template keeps its uID, every template_uID in the report settings stays valid, and the new PDF becomes the next DMS version.

Uploading through PDF-Vorlage hochladen (POST /admin/report-templates) creates a second template instead — and the old one cannot be deleted while a report still uses it as stationery (409, naming the reports). That is the wrong route for an exchange.

If the new PDF has fewer pages than the old one, first_page and other_page are clamped to the last page that exists rather than pointing past the end.

Versioning

Every upload creates a new DMS version; the previous one stays retrievable:

  • GET /admin/report-templates/{id}/versions — version list
  • POST /admin/report-templates/{id}/versions/{versionId}/activate — re-activate an older version
  • GET /admin/report-templates/{id}/versions/{versionId}/download — download a version

This is the same mechanism the report packages themselves use. An accidental upload is therefore one click back, not a restore case.

Where report templates come from

The actual JasperReports templates (.jrxml) are not part of the application. They are maintained, versioned and reviewed in calhelp/calServer-reports and published as packages at calhelp.github.io/calServer-reports/downloads — see Downloading report packages.

This is a deliberate decision, not a gap: a template shipped inside the application image would fork the copy maintained over there. That is why calServer creates a row for each of the three system reports but leaves it empty, naming the package that belongs in it right in the report management (LOCATION-JSON-SAMPLE, PRICE-LIST-JSON-SAMPLE, ORDER-JSON-SAMPLE).

New templates should use the JSON data source instead of embedded SQL: Authoring V2 reports with a JSON data source.

Generating a report

Reports are generated at their entity, not through a central generation endpoint:

GET  /api/v2/calibrations/{ctag}/reports            # available reports
POST /api/v2/calibrations/{ctag}/reports/generate   # generate
GET  /api/v2/inventories/{mtag}/reports
POST /api/v2/inventories/{mtag}/reports/generate
GET  /api/v2/bookings/{bookingId}/reports
POST /api/v2/bookings/{bookingId}/reports/generate

Rendering happens in the report-runner container. Its state:

docker ps --filter name=report-runner --format '{{.Names}}\t{{.Status}}'
docker logs report-runner --tail 50

If it is not running, every report fails with "Report runner unavailable".