Skip to main content

Reporting

Cascadia PLM includes a built-in report engine that lets users define, execute, and export tabular reports against any item type. Reports are configured through a visual builder and stored in the database as reusable definitions that can be shared with other users or roles.


Concepts​

A report definition describes which item type to query, which columns to display, which filters to apply, and how to sort the results. Definitions are persisted across five database tables:

TablePurpose
reportsReport metadata (name, description, item type, visibility)
report_columnsOrdered list of columns to display
report_filtersSaved filter conditions
report_sortsMulti-level sort configuration
report_executionsAudit log of every execution (timing, row count, success/failure)
report_exportsRecord of CSV exports

A report execution runs the definition's query against the live database and returns rows, column metadata, and pagination info. Executions are logged in report_executions for auditing.


Report Definitions​

Structure​

A report definition contains:

{
"name": "High-Cost Parts",
"description": "Parts with cost exceeding $100",
"itemType": "Part",
"isPublic": false,
"columns": [
{
"fieldPath": "itemNumber",
"label": "Item Number",
"displayOrder": 0,
"isVisible": true
},
{
"fieldPath": "name",
"label": "Name",
"displayOrder": 1,
"isVisible": true
},
{
"fieldPath": "parts.cost",
"label": "Cost",
"displayOrder": 2,
"isVisible": true,
"formatType": "currency"
},
{
"fieldPath": "parts.material",
"label": "Material",
"displayOrder": 3,
"isVisible": true
},
{
"fieldPath": "state",
"label": "State",
"displayOrder": 4,
"isVisible": true
}
],
"filters": [
{
"fieldPath": "parts.cost",
"operator": "gte",
"value": "100",
"displayOrder": 0
}
],
"sorts": [{ "fieldPath": "parts.cost", "direction": "desc", "priority": 0 }]
}

Supported Item Types​

Reports can be created for any of the following item types:

  • Part
  • Document
  • ChangeOrder
  • Requirement
  • Task

Column Configuration​

Each column specifies:

PropertyRequiredDescription
fieldPathyesDot-notation path to the field (e.g., itemNumber or parts.cost)
labelyesDisplay name in the report header
displayOrderyesColumn position (0-indexed)
isVisibleyesWhether the column appears in output (hidden columns are excluded from CSV)
formatTypenoValue formatter (see below)
widthnoColumn width in pixels

Field Paths​

Base fields (available for all item types) use a single name:

  • itemNumber, name, revision, state, createdAt, modifiedAt

Type-specific fields use dot notation with the table name:

  • Part: parts.description, parts.partType, parts.material, parts.weight, parts.cost, parts.leadTimeDays, parts.quantityOnHand, parts.location
  • Document: documents.description, documents.fileName, documents.fileSize, documents.mimeType
  • ChangeOrder: change_orders.changeType, change_orders.priority, change_orders.reasonForChange, change_orders.riskLevel
  • Requirement: requirements.description, requirements.type, requirements.priority, requirements.status, requirements.category
  • Task: tasks.description, tasks.priority, tasks.dueDate, tasks.estimatedHours, tasks.actualHours

Format Types​

FormatBehavior
textRaw string (default)
numberLocale-formatted number
currencyUSD currency formatting
dateDate only
datetimeDate and time
boolean"Yes" / "No"
percentageMultiply by 100, append "%"
emailPlain text (no special handling)
urlPlain text (no special handling)

Filter Configuration​

Each filter specifies a condition that rows must satisfy:

PropertyRequiredDescription
fieldPathyesField to filter on
operatoryesComparison operator
valueconditionalComparison value (not needed for is_null/is_not_null)
value2conditionalEnd value for between operator
displayOrderyesFilter position

Filter Operators​

OperatorDescriptionValue Required
eqEqualsyes
neNot equalsyes
gtGreater thanyes
ltLess thanyes
gteGreater than or equalyes
lteLess than or equalyes
likeContains (case-insensitive ILIKE)yes
not_likeDoes not containyes
inIn comma-separated listyes
not_inNot in comma-separated listyes
is_nullField is nullno
is_not_nullField is not nullno
starts_withStarts with (case-insensitive)yes
ends_withEnds with (case-insensitive)yes
betweenBetween two values (inclusive)value + value2

Sort Configuration​

Reports support multi-level sorting:

PropertyRequiredDescription
fieldPathyesField to sort by
directionyesasc or desc
priorityyesSort precedence (0 = first)

When no sorts are configured, the engine defaults to modifiedAt descending.


Visibility and Sharing​

Reports have four levels of access:

MechanismDescription
CreatorThe user who created the report always has access
PublicWhen isPublic = true, all authenticated users can see the report
Shared with rolessharedWithRoles is a JSON array of role names; users with any matching role gain access
Shared with userssharedWithUsers is a JSON array of user UUIDs who gain access

The ReportService.list() method evaluates all four conditions with OR logic, so a report is visible if any one of them matches.


Report Execution​

Executing a report runs the underlying query against the database and returns structured results.

Execution Flow​

  1. Load the report definition (columns, filters, sorts).
  2. Merge saved filters with any runtime filter overrides.
  3. Build the SQL query: items table joined with the type-specific table (e.g., parts), with WHERE conditions from filters, ORDER BY from sorts.
  4. Execute the query with limit + 1 rows to detect whether more data exists.
  5. Transform raw row data into flat objects keyed by fieldPath.
  6. Log the execution in report_executions (duration, row count, success).
  7. Return columns, rows, and pagination metadata.

Runtime Filter Overrides​

When executing a report, callers can pass runtimeFilters to add conditions that supplement (not replace) the saved filters. This enables parameterized reports:

{
"limit": 50,
"offset": 0,
"runtimeFilters": [
{ "fieldPath": "state", "operator": "eq", "value": "Released" }
]
}

Runtime filters use the same operator vocabulary as saved filters.

Execution Result​

{
"data": {
"result": {
"reportId": "uuid",
"reportName": "High-Cost Parts",
"executedAt": "2026-03-27T12:00:00Z",
"durationMs": 42,
"totalRows": 15,
"columns": [
{
"fieldPath": "itemNumber",
"label": "Item Number",
"displayOrder": 0,
"isVisible": true
}
],
"rows": [
{
"itemNumber": "PRT-001",
"name": "Motor Bracket",
"parts.cost": 150.0
}
],
"pagination": {
"limit": 100,
"offset": 0,
"hasMore": false
}
}
}
}

Execution Audit Trail​

Every execution is logged in report_executions:

  • reportId -- which report was run
  • executedBy -- user UUID
  • executedAt -- timestamp
  • rowCount -- number of rows returned
  • durationMs -- query time in milliseconds
  • parameters -- the execution options that were passed
  • success -- whether execution completed without error
  • errorMessage -- error details if execution failed

CSV Export​

Reports can be exported to CSV via the export endpoint. The flow is:

  1. Execute the report (same as a normal execution).
  2. Pass the execution result through ReportService.exportToCSV().
  3. Return the CSV as a downloadable file with Content-Disposition: attachment.

CSV formatting rules:

  • Only visible columns are included.
  • Column headers use the configured label.
  • Values are formatted according to the column's formatType.
  • Values containing commas, quotes, or newlines are wrapped in double quotes.
  • Internal double quotes are escaped as "".

The generated filename follows the pattern report-<id>-<YYYY-MM-DD>.csv.

Export records are tracked in the report_exports table with file size and storage path metadata.


UI Components​

Report Builder (/reports/new, /reports/:id/edit)​

The ReportBuilder component provides a visual form for defining reports:

  • Report Information -- name, description, item type, public/private toggle
  • Columns -- add, remove, reorder (up/down), select field and format type
  • Filters -- add, remove, select field, operator, and value(s)
  • Sorting -- add, remove, select field and direction with priority ordering

When the item type changes, columns, filters, and sorts are reset to prevent field path mismatches.

Report Viewer (/reports/:id/view)​

The ReportViewer component:

  • Automatically executes the report on mount
  • Displays summary cards (total rows, column count, execution time, active filters)
  • Renders the result in a table with formatted values
  • Provides a Refresh button to re-execute
  • Provides an Export CSV button
  • Shows pagination info and a "more results available" indicator
  • Displays active filter and sort summaries

Report List (/reports)​

The ReportTable component renders all accessible reports in a DataGrid with:

  • Clickable report names linking to the viewer
  • Visibility badges (Public/Private)
  • Column and filter counts
  • Row actions: Run, Edit, Delete

Reports are grouped by item type with summary statistics (total, public, private, type count).


Report API Reference​

GET /api/reports​

List all reports accessible to the current user.

ParameterRequiredDescription
itemTypenoFilter by item type
limitnoPage size (default: 100, max: 500)
offsetnoPagination offset

Response:

{
"data": {
"reports": [...],
"total": 12
}
}

POST /api/reports​

Create a new report. Body must conform to the reportSchema (see Report Definitions for the full structure). At least one column is required.

Response: 201 Created

{
"data": {
"report": { ... }
}
}

GET /api/reports/:id​

Get a single report definition with its columns, filters, and sorts.

Response:

{
"data": {
"report": { ... }
}
}

PUT /api/reports/:id​

Update a report definition. Accepts a partial body -- only the fields included are updated. When columns, filters, or sorts are provided, they fully replace the existing set (delete-and-reinsert).

DELETE /api/reports/:id​

Delete a report and all its associated columns, filters, sorts, executions, and exports (cascade).

Response:

{
"data": {
"success": true
}
}

POST /api/reports/:id/execute​

Execute a report and return results.

Request body (optional):

{
"limit": 100,
"offset": 0,
"runtimeFilters": [
{ "fieldPath": "state", "operator": "eq", "value": "Released" }
]
}

Response: See Execution Result above.

POST /api/reports/:id/export​

Execute a report and return the result as a CSV file download.

Request body (optional): Same as the execute endpoint. The default limit for exports is 1000 rows.

Response: 200 OK with Content-Type: text/csv and Content-Disposition: attachment; filename="report-<id>-<date>.csv".