per-user trade-direction access scope

This commit is contained in:
Marshal
2026-08-02 22:29:58 +00:00
parent c055abe8c1
commit f4fd469643
47 changed files with 1451 additions and 107 deletions

View File

@@ -5,6 +5,8 @@ import {
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 { BookingView } from '../../common/booking-guards';
import { OverviewQueryDto } from './dto/overview-query.dto';
@@ -18,43 +20,79 @@ import {
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) {}
constructor(
private readonly overviewService: OverviewService,
private readonly userTradeAccessService: UserTradeAccessService,
) {}
@Get()
@BookingView()
@ApiOperation({ summary: 'Aggregated dashboard summary for backoffice overview' })
@ApiOkResponse({ type: OverviewResponseDto })
getDashboard(@Query() query: OverviewQueryDto): Promise<OverviewResponseDto> {
return this.overviewService.getDashboard(query.range ?? '30d');
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')
@BookingView()
@ApiOperation({ summary: 'Bookings tab metrics and charts' })
@ApiOkResponse({ type: OverviewBookingsTabDto })
getBookingsTab(@Query() query: OverviewQueryDto): Promise<OverviewBookingsTabDto> {
return this.overviewService.getBookingsTab(query.range ?? '30d');
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')
@BookingView()
@ApiOperation({ summary: 'Contracts tab metrics and charts' })
@ApiOkResponse({ type: OverviewContractsTabDto })
getContractsTab(@Query() query: OverviewQueryDto): Promise<OverviewContractsTabDto> {
return this.overviewService.getContractsTab(query.range ?? '30d');
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')
@BookingView()
@ApiOperation({ summary: 'Billing tab metrics and charts' })
@ApiOkResponse({ type: OverviewBillingTabDto })
getBillingTab(@Query() query: OverviewQueryDto): Promise<OverviewBillingTabDto> {
return this.overviewService.getBillingTab(query.range ?? '30d');
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')
@@ -69,8 +107,16 @@ export class OverviewController {
@BookingView()
@ApiOperation({ summary: 'Customers tab metrics and charts' })
@ApiOkResponse({ type: OverviewCustomersTabDto })
getCustomersTab(@Query() query: OverviewQueryDto): Promise<OverviewCustomersTabDto> {
return this.overviewService.getCustomersTab(query.range ?? '30d');
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')

View File

@@ -11,6 +11,7 @@ import { Contract } from "../contracts/entities/contract.entity";
import { PaymentEntity } from "../payment/entities/payment.entity";
import { Train } from "../trains/entities/train.entity";
import { Wagon } from "../wagons/entities/wagon.entity";
import { UserTradeAccessModule } from "../user-trade-access/user-trade-access.module";
import { OverviewController } from "./overview.controller";
import { OverviewRepository } from "./overview.repository";
import { OverviewService } from "./overview.service";
@@ -29,6 +30,7 @@ import { OverviewService } from "./overview.service";
Employee,
User,
]),
UserTradeAccessModule,
],
controllers: [OverviewController],
providers: [OverviewService, OverviewRepository],

View File

@@ -24,6 +24,10 @@ import {
OVERVIEW_URGENT_PRIORITY_THRESHOLD,
} from "./overview.constants";
import { Company } from "../companies/entities/company.entity";
import {
bookingRefScopeSql,
directionScopeSql,
} from "../user-trade-access/trade-scope.util";
/** Bookings carry a contract_kind column; GENERAL = umbrella contract row, not a shipment. */
const EXCLUDE_GENERAL_CONTRACT_BOOKINGS =
@@ -93,7 +97,8 @@ export class OverviewRepository {
private readonly userRepository: Repository<User>,
) { }
async getBookingKpis(): Promise<OverviewBookingKpisRow> {
async getBookingKpis(dirs?: string[]): Promise<OverviewBookingKpisRow> {
const scope = directionScopeSql("booking.trade_direction", dirs);
const row = await this.bookingRepository
.createQueryBuilder("booking")
.select(
@@ -118,6 +123,7 @@ export class OverviewRepository {
)
.where("booking.deleted_at IS NULL")
.andWhere(EXCLUDE_GENERAL_CONTRACT_BOOKINGS)
.andWhere(scope.sql, scope.params)
.setParameters({
closedStatuses: [...OVERVIEW_CLOSED_STATUSES],
needsActionStatuses: [...OVERVIEW_NEEDS_ACTION_STATUSES],
@@ -202,12 +208,13 @@ export class OverviewRepository {
};
}
async getBillingKpis(): Promise<{
async getBillingKpis(dirs?: string[]): Promise<{
revenueMtdEtb: number;
revenueMtdUsd: number;
pendingPayments: number;
successfulPaymentsMtd: number;
}> {
const scope = bookingRefScopeSql("payment.ref_id", dirs);
const revenueRow = await this.paymentRepository
.createQueryBuilder("payment")
.select(
@@ -223,6 +230,7 @@ export class OverviewRepository {
.andWhere(
`COALESCE(payment.paid_at, payment.created_at) >= date_trunc('month', CURRENT_DATE)`,
)
.andWhere(scope.sql, scope.params)
.getRawOne<Record<string, string>>();
const pendingPayments = await this.paymentRepository
@@ -230,6 +238,7 @@ export class OverviewRepository {
.where("payment.status IN (:...statuses)", {
statuses: ["action-required", "processing"],
})
.andWhere(scope.sql, scope.params)
.getCount();
return {
@@ -261,13 +270,16 @@ export class OverviewRepository {
async getBookingTrend(
days: number,
dirs?: string[],
): Promise<{ date: string; count: number }[]> {
const scope = directionScopeSql("booking.trade_direction", dirs);
const rows = await this.bookingRepository
.createQueryBuilder("booking")
.select(`to_char(booking.created_at::date, 'YYYY-MM-DD')`, "date")
.addSelect("COUNT(*)::int", "count")
.where("booking.deleted_at IS NULL")
.andWhere(EXCLUDE_GENERAL_CONTRACT_BOOKINGS)
.andWhere(scope.sql, scope.params)
.andWhere(`booking.created_at >= CURRENT_DATE - :days::int + 1`, { days })
.groupBy("booking.created_at::date")
.orderBy("booking.created_at::date", "ASC")
@@ -279,13 +291,15 @@ export class OverviewRepository {
}));
}
async getStatusCounts(): Promise<Record<string, number>> {
async getStatusCounts(dirs?: string[]): Promise<Record<string, number>> {
const scope = directionScopeSql("booking.trade_direction", dirs);
const rows = await this.bookingRepository
.createQueryBuilder("booking")
.select("booking.status", "status")
.addSelect("COUNT(*)::int", "count")
.where("booking.deleted_at IS NULL")
.andWhere(EXCLUDE_GENERAL_CONTRACT_BOOKINGS)
.andWhere(scope.sql, scope.params)
.groupBy("booking.status")
.getRawMany<{ status: string; count: string }>();
@@ -296,7 +310,9 @@ export class OverviewRepository {
async getPaymentTrend(
days: number,
dirs?: string[],
): Promise<{ date: string; amountEtb: number; amountUsd: number }[]> {
const scope = bookingRefScopeSql("payment.ref_id", dirs);
const rows = await this.paymentRepository
.createQueryBuilder("payment")
.select(
@@ -316,6 +332,7 @@ export class OverviewRepository {
`COALESCE(payment.paid_at, payment.created_at) >= CURRENT_DATE - :days::int + 1`,
{ days },
)
.andWhere(scope.sql, scope.params)
.groupBy(`COALESCE(payment.paid_at, payment.created_at)::date`)
.orderBy(`COALESCE(payment.paid_at, payment.created_at)::date`, "ASC")
.getRawMany<{ date: string; amountEtb: string; amountUsd: string }>();
@@ -327,7 +344,11 @@ export class OverviewRepository {
}));
}
async getRecentBookings(limit: number): Promise<OverviewRecentBookingRow[]> {
async getRecentBookings(
limit: number,
dirs?: string[],
): Promise<OverviewRecentBookingRow[]> {
const scope = directionScopeSql("booking.trade_direction", dirs);
const rows = await this.bookingRepository
.createQueryBuilder("booking")
.leftJoin("booking.company", "company")
@@ -341,6 +362,7 @@ export class OverviewRepository {
.addSelect("booking.created_at", "createdAt")
.where("booking.deleted_at IS NULL")
.andWhere(EXCLUDE_GENERAL_CONTRACT_BOOKINGS)
.andWhere(scope.sql, scope.params)
.orderBy("booking.created_at", "DESC")
.limit(limit)
.getRawMany<{
@@ -366,9 +388,10 @@ export class OverviewRepository {
}));
}
async getBookingsByFreightType(): Promise<
{ label: string; count: number }[]
> {
async getBookingsByFreightType(
dirs?: string[],
): Promise<{ label: string; count: number }[]> {
const scope = directionScopeSql("booking.trade_direction", dirs);
const rows = await this.bookingRepository
.createQueryBuilder("booking")
.select("booking.freight_type", "label")
@@ -376,6 +399,7 @@ export class OverviewRepository {
.where("booking.deleted_at IS NULL")
.andWhere(EXCLUDE_GENERAL_CONTRACT_BOOKINGS)
.andWhere("booking.status != 'DRAFT'")
.andWhere(scope.sql, scope.params)
.groupBy("booking.freight_type")
.orderBy("count", "DESC")
.getRawMany<{ label: string; count: string }>();
@@ -386,7 +410,10 @@ export class OverviewRepository {
}));
}
async getBookingsByCurrency(): Promise<{ label: string; count: number }[]> {
async getBookingsByCurrency(
dirs?: string[],
): Promise<{ label: string; count: number }[]> {
const scope = directionScopeSql("booking.trade_direction", dirs);
const rows = await this.bookingRepository
.createQueryBuilder("booking")
.select("booking.payment_currency", "label")
@@ -394,6 +421,7 @@ export class OverviewRepository {
.where("booking.deleted_at IS NULL")
.andWhere(EXCLUDE_GENERAL_CONTRACT_BOOKINGS)
.andWhere("booking.status != 'DRAFT'")
.andWhere(scope.sql, scope.params)
.groupBy("booking.payment_currency")
.orderBy("count", "DESC")
.getRawMany<{ label: string; count: string }>();
@@ -404,11 +432,15 @@ export class OverviewRepository {
}));
}
async getPaymentsByStatus(): Promise<{ status: string; count: number }[]> {
async getPaymentsByStatus(
dirs?: string[],
): Promise<{ status: string; count: number }[]> {
const scope = bookingRefScopeSql("payment.ref_id", dirs);
const rows = await this.paymentRepository
.createQueryBuilder("payment")
.select("payment.status", "status")
.addSelect("COUNT(*)::int", "count")
.where(scope.sql, scope.params)
.groupBy("payment.status")
.orderBy("count", "DESC")
.getRawMany<{ status: string; count: string }>();
@@ -419,9 +451,12 @@ export class OverviewRepository {
}));
}
async getPaymentsByMethod(): Promise<
async getPaymentsByMethod(
dirs?: string[],
): Promise<
{ method: string; count: number; amountEtb: number; amountUsd: number }[]
> {
const scope = bookingRefScopeSql("payment.ref_id", dirs);
const rows = await this.paymentRepository
.createQueryBuilder("payment")
.select("payment.method", "method")
@@ -434,6 +469,7 @@ export class OverviewRepository {
`COALESCE(SUM(payment.amount) FILTER (WHERE payment.currency = 'USD' AND payment.status = 'success'), 0)`,
"amountUsd",
)
.where(scope.sql, scope.params)
.groupBy("payment.method")
.orderBy("count", "DESC")
.getRawMany<{
@@ -451,9 +487,10 @@ export class OverviewRepository {
}));
}
async getRevenueByCurrency(): Promise<
{ currency: string; amount: number }[]
> {
async getRevenueByCurrency(
dirs?: string[],
): Promise<{ currency: string; amount: number }[]> {
const scope = bookingRefScopeSql("payment.ref_id", dirs);
const rows = await this.paymentRepository
.createQueryBuilder("payment")
.select("payment.currency", "currency")
@@ -462,6 +499,7 @@ export class OverviewRepository {
.andWhere(
`COALESCE(payment.paid_at, payment.created_at) >= date_trunc('month', CURRENT_DATE)`,
)
.andWhere(scope.sql, scope.params)
.groupBy("payment.currency")
.getRawMany<{ currency: string; amount: string }>();
@@ -556,7 +594,9 @@ export class OverviewRepository {
async getTopCustomersByBookings(
limit: number,
dirs?: string[],
): Promise<{ label: string; count: number }[]> {
const scope = directionScopeSql("booking.trade_direction", dirs);
const rows = await this.bookingRepository
.createQueryBuilder("booking")
.leftJoin("booking.company", "company")
@@ -564,6 +604,7 @@ export class OverviewRepository {
.addSelect("COUNT(*)::int", "count")
.where("booking.deleted_at IS NULL")
.andWhere("booking.status != 'DRAFT'")
.andWhere(scope.sql, scope.params)
.groupBy("company.name")
.orderBy("count", "DESC")
.limit(limit)
@@ -632,7 +673,8 @@ export class OverviewRepository {
// ── Contracts (overview Contract tab) ──────────────────────────────────────
async getContractKpis(): Promise<OverviewContractKpisRow> {
async getContractKpis(dirs?: string[]): Promise<OverviewContractKpisRow> {
const scope = directionScopeSql("contract.trade_direction", dirs);
const row = await this.contractRepository
.createQueryBuilder("contract")
.select(
@@ -656,6 +698,7 @@ export class OverviewRepository {
"createdToday",
)
.where("contract.deleted_at IS NULL")
.andWhere(scope.sql, scope.params)
.setParameters({
closedStatuses: [...OVERVIEW_CONTRACT_CLOSED_STATUSES],
needsActionStatuses: [...OVERVIEW_CONTRACT_NEEDS_ACTION_STATUSES],
@@ -673,24 +716,33 @@ export class OverviewRepository {
};
}
async getContractStatusCounts(): Promise<Record<string, number>> {
async getContractStatusCounts(
dirs?: string[],
): Promise<Record<string, number>> {
const scope = directionScopeSql("contract.trade_direction", dirs);
const rows = await this.contractRepository
.createQueryBuilder("contract")
.select("contract.status", "status")
.addSelect("COUNT(*)::int", "count")
.where("contract.deleted_at IS NULL")
.andWhere(scope.sql, scope.params)
.groupBy("contract.status")
.getRawMany<{ status: string; count: string }>();
return Object.fromEntries(rows.map((row) => [row.status, Number(row.count)]));
}
async getContractTrend(days: number): Promise<{ date: string; count: number }[]> {
async getContractTrend(
days: number,
dirs?: string[],
): Promise<{ date: string; count: number }[]> {
const scope = directionScopeSql("contract.trade_direction", dirs);
const rows = await this.contractRepository
.createQueryBuilder("contract")
.select(`to_char(contract.created_at::date, 'YYYY-MM-DD')`, "date")
.addSelect("COUNT(*)::int", "count")
.where("contract.deleted_at IS NULL")
.andWhere(scope.sql, scope.params)
.andWhere(`contract.created_at >= CURRENT_DATE - :days::int + 1`, { days })
.groupBy("contract.created_at::date")
.orderBy("contract.created_at::date", "ASC")
@@ -699,13 +751,17 @@ export class OverviewRepository {
return rows.map((row) => ({ date: row.date, count: Number(row.count) }));
}
async getContractsByKind(): Promise<{ label: string; count: number }[]> {
async getContractsByKind(
dirs?: string[],
): Promise<{ label: string; count: number }[]> {
const scope = directionScopeSql("contract.trade_direction", dirs);
const rows = await this.contractRepository
.createQueryBuilder("contract")
.select("contract.contract_kind", "label")
.addSelect("COUNT(*)::int", "count")
.where("contract.deleted_at IS NULL")
.andWhere("contract.status != 'DRAFT'")
.andWhere(scope.sql, scope.params)
.groupBy("contract.contract_kind")
.orderBy("count", "DESC")
.getRawMany<{ label: string; count: string }>();
@@ -713,13 +769,17 @@ export class OverviewRepository {
return rows.map((row) => ({ label: row.label, count: Number(row.count) }));
}
async getContractsByFreightType(): Promise<{ label: string; count: number }[]> {
async getContractsByFreightType(
dirs?: string[],
): Promise<{ label: string; count: number }[]> {
const scope = directionScopeSql("contract.trade_direction", dirs);
const rows = await this.contractRepository
.createQueryBuilder("contract")
.select("contract.freight_type", "label")
.addSelect("COUNT(*)::int", "count")
.where("contract.deleted_at IS NULL")
.andWhere("contract.status != 'DRAFT'")
.andWhere(scope.sql, scope.params)
.groupBy("contract.freight_type")
.orderBy("count", "DESC")
.getRawMany<{ label: string; count: string }>();
@@ -727,7 +787,11 @@ export class OverviewRepository {
return rows.map((row) => ({ label: row.label, count: Number(row.count) }));
}
async getRecentContracts(limit: number): Promise<OverviewRecentContractRow[]> {
async getRecentContracts(
limit: number,
dirs?: string[],
): Promise<OverviewRecentContractRow[]> {
const scope = directionScopeSql("contract.trade_direction", dirs);
const rows = await this.contractRepository
.createQueryBuilder("contract")
.leftJoin("contract.company", "company")
@@ -741,6 +805,7 @@ export class OverviewRepository {
.addSelect("contract.contract_valid_until", "validUntil")
.addSelect("contract.created_at", "createdAt")
.where("contract.deleted_at IS NULL")
.andWhere(scope.sql, scope.params)
.orderBy("contract.created_at", "DESC")
.limit(limit)
.getRawMany<{

View File

@@ -40,7 +40,10 @@ export class OverviewService {
return { bookingsByPipeline, bookingsByStatus };
}
async getDashboard(range: OverviewRangeQuery = '30d'): Promise<OverviewResponseDto> {
async getDashboard(
range: OverviewRangeQuery = '30d',
dirs?: string[],
): Promise<OverviewResponseDto> {
const days = OVERVIEW_RANGE_DAYS[range];
const [
@@ -55,16 +58,16 @@ export class OverviewService {
paymentTrend,
recentBookings,
] = await Promise.all([
this.overviewRepository.getBookingKpis(),
this.overviewRepository.getContractKpis(),
this.overviewRepository.getBookingKpis(dirs),
this.overviewRepository.getContractKpis(dirs),
this.overviewRepository.getOperationsKpis(),
this.overviewRepository.getCustomerKpis(),
this.overviewRepository.getBillingKpis(),
this.overviewRepository.getBillingKpis(dirs),
this.overviewRepository.getStaffKpis(),
this.overviewRepository.getBookingTrend(days),
this.overviewRepository.getStatusCounts(),
this.overviewRepository.getPaymentTrend(days),
this.overviewRepository.getRecentBookings(8),
this.overviewRepository.getBookingTrend(days, dirs),
this.overviewRepository.getStatusCounts(dirs),
this.overviewRepository.getPaymentTrend(days, dirs),
this.overviewRepository.getRecentBookings(8, dirs),
]);
const { bookingsByPipeline, bookingsByStatus } =
@@ -91,7 +94,10 @@ export class OverviewService {
};
}
async getBookingsTab(range: OverviewRangeQuery = '30d'): Promise<OverviewBookingsTabDto> {
async getBookingsTab(
range: OverviewRangeQuery = '30d',
dirs?: string[],
): Promise<OverviewBookingsTabDto> {
const days = OVERVIEW_RANGE_DAYS[range];
const [
@@ -102,12 +108,12 @@ export class OverviewService {
bookingsByCurrency,
recentBookings,
] = await Promise.all([
this.overviewRepository.getBookingKpis(),
this.overviewRepository.getBookingTrend(days),
this.overviewRepository.getStatusCounts(),
this.overviewRepository.getBookingsByFreightType(),
this.overviewRepository.getBookingsByCurrency(),
this.overviewRepository.getRecentBookings(8),
this.overviewRepository.getBookingKpis(dirs),
this.overviewRepository.getBookingTrend(days, dirs),
this.overviewRepository.getStatusCounts(dirs),
this.overviewRepository.getBookingsByFreightType(dirs),
this.overviewRepository.getBookingsByCurrency(dirs),
this.overviewRepository.getRecentBookings(8, dirs),
]);
const { bookingsByPipeline, bookingsByStatus } =
@@ -130,6 +136,7 @@ export class OverviewService {
async getContractsTab(
range: OverviewRangeQuery = '30d',
dirs?: string[],
): Promise<OverviewContractsTabDto> {
const days = OVERVIEW_RANGE_DAYS[range];
@@ -141,12 +148,12 @@ export class OverviewService {
contractsByFreightType,
recentContracts,
] = await Promise.all([
this.overviewRepository.getContractKpis(),
this.overviewRepository.getContractTrend(days),
this.overviewRepository.getContractStatusCounts(),
this.overviewRepository.getContractsByKind(),
this.overviewRepository.getContractsByFreightType(),
this.overviewRepository.getRecentContracts(8),
this.overviewRepository.getContractKpis(dirs),
this.overviewRepository.getContractTrend(days, dirs),
this.overviewRepository.getContractStatusCounts(dirs),
this.overviewRepository.getContractsByKind(dirs),
this.overviewRepository.getContractsByFreightType(dirs),
this.overviewRepository.getRecentContracts(8, dirs),
]);
const contractsByStatus = Object.entries(statusCounts)
@@ -170,16 +177,19 @@ export class OverviewService {
};
}
async getBillingTab(range: OverviewRangeQuery = '30d'): Promise<OverviewBillingTabDto> {
async getBillingTab(
range: OverviewRangeQuery = '30d',
dirs?: string[],
): Promise<OverviewBillingTabDto> {
const days = OVERVIEW_RANGE_DAYS[range];
const [kpis, paymentTrend, paymentsByStatus, paymentsByMethod, revenueByCurrency] =
await Promise.all([
this.overviewRepository.getBillingKpis(),
this.overviewRepository.getPaymentTrend(days),
this.overviewRepository.getPaymentsByStatus(),
this.overviewRepository.getPaymentsByMethod(),
this.overviewRepository.getRevenueByCurrency(),
this.overviewRepository.getBillingKpis(dirs),
this.overviewRepository.getPaymentTrend(days, dirs),
this.overviewRepository.getPaymentsByStatus(dirs),
this.overviewRepository.getPaymentsByMethod(dirs),
this.overviewRepository.getRevenueByCurrency(dirs),
]);
return {
@@ -217,7 +227,10 @@ export class OverviewService {
};
}
async getCustomersTab(range: OverviewRangeQuery = '30d'): Promise<OverviewCustomersTabDto> {
async getCustomersTab(
range: OverviewRangeQuery = '30d',
dirs?: string[],
): Promise<OverviewCustomersTabDto> {
const days = OVERVIEW_RANGE_DAYS[range];
const [kpis, customerGrowthTrend, customersByType, topCustomersByBookings] =
@@ -225,7 +238,7 @@ export class OverviewService {
this.overviewRepository.getCustomerKpis(),
this.overviewRepository.getCustomerGrowthTrend(days),
this.overviewRepository.getCustomersByType(),
this.overviewRepository.getTopCustomersByBookings(8),
this.overviewRepository.getTopCustomersByBookings(8, dirs),
]);
return {