diff --git a/apps/edr-freight-api/src/modules/reports/report-runner.service.ts b/apps/edr-freight-api/src/modules/reports/report-runner.service.ts index 5df9ba2e1..09933de42 100644 --- a/apps/edr-freight-api/src/modules/reports/report-runner.service.ts +++ b/apps/edr-freight-api/src/modules/reports/report-runner.service.ts @@ -48,6 +48,15 @@ function coerceParams( return params; } +/** + * Sort expression for a column with no explicit `sortExpr`: the SELECT alias + * TypeORM emitted for it, quoted. TypeORM always double-quotes `addSelect` + * aliases in the generated SQL (preserving case) — ordering by the bare, + * unquoted key instead lets Postgres fold it to lowercase and 42703 on any + * camelCase alias (e.g. "utilizationPct" -> unquoted "utilizationpct"). + */ +const aliasSortExpr = (key: string): string => `"${key.replace(/"/g, '""')}"`; + /** Resolve a client-requested sort column against the report's own whitelist. */ function resolveSort( def: ReportDefinition, @@ -57,14 +66,14 @@ function resolveSort( const dir = sortOrder?.toUpperCase() === 'DESC' ? 'DESC' : 'ASC'; const requested = sortBy && def.columns.find((c) => c.key === sortBy && c.sortable); if (requested) { - return { key: requested.key, expr: requested.sortExpr ?? requested.key, dir }; + return { key: requested.key, expr: requested.sortExpr ?? aliasSortExpr(requested.key), dir }; } if (!def.defaultSort) return null; const fallback = def.columns.find((c) => c.key === def.defaultSort!.key); if (!fallback) return null; return { key: fallback.key, - expr: fallback.sortExpr ?? fallback.key, + expr: fallback.sortExpr ?? aliasSortExpr(fallback.key), dir: def.defaultSort.dir, }; }