mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
New catalog endpoint, same shape as GET /reports: returns the overview layouts (key + label) the caller holds the matching edr_freight_app:overview:<layout>:view permission for, in priority order. Backend enforcement to go with the permission-based frontend resolver (next commit) — a caller can no longer land on a layout their JWT doesn't actually carry the permission for.
173 lines
6.0 KiB
TypeScript
173 lines
6.0 KiB
TypeScript
import { Controller, Get, Query } from '@nestjs/common';
|
|
import {
|
|
ApiBearerAuth,
|
|
ApiOkResponse,
|
|
ApiOperation,
|
|
ApiTags,
|
|
} from '@nestjs/swagger';
|
|
import { CurrentUser } from '@edr/api-common';
|
|
import type { TCurrentUser } from '@tria-plc/api-common/modules/auth/types/current-user.type';
|
|
|
|
import { BookingStaff } from '../../common/booking-guards';
|
|
import { hasFreightPermission } from '../../common/freight-permission.util';
|
|
import {
|
|
FREIGHT_PERMS,
|
|
OVERVIEW_LAYOUT_KEYS,
|
|
OVERVIEW_LAYOUT_LABELS,
|
|
} from '../../seed/freight-permissions.registry';
|
|
import { OverviewLayoutDto } from './dto/overview-layout.dto';
|
|
import { OverviewQueryDto } from './dto/overview-query.dto';
|
|
import { OverviewResponseDto } from './dto/overview-response.dto';
|
|
import {
|
|
OverviewBillingTabDto,
|
|
OverviewBookingsTabDto,
|
|
OverviewClearanceTabDto,
|
|
OverviewContractsTabDto,
|
|
OverviewCustomersTabDto,
|
|
OverviewFleetTabDto,
|
|
OverviewOperationsTabDto,
|
|
OverviewStaffTabDto,
|
|
} from './dto/overview-tab-response.dto';
|
|
import { OverviewService } from './overview.service';
|
|
import { UserTradeAccessService } from '../user-trade-access/user-trade-access.service';
|
|
|
|
@ApiTags('Overview')
|
|
@ApiBearerAuth()
|
|
@Controller('overview')
|
|
export class OverviewController {
|
|
constructor(
|
|
private readonly overviewService: OverviewService,
|
|
private readonly userTradeAccessService: UserTradeAccessService,
|
|
) {}
|
|
|
|
/**
|
|
* Layouts the caller has permission to render, in priority order — exactly
|
|
* the same "server filters by permission, frontend just renders what comes
|
|
* back" shape as GET /reports. A caller lands on exactly one layout, so the
|
|
* frontend picks the first entry here rather than rendering the whole list.
|
|
*/
|
|
@Get('layouts')
|
|
@BookingStaff(FREIGHT_PERMS.overview.view)
|
|
@ApiOperation({ summary: 'Overview dashboard layouts the caller has permission to render' })
|
|
@ApiOkResponse({ type: OverviewLayoutDto, isArray: true })
|
|
getLayouts(@CurrentUser() user: TCurrentUser): OverviewLayoutDto[] {
|
|
return OVERVIEW_LAYOUT_KEYS.filter((key) =>
|
|
hasFreightPermission(user, FREIGHT_PERMS.overview.layout(key)),
|
|
).map((key) => ({ key, label: OVERVIEW_LAYOUT_LABELS[key] }));
|
|
}
|
|
|
|
@Get()
|
|
@BookingStaff(FREIGHT_PERMS.overview.view)
|
|
@ApiOperation({ summary: 'Aggregated dashboard summary for backoffice overview' })
|
|
@ApiOkResponse({ type: OverviewResponseDto })
|
|
async getDashboard(
|
|
@Query() query: OverviewQueryDto,
|
|
@CurrentUser() user: TCurrentUser,
|
|
): Promise<OverviewResponseDto> {
|
|
const allowed =
|
|
await this.userTradeAccessService.resolveAllowedDirections(user);
|
|
return this.overviewService.getDashboard(
|
|
query.range ?? '30d',
|
|
allowed ?? undefined,
|
|
);
|
|
}
|
|
|
|
@Get('bookings')
|
|
@BookingStaff(FREIGHT_PERMS.overview.view)
|
|
@ApiOperation({ summary: 'Bookings tab metrics and charts' })
|
|
@ApiOkResponse({ type: OverviewBookingsTabDto })
|
|
async getBookingsTab(
|
|
@Query() query: OverviewQueryDto,
|
|
@CurrentUser() user: TCurrentUser,
|
|
): Promise<OverviewBookingsTabDto> {
|
|
const allowed =
|
|
await this.userTradeAccessService.resolveAllowedDirections(user);
|
|
return this.overviewService.getBookingsTab(
|
|
query.range ?? '30d',
|
|
allowed ?? undefined,
|
|
);
|
|
}
|
|
|
|
@Get('contracts')
|
|
@BookingStaff(FREIGHT_PERMS.overview.view)
|
|
@ApiOperation({ summary: 'Contracts tab metrics and charts' })
|
|
@ApiOkResponse({ type: OverviewContractsTabDto })
|
|
async getContractsTab(
|
|
@Query() query: OverviewQueryDto,
|
|
@CurrentUser() user: TCurrentUser,
|
|
): Promise<OverviewContractsTabDto> {
|
|
const allowed =
|
|
await this.userTradeAccessService.resolveAllowedDirections(user);
|
|
return this.overviewService.getContractsTab(
|
|
query.range ?? '30d',
|
|
allowed ?? undefined,
|
|
);
|
|
}
|
|
|
|
@Get('billing')
|
|
@BookingStaff(FREIGHT_PERMS.overview.view)
|
|
@ApiOperation({ summary: 'Billing tab metrics and charts' })
|
|
@ApiOkResponse({ type: OverviewBillingTabDto })
|
|
async getBillingTab(
|
|
@Query() query: OverviewQueryDto,
|
|
@CurrentUser() user: TCurrentUser,
|
|
): Promise<OverviewBillingTabDto> {
|
|
const allowed =
|
|
await this.userTradeAccessService.resolveAllowedDirections(user);
|
|
return this.overviewService.getBillingTab(
|
|
query.range ?? '30d',
|
|
allowed ?? undefined,
|
|
);
|
|
}
|
|
|
|
@Get('operations')
|
|
@BookingStaff(FREIGHT_PERMS.overview.view)
|
|
@ApiOperation({ summary: 'Operations tab metrics and charts' })
|
|
@ApiOkResponse({ type: OverviewOperationsTabDto })
|
|
getOperationsTab(
|
|
@Query() query: OverviewQueryDto,
|
|
): Promise<OverviewOperationsTabDto> {
|
|
return this.overviewService.getOperationsTab(query.range ?? '30d');
|
|
}
|
|
|
|
@Get('fleet')
|
|
@BookingStaff(FREIGHT_PERMS.overview.view)
|
|
@ApiOperation({ summary: 'Wagon and locomotive fleet detail, plus turnaround' })
|
|
@ApiOkResponse({ type: OverviewFleetTabDto })
|
|
getFleetTab(@Query() query: OverviewQueryDto): Promise<OverviewFleetTabDto> {
|
|
return this.overviewService.getFleetTab(query.range ?? '30d');
|
|
}
|
|
|
|
@Get('clearance')
|
|
@BookingStaff(FREIGHT_PERMS.overview.view)
|
|
@ApiOperation({ summary: 'Document review and invoice queues' })
|
|
@ApiOkResponse({ type: OverviewClearanceTabDto })
|
|
getClearanceTab(): Promise<OverviewClearanceTabDto> {
|
|
return this.overviewService.getClearanceTab();
|
|
}
|
|
|
|
@Get('customers')
|
|
@BookingStaff(FREIGHT_PERMS.overview.view)
|
|
@ApiOperation({ summary: 'Customers tab metrics and charts' })
|
|
@ApiOkResponse({ type: OverviewCustomersTabDto })
|
|
async getCustomersTab(
|
|
@Query() query: OverviewQueryDto,
|
|
@CurrentUser() user: TCurrentUser,
|
|
): Promise<OverviewCustomersTabDto> {
|
|
const allowed =
|
|
await this.userTradeAccessService.resolveAllowedDirections(user);
|
|
return this.overviewService.getCustomersTab(
|
|
query.range ?? '30d',
|
|
allowed ?? undefined,
|
|
);
|
|
}
|
|
|
|
@Get('staff')
|
|
@BookingStaff(FREIGHT_PERMS.overview.view)
|
|
@ApiOperation({ summary: 'Staff tab metrics and charts' })
|
|
@ApiOkResponse({ type: OverviewStaffTabDto })
|
|
getStaffTab(@Query() query: OverviewQueryDto): Promise<OverviewStaffTabDto> {
|
|
return this.overviewService.getStaffTab(query.range ?? '30d');
|
|
}
|
|
}
|