enhance contract and booking services with server-side search and validation improvements

- Added  parameter to  and  for server-side free-text search on contract reference, company name, and booking details.
- Introduced new validation errors in  for container clashes and space issues when creating bookings.
- Implemented paginated dropdown settings retrieval in .
- Updated  to fetch active yards using a new method that handles pagination.
- Enhanced  with a  method to fetch all records by walking through pages.
- Refactored  to support filtering and pagination in schedule listings.
- Improved  to return a paginated list of facilities.
- Updated UI components in  and  to utilize debounced search inputs for better performance.
- Added alerts in  to inform users about booking constraints related to splits and capacity.
- Enhanced  to display notifications for split bookings and capacity usage.
This commit is contained in:
Marshal
2026-07-12 10:51:31 +00:00
parent 6695c5448e
commit 4b7f6d2548
108 changed files with 3187 additions and 1368 deletions

View File

@@ -5,6 +5,7 @@ import {
import { RuleEngineManage, RuleEngineView } from '../../../common/rule-engine-guards';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { CreateApprovalRuleDto } from '../dto/create-approval-rule.dto';
import { ListApprovalRulesQueryDto } from '../dto/list-rule-engine-query.dto';
import { MoveOrderDto } from '../dto/move-order.dto';
import { ReorderItemsDto } from '../dto/reorder-items.dto';
import { UpdateApprovalRuleDto } from '../dto/update-approval-rule.dto';
@@ -19,15 +20,8 @@ export class ApprovalRulesController {
@Get()
@RuleEngineView('approval-rules')
@ApiOperation({ summary: 'List approval rules' })
findAll(@Query() query: Record<string, string>) {
return this.service.findAll({
requiresDirectorApproval:
query['requiresDirectorApproval'] !== undefined
? query['requiresDirectorApproval'] === 'true'
: undefined,
page: query['page'] ? parseInt(query['page'], 10) : undefined,
pageSize: query['pageSize'] ? parseInt(query['pageSize'], 10) : undefined,
});
findAll(@Query() query: ListApprovalRulesQueryDto) {
return this.service.findAll(query);
}
@Get('chain')

View File

@@ -5,6 +5,7 @@ import {
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { RuleEngineManage, RuleEngineView } from '../../../common/rule-engine-guards';
import { CreateCargoTypeDto } from '../dto/create-cargo-type.dto';
import { ListCargoTypesQueryDto } from '../dto/list-rule-engine-query.dto';
import { MoveOrderDto } from '../dto/move-order.dto';
import { ReorderItemsDto } from '../dto/reorder-items.dto';
import { UpdateCargoTypeDto } from '../dto/update-cargo-type.dto';
@@ -19,19 +20,8 @@ export class CargoTypesController {
@Get()
@RuleEngineView('cargo-types')
@ApiOperation({ summary: 'List cargo types' })
findAll(@Query() query: Record<string, string>) {
return this.service.findAll({
isActive: query['isActive'] !== undefined ? query['isActive'] === 'true' : undefined,
requiresDirectorApproval: query['requiresDirectorApproval'] !== undefined
? query['requiresDirectorApproval'] === 'true'
: undefined,
parentGroupId: query['parentGroupId'],
search: query['search'],
page: query['page'] ? parseInt(query['page'], 10) : undefined,
pageSize: query['pageSize'] ? parseInt(query['pageSize'], 10) : undefined,
sortBy: query['sortBy'],
sortOrder: (query['sortOrder'] as 'ASC' | 'DESC') ?? 'ASC',
});
findAll(@Query() query: ListCargoTypesQueryDto) {
return this.service.findAll(query);
}
@Post('reorder')

View File

@@ -5,6 +5,7 @@ import {
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { RuleEngineManage, RuleEngineView } from '../../../common/rule-engine-guards';
import { CreateContainerTypeDto } from '../dto/create-container-type.dto';
import { ListContainerTypesQueryDto } from '../dto/list-rule-engine-query.dto';
import { MoveOrderDto } from '../dto/move-order.dto';
import { ReorderItemsDto } from '../dto/reorder-items.dto';
import { UpdateContainerTypeDto } from '../dto/update-container-type.dto';
@@ -19,12 +20,8 @@ export class ContainerTypesController {
@Get()
@RuleEngineView('container-types')
@ApiOperation({ summary: 'List container types' })
findAll(@Query() query: Record<string, string>) {
return this.service.findAll({
isActive: query['isActive'] !== undefined ? query['isActive'] === 'true' : undefined,
page: query['page'] ? parseInt(query['page'], 10) : undefined,
pageSize: query['pageSize'] ? parseInt(query['pageSize'], 10) : undefined,
});
findAll(@Query() query: ListContainerTypesQueryDto) {
return this.service.findAll(query);
}
@Post('reorder')

View File

@@ -5,6 +5,7 @@ import {
import { RuleEngineManage, RuleEngineView } from '../../../common/rule-engine-guards';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { CreatePriorityConfigDto } from '../dto/create-priority-config.dto';
import { ListPriorityConfigsQueryDto } from '../dto/list-rule-engine-query.dto';
import { UpdatePriorityConfigDto } from '../dto/update-priority-config.dto';
import { MoveOrderDto } from '../dto/move-order.dto';
import { ReorderItemsDto } from '../dto/reorder-items.dto';
@@ -19,13 +20,8 @@ export class PriorityConfigsController {
@Get()
@RuleEngineView('priority-configs')
@ApiOperation({ summary: 'List priority configs' })
findAll(@Query() query: Record<string, string>) {
return this.service.findAll({
type: (query['type'] as 'WAGON' | 'CURRENCY' | 'CUSTOMS') || undefined,
isActive: query['isActive'] !== undefined ? query['isActive'] === 'true' : undefined,
page: query['page'] ? parseInt(query['page'], 10) : undefined,
pageSize: query['pageSize'] ? parseInt(query['pageSize'], 10) : undefined,
});
findAll(@Query() query: ListPriorityConfigsQueryDto) {
return this.service.findAll(query);
}
@Get(':id')

View File

@@ -6,6 +6,7 @@ import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { CurrentUser } from '@edr/api-common';
import { RuleEngineManage, RuleEngineView } from '../../../common/rule-engine-guards';
import { CreateRateDto } from '../dto/create-rate.dto';
import { ListRatesQueryDto } from '../dto/list-rule-engine-query.dto';
import {
type AuthUserPayload,
resolveAuthUserId,
@@ -22,13 +23,8 @@ export class RatesController {
@Get()
@RuleEngineView('rates')
@ApiOperation({ summary: 'List rates' })
findAll(@Query() query: Record<string, string>) {
return this.service.findAll({
status: query['status'],
rateType: query['rateType'],
page: query['page'] ? parseInt(query['page'], 10) : undefined,
pageSize: query['pageSize'] ? parseInt(query['pageSize'], 10) : undefined,
});
findAll(@Query() query: ListRatesQueryDto) {
return this.service.findAll(query);
}
@Get('live')

View File

@@ -5,6 +5,7 @@ import {
import { RuleEngineManage, RuleEngineView } from '../../../common/rule-engine-guards';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { CreateServiceTypeDto } from '../dto/create-service-type.dto';
import { ListServiceTypesQueryDto } from '../dto/list-rule-engine-query.dto';
import { MoveOrderDto } from '../dto/move-order.dto';
import { ReorderItemsDto } from '../dto/reorder-items.dto';
import { UpdateServiceTypeDto } from '../dto/update-service-type.dto';
@@ -19,16 +20,8 @@ export class ServiceTypesController {
@Get()
@RuleEngineView('service-types')
@ApiOperation({ summary: 'List service types' })
findAll(@Query() query: Record<string, string>) {
return this.service.findAll({
isActive: query['isActive'] !== undefined ? query['isActive'] === 'true' : undefined,
canBeBookedAlone: query['canBeBookedAlone'] !== undefined ? query['canBeBookedAlone'] === 'true' : undefined,
search: query['search'],
page: query['page'] ? parseInt(query['page'], 10) : undefined,
pageSize: query['pageSize'] ? parseInt(query['pageSize'], 10) : undefined,
sortBy: query['sortBy'],
sortOrder: (query['sortOrder'] as 'ASC' | 'DESC') ?? 'ASC',
});
findAll(@Query() query: ListServiceTypesQueryDto) {
return this.service.findAll(query);
}
@Post('reorder')

View File

@@ -5,6 +5,7 @@ import {
import { RuleEngineManage, RuleEngineView } from '../../../common/rule-engine-guards';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { CreateShippingLineDto } from '../dto/create-shipping-line.dto';
import { ListRuleEngineQueryDto } from '../dto/list-rule-engine-query.dto';
import { UpdateShippingLineDto } from '../dto/update-shipping-line.dto';
import { ShippingLinesService } from '../services/shipping-lines.service';
@@ -17,12 +18,8 @@ export class ShippingLinesController {
@Get()
@RuleEngineView('shipping-lines')
@ApiOperation({ summary: 'List shipping lines' })
findAll(@Query() query: Record<string, string>) {
return this.service.findAll({
isActive: query['isActive'] !== undefined ? query['isActive'] === 'true' : undefined,
page: query['page'] ? parseInt(query['page'], 10) : undefined,
pageSize: query['pageSize'] ? parseInt(query['pageSize'], 10) : undefined,
});
findAll(@Query() query: ListRuleEngineQueryDto) {
return this.service.findAll(query);
}
@Get(':id')

View File

@@ -5,6 +5,7 @@ import {
import { RuleEngineManage, RuleEngineView } from '../../../common/rule-engine-guards';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { CreateWeightLimitRuleDto } from '../dto/create-weight-limit-rule.dto';
import { ListWeightLimitRulesQueryDto } from '../dto/list-rule-engine-query.dto';
import { UpdateWeightLimitRuleDto } from '../dto/update-weight-limit-rule.dto';
import { WeightLimitRulesService } from '../services/weight-limit-rules.service';
@@ -17,13 +18,8 @@ export class WeightLimitRulesController {
@Get()
@RuleEngineView('weight-limit-rules')
@ApiOperation({ summary: 'List weight limit rules' })
findAll(@Query() query: Record<string, string>) {
return this.service.findAll({
tradeDirection: query['tradeDirection'],
containerTypeId: query['containerTypeId'],
page: query['page'] ? parseInt(query['page'], 10) : undefined,
pageSize: query['pageSize'] ? parseInt(query['pageSize'], 10) : undefined,
});
findAll(@Query() query: ListWeightLimitRulesQueryDto) {
return this.service.findAll(query);
}
@Get(':id')

View File

@@ -5,6 +5,7 @@ import {
import { RuleEngineManage, RuleEngineView } from '../../../common/rule-engine-guards';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { CreateYardDto } from '../dto/create-yard.dto';
import { ListYardsQueryDto } from '../dto/list-rule-engine-query.dto';
import { MoveOrderDto } from '../dto/move-order.dto';
import { ReorderItemsDto } from '../dto/reorder-items.dto';
import { UpdateYardDto } from '../dto/update-yard.dto';
@@ -19,13 +20,8 @@ export class YardsController {
@Get()
@RuleEngineView('yards')
@ApiOperation({ summary: 'List yards' })
findAll(@Query() query: Record<string, string>) {
return this.service.findAll({
isActive: query['isActive'] !== undefined ? query['isActive'] === 'true' : undefined,
country: query['country'],
page: query['page'] ? parseInt(query['page'], 10) : undefined,
pageSize: query['pageSize'] ? parseInt(query['pageSize'], 10) : undefined,
});
findAll(@Query() query: ListYardsQueryDto) {
return this.service.findAll(query);
}
@Post('reorder')

View File

@@ -0,0 +1,129 @@
import { ApiPropertyOptional } from '@nestjs/swagger';
import { Transform, TransformFnParams } from 'class-transformer';
import { IsBoolean, IsIn, IsOptional, IsString, IsUUID, MaxLength } from 'class-validator';
import { PaginationQueryDto } from '../../../common/dto/pagination-query.dto';
/**
* Query-string booleans arrive as strings; implicit conversion is disabled
* app-wide, so coerce explicitly. Mirrors the previous controller behaviour
* (`query['flag'] === 'true'`): only the literal "true" is truthy.
*/
const toOptionalBoolean = ({ value }: TransformFnParams): boolean | undefined =>
value === undefined || value === null || value === '' ? undefined : value === true || value === 'true';
/**
* Shared list query for rule-engine resources. Every rule-engine list endpoint
* returns the standard `PaginatedResponse` envelope (`items` + `meta`) built by
* `common/utils/pagination.util.ts`; `search` is applied server-side against
* each resource's human-readable columns (see the repository `findPaged`).
*/
export class ListRuleEngineQueryDto extends PaginationQueryDto {
@ApiPropertyOptional({ description: 'Filter by active flag.' })
@IsOptional()
@Transform(toOptionalBoolean)
@IsBoolean()
isActive?: boolean;
}
export class ListCargoTypesQueryDto extends ListRuleEngineQueryDto {
@ApiPropertyOptional({ description: 'Filter by director-approval requirement.' })
@IsOptional()
@Transform(toOptionalBoolean)
@IsBoolean()
requiresDirectorApproval?: boolean;
@ApiPropertyOptional({ description: 'Filter by parent cargo-type group.' })
@IsOptional()
@IsUUID()
parentGroupId?: string;
@ApiPropertyOptional({ enum: ['displayOrder', 'cargoTypeName', 'code', 'createdAt'], default: 'displayOrder' })
@IsOptional()
@IsIn(['displayOrder', 'cargoTypeName', 'code', 'createdAt'])
sortBy?: string;
}
export class ListContainerTypesQueryDto extends ListRuleEngineQueryDto {
@ApiPropertyOptional({ enum: ['displayOrder'], default: 'displayOrder' })
@IsOptional()
@IsIn(['displayOrder'])
sortBy?: string;
}
export class ListPriorityConfigsQueryDto extends ListRuleEngineQueryDto {
@ApiPropertyOptional({ enum: ['WAGON', 'CURRENCY', 'CUSTOMS'] })
@IsOptional()
@IsIn(['WAGON', 'CURRENCY', 'CUSTOMS'])
type?: 'WAGON' | 'CURRENCY' | 'CUSTOMS';
@ApiPropertyOptional({ enum: ['displayOrder'], default: 'displayOrder' })
@IsOptional()
@IsIn(['displayOrder'])
sortBy?: string;
}
export class ListServiceTypesQueryDto extends ListRuleEngineQueryDto {
@ApiPropertyOptional({ description: 'Filter by standalone-bookable flag.' })
@IsOptional()
@Transform(toOptionalBoolean)
@IsBoolean()
canBeBookedAlone?: boolean;
@ApiPropertyOptional({ enum: ['displayOrder', 'serviceName', 'code', 'createdAt'], default: 'displayOrder' })
@IsOptional()
@IsIn(['displayOrder', 'serviceName', 'code', 'createdAt'])
sortBy?: string;
}
export class ListYardsQueryDto extends ListRuleEngineQueryDto {
@ApiPropertyOptional({ description: 'Filter by yard country.' })
@IsOptional()
@IsString()
@MaxLength(50)
country?: string;
@ApiPropertyOptional({ enum: ['displayOrder'], default: 'displayOrder' })
@IsOptional()
@IsIn(['displayOrder'])
sortBy?: string;
}
export class ListApprovalRulesQueryDto extends PaginationQueryDto {
@ApiPropertyOptional({ description: 'Filter by approval chain (director vs standard).' })
@IsOptional()
@Transform(toOptionalBoolean)
@IsBoolean()
requiresDirectorApproval?: boolean;
@ApiPropertyOptional({ enum: ['stepOrder'], default: 'stepOrder' })
@IsOptional()
@IsIn(['stepOrder'])
sortBy?: string;
}
export class ListRatesQueryDto extends PaginationQueryDto {
@ApiPropertyOptional({ description: 'Filter by rate status (DRAFT, PENDING_APPROVAL, LIVE...).' })
@IsOptional()
@IsString()
@MaxLength(20)
status?: string;
@ApiPropertyOptional({ description: 'Filter by derived rate type.' })
@IsOptional()
@IsString()
@MaxLength(50)
rateType?: string;
}
export class ListWeightLimitRulesQueryDto extends PaginationQueryDto {
@ApiPropertyOptional({ description: 'Filter by container type.' })
@IsOptional()
@IsUUID()
containerTypeId?: string;
@ApiPropertyOptional({ description: 'Filter by trade direction (IMPORT/EXPORT/BOTH).' })
@IsOptional()
@IsString()
@MaxLength(10)
tradeDirection?: string;
}

View File

@@ -1,4 +1,6 @@
import { PaginatedResponse } from '@edr/types';
import { FindManyOptions } from 'typeorm';
import { ListApprovalRulesQueryDto } from '../dto/list-rule-engine-query.dto';
import { ApprovalRule } from '../entities/approval-rule.entity';
export interface IApprovalRulesRepository {
@@ -6,6 +8,7 @@ export interface IApprovalRulesRepository {
findChainForCargo(requiresDirectorApproval: boolean): Promise<ApprovalRule[]>;
findAll(options?: FindManyOptions<ApprovalRule>): Promise<ApprovalRule[]>;
findAndCount(options?: FindManyOptions<ApprovalRule>): Promise<[ApprovalRule[], number]>;
findPaged(query: ListApprovalRulesQueryDto): Promise<PaginatedResponse<ApprovalRule>>;
create(data: Partial<ApprovalRule>): Promise<ApprovalRule>;
update(id: string, data: Partial<ApprovalRule>): Promise<ApprovalRule | null>;
softDelete(id: string): Promise<void>;

View File

@@ -1,4 +1,6 @@
import { PaginatedResponse } from '@edr/types';
import { FindManyOptions } from 'typeorm';
import { ListCargoTypesQueryDto } from '../dto/list-rule-engine-query.dto';
import { CargoType } from '../entities/cargo-type.entity';
export interface ICargoTypesRepository {
@@ -6,6 +8,7 @@ export interface ICargoTypesRepository {
findByCode(code: string): Promise<CargoType | null>;
findAll(options?: FindManyOptions<CargoType>): Promise<CargoType[]>;
findAndCount(options?: FindManyOptions<CargoType>): Promise<[CargoType[], number]>;
findPaged(query: ListCargoTypesQueryDto): Promise<PaginatedResponse<CargoType>>;
create(data: Partial<CargoType>): Promise<CargoType>;
update(id: string, data: Partial<CargoType>): Promise<CargoType | null>;
softDelete(id: string): Promise<void>;

View File

@@ -1,4 +1,6 @@
import { PaginatedResponse } from '@edr/types';
import { FindManyOptions } from 'typeorm';
import { ListContainerTypesQueryDto } from '../dto/list-rule-engine-query.dto';
import { ContainerType } from '../entities/container-type.entity';
export interface IContainerTypesRepository {
@@ -6,6 +8,7 @@ export interface IContainerTypesRepository {
findByCode(code: string): Promise<ContainerType | null>;
findAll(options?: FindManyOptions<ContainerType>): Promise<ContainerType[]>;
findAndCount(options?: FindManyOptions<ContainerType>): Promise<[ContainerType[], number]>;
findPaged(query: ListContainerTypesQueryDto): Promise<PaginatedResponse<ContainerType>>;
create(data: Partial<ContainerType>): Promise<ContainerType>;
update(id: string, data: Partial<ContainerType>): Promise<ContainerType | null>;
softDelete(id: string): Promise<void>;

View File

@@ -1,4 +1,6 @@
import { PaginatedResponse } from '@edr/types';
import { FindManyOptions } from 'typeorm';
import { ListPriorityConfigsQueryDto } from '../dto/list-rule-engine-query.dto';
import { PriorityConfig } from '../entities/priority-config.entity';
export const PRIORITY_CONFIGS_REPOSITORY = Symbol('PRIORITY_CONFIGS_REPOSITORY');
@@ -7,6 +9,7 @@ export interface IPriorityConfigsRepository {
findById(id: string): Promise<PriorityConfig | null>;
findAll(options?: FindManyOptions<PriorityConfig>): Promise<PriorityConfig[]>;
findAndCount(options?: FindManyOptions<PriorityConfig>): Promise<[PriorityConfig[], number]>;
findPaged(query: ListPriorityConfigsQueryDto): Promise<PaginatedResponse<PriorityConfig>>;
findAllActive(): Promise<PriorityConfig[]>;
create(data: Partial<PriorityConfig>): Promise<PriorityConfig>;
update(id: string, data: Partial<PriorityConfig>): Promise<PriorityConfig | null>;

View File

@@ -1,4 +1,6 @@
import { PaginatedResponse } from '@edr/types';
import { FindManyOptions } from 'typeorm';
import { ListRatesQueryDto } from '../dto/list-rule-engine-query.dto';
import { Rate } from '../entities/rate.entity';
export interface IRatesRepository {
@@ -13,6 +15,7 @@ export interface IRatesRepository {
}): Promise<Rate | null>;
findAll(options?: FindManyOptions<Rate>): Promise<Rate[]>;
findAndCount(options?: FindManyOptions<Rate>): Promise<[Rate[], number]>;
findPaged(query: ListRatesQueryDto): Promise<PaginatedResponse<Rate>>;
create(data: Partial<Rate>): Promise<Rate>;
update(id: string, data: Partial<Rate>): Promise<Rate | null>;
softDelete(id: string): Promise<void>;

View File

@@ -1,4 +1,6 @@
import { PaginatedResponse } from '@edr/types';
import { FindManyOptions } from 'typeorm';
import { ListServiceTypesQueryDto } from '../dto/list-rule-engine-query.dto';
import { ServiceType } from '../entities/service-type.entity';
export interface IServiceTypesRepository {
@@ -6,6 +8,7 @@ export interface IServiceTypesRepository {
findByCode(code: string): Promise<ServiceType | null>;
findAll(options?: FindManyOptions<ServiceType>): Promise<ServiceType[]>;
findAndCount(options?: FindManyOptions<ServiceType>): Promise<[ServiceType[], number]>;
findPaged(query: ListServiceTypesQueryDto): Promise<PaginatedResponse<ServiceType>>;
create(data: Partial<ServiceType>): Promise<ServiceType>;
update(id: string, data: Partial<ServiceType>): Promise<ServiceType | null>;
softDelete(id: string): Promise<void>;

View File

@@ -1,4 +1,6 @@
import { PaginatedResponse } from '@edr/types';
import { FindManyOptions } from 'typeorm';
import { ListRuleEngineQueryDto } from '../dto/list-rule-engine-query.dto';
import { ShippingLine } from '../entities/shipping-line.entity';
export interface IShippingLinesRepository {
@@ -6,6 +8,7 @@ export interface IShippingLinesRepository {
findByCode(code: string): Promise<ShippingLine | null>;
findAll(options?: FindManyOptions<ShippingLine>): Promise<ShippingLine[]>;
findAndCount(options?: FindManyOptions<ShippingLine>): Promise<[ShippingLine[], number]>;
findPaged(query: ListRuleEngineQueryDto): Promise<PaginatedResponse<ShippingLine>>;
create(data: Partial<ShippingLine>): Promise<ShippingLine>;
update(id: string, data: Partial<ShippingLine>): Promise<ShippingLine | null>;
softDelete(id: string): Promise<void>;

View File

@@ -1,4 +1,6 @@
import { PaginatedResponse } from '@edr/types';
import { FindManyOptions } from 'typeorm';
import { ListWeightLimitRulesQueryDto } from '../dto/list-rule-engine-query.dto';
import { WeightLimitRule } from '../entities/weight-limit-rule.entity';
export interface IWeightLimitRulesRepository {
@@ -14,6 +16,7 @@ export interface IWeightLimitRulesRepository {
): Promise<WeightLimitRule | null>;
findAll(options?: FindManyOptions<WeightLimitRule>): Promise<WeightLimitRule[]>;
findAndCount(options?: FindManyOptions<WeightLimitRule>): Promise<[WeightLimitRule[], number]>;
findPaged(query: ListWeightLimitRulesQueryDto): Promise<PaginatedResponse<WeightLimitRule>>;
create(data: Partial<WeightLimitRule>): Promise<WeightLimitRule>;
update(id: string, data: Partial<WeightLimitRule>): Promise<WeightLimitRule | null>;
softDelete(id: string): Promise<void>;

View File

@@ -1,4 +1,6 @@
import { PaginatedResponse } from '@edr/types';
import { FindManyOptions } from 'typeorm';
import { ListYardsQueryDto } from '../dto/list-rule-engine-query.dto';
import { Yard } from '../entities/yard.entity';
export interface IYardsRepository {
@@ -6,6 +8,7 @@ export interface IYardsRepository {
findByCode(code: string): Promise<Yard | null>;
findAll(options?: FindManyOptions<Yard>): Promise<Yard[]>;
findAndCount(options?: FindManyOptions<Yard>): Promise<[Yard[], number]>;
findPaged(query: ListYardsQueryDto): Promise<PaginatedResponse<Yard>>;
create(data: Partial<Yard>): Promise<Yard>;
update(id: string, data: Partial<Yard>): Promise<Yard | null>;
softDelete(id: string): Promise<void>;

View File

@@ -1,5 +1,8 @@
import { PaginatedResponse } from '@edr/types';
import { Injectable } from '@nestjs/common';
import { DataSource, FindManyOptions, Repository } from 'typeorm';
import { paginateQuery } from '../../../common/utils/pagination.util';
import { ListApprovalRulesQueryDto } from '../dto/list-rule-engine-query.dto';
import { ApprovalRule } from '../entities/approval-rule.entity';
import { IApprovalRulesRepository } from '../interfaces/approval-rules.repository.interface';
@@ -30,6 +33,31 @@ export class ApprovalRulesRepository implements IApprovalRulesRepository {
return this.repo.findAndCount(options);
}
/**
* Paged list in the standard envelope. Chain grouping is preserved: rows are
* grouped by chain (requiresDirectorApproval) first, then step order.
*/
findPaged(query: ListApprovalRulesQueryDto): Promise<PaginatedResponse<ApprovalRule>> {
const qb = this.repo
.createQueryBuilder('rule')
.orderBy('rule.requiresDirectorApproval', 'ASC')
.addOrderBy('rule.stepOrder', query.sortOrder ?? 'ASC');
if (query.requiresDirectorApproval !== undefined) {
qb.andWhere('rule.requiresDirectorApproval = :requiresDirectorApproval', {
requiresDirectorApproval: query.requiresDirectorApproval,
});
}
if (query.search) {
qb.andWhere(
'(rule.actionLabel ILIKE :search OR rule.requiredRole ILIKE :search OR rule.blocksRole ILIKE :search)',
{ search: `%${query.search}%` },
);
}
return paginateQuery(qb, query);
}
async create(data: Partial<ApprovalRule>): Promise<ApprovalRule> {
const entity = this.repo.create(data);
return this.repo.save(entity);

View File

@@ -1,5 +1,8 @@
import { PaginatedResponse } from '@edr/types';
import { Injectable } from '@nestjs/common';
import { DataSource, FindManyOptions, Repository } from 'typeorm';
import { paginateQuery } from '../../../common/utils/pagination.util';
import { ListCargoTypesQueryDto } from '../dto/list-rule-engine-query.dto';
import { CargoType } from '../entities/cargo-type.entity';
import { ICargoTypesRepository } from '../interfaces/cargo-types.repository.interface';
@@ -27,6 +30,33 @@ export class CargoTypesRepository implements ICargoTypesRepository {
return this.repo.findAndCount(options);
}
/** Paged list with server-side search (name/code) in the standard envelope. */
findPaged(query: ListCargoTypesQueryDto): Promise<PaginatedResponse<CargoType>> {
const qb = this.repo
.createQueryBuilder('cargoType')
.leftJoinAndSelect('cargoType.parent', 'parent')
.orderBy(`cargoType.${query.sortBy ?? 'displayOrder'}`, query.sortOrder ?? 'ASC');
if (query.isActive !== undefined) {
qb.andWhere('cargoType.isActive = :isActive', { isActive: query.isActive });
}
if (query.requiresDirectorApproval !== undefined) {
qb.andWhere('cargoType.requiresDirectorApproval = :requiresDirectorApproval', {
requiresDirectorApproval: query.requiresDirectorApproval,
});
}
if (query.parentGroupId !== undefined) {
qb.andWhere('cargoType.parentGroupId = :parentGroupId', { parentGroupId: query.parentGroupId });
}
if (query.search) {
qb.andWhere('(cargoType.cargoTypeName ILIKE :search OR cargoType.code ILIKE :search)', {
search: `%${query.search}%`,
});
}
return paginateQuery(qb, query);
}
async create(data: Partial<CargoType>): Promise<CargoType> {
const entity = this.repo.create(data);
return this.repo.save(entity);

View File

@@ -1,5 +1,8 @@
import { PaginatedResponse } from '@edr/types';
import { Injectable } from '@nestjs/common';
import { DataSource, FindManyOptions, Repository } from 'typeorm';
import { paginateQuery } from '../../../common/utils/pagination.util';
import { ListContainerTypesQueryDto } from '../dto/list-rule-engine-query.dto';
import { ContainerType } from '../entities/container-type.entity';
import { IContainerTypesRepository } from '../interfaces/container-types.repository.interface';
@@ -27,6 +30,24 @@ export class ContainerTypesRepository implements IContainerTypesRepository {
return this.repo.findAndCount(options);
}
/** Paged list with server-side search (label/code) in the standard envelope. */
findPaged(query: ListContainerTypesQueryDto): Promise<PaginatedResponse<ContainerType>> {
const qb = this.repo
.createQueryBuilder('containerType')
.orderBy(`containerType.${query.sortBy ?? 'displayOrder'}`, query.sortOrder ?? 'ASC');
if (query.isActive !== undefined) {
qb.andWhere('containerType.isActive = :isActive', { isActive: query.isActive });
}
if (query.search) {
qb.andWhere('(containerType.label ILIKE :search OR containerType.code ILIKE :search)', {
search: `%${query.search}%`,
});
}
return paginateQuery(qb, query);
}
async create(data: Partial<ContainerType>): Promise<ContainerType> {
const entity = this.repo.create(data);
return this.repo.save(entity);

View File

@@ -1,5 +1,8 @@
import { PaginatedResponse } from '@edr/types';
import { Injectable } from '@nestjs/common';
import { DataSource, FindManyOptions, Repository } from 'typeorm';
import { paginateQuery } from '../../../common/utils/pagination.util';
import { ListPriorityConfigsQueryDto } from '../dto/list-rule-engine-query.dto';
import { PriorityConfig } from '../entities/priority-config.entity';
import { IPriorityConfigsRepository } from '../interfaces/priority-configs.repository.interface';
@@ -23,6 +26,28 @@ export class PriorityConfigsRepository implements IPriorityConfigsRepository {
return this.repo.findAndCount(options);
}
/** Paged list with server-side search (label/type/currency) in the standard envelope. */
findPaged(query: ListPriorityConfigsQueryDto): Promise<PaginatedResponse<PriorityConfig>> {
const qb = this.repo
.createQueryBuilder('config')
.orderBy(`config.${query.sortBy ?? 'displayOrder'}`, query.sortOrder ?? 'ASC');
if (query.type !== undefined) {
qb.andWhere('config.type = :type', { type: query.type });
}
if (query.isActive !== undefined) {
qb.andWhere('config.isActive = :isActive', { isActive: query.isActive });
}
if (query.search) {
qb.andWhere(
'(config.label ILIKE :search OR config.type ILIKE :search OR config.currency ILIKE :search)',
{ search: `%${query.search}%` },
);
}
return paginateQuery(qb, query);
}
async findAllActive(): Promise<PriorityConfig[]> {
return this.repo.find({
where: { isActive: true },

View File

@@ -1,5 +1,8 @@
import { PaginatedResponse } from '@edr/types';
import { Injectable } from '@nestjs/common';
import { DataSource, FindManyOptions, Repository } from 'typeorm';
import { paginateQuery } from '../../../common/utils/pagination.util';
import { ListRatesQueryDto } from '../dto/list-rule-engine-query.dto';
import { Rate } from '../entities/rate.entity';
import { IRatesRepository } from '../interfaces/rates.repository.interface';
@@ -68,6 +71,28 @@ export class RatesRepository implements IRatesRepository {
return this.repo.findAndCount(options);
}
/** Paged list with server-side search (type/status/unit/currency), newest first. */
findPaged(query: ListRatesQueryDto): Promise<PaginatedResponse<Rate>> {
const qb = this.repo
.createQueryBuilder('rate')
.orderBy('rate.createdAt', query.sortOrder ?? 'DESC');
if (query.status) {
qb.andWhere('rate.status = :status', { status: query.status });
}
if (query.rateType) {
qb.andWhere('rate.rateType = :rateType', { rateType: query.rateType });
}
if (query.search) {
qb.andWhere(
'(rate.rateType ILIKE :search OR rate.status ILIKE :search OR rate.rateUnit ILIKE :search OR rate.currency ILIKE :search)',
{ search: `%${query.search}%` },
);
}
return paginateQuery(qb, query);
}
async create(data: Partial<Rate>): Promise<Rate> {
const entity = this.repo.create(data);
return this.repo.save(entity);

View File

@@ -1,5 +1,8 @@
import { PaginatedResponse } from '@edr/types';
import { Injectable } from '@nestjs/common';
import { DataSource, FindManyOptions, Repository } from 'typeorm';
import { paginateQuery } from '../../../common/utils/pagination.util';
import { ListServiceTypesQueryDto } from '../dto/list-rule-engine-query.dto';
import { ServiceType } from '../entities/service-type.entity';
import { IServiceTypesRepository } from '../interfaces/service-types.repository.interface';
@@ -27,6 +30,30 @@ export class ServiceTypesRepository implements IServiceTypesRepository {
return this.repo.findAndCount(options);
}
/** Paged list with server-side search (name/code/description) in the standard envelope. */
findPaged(query: ListServiceTypesQueryDto): Promise<PaginatedResponse<ServiceType>> {
const qb = this.repo
.createQueryBuilder('serviceType')
.orderBy(`serviceType.${query.sortBy ?? 'displayOrder'}`, query.sortOrder ?? 'ASC');
if (query.isActive !== undefined) {
qb.andWhere('serviceType.isActive = :isActive', { isActive: query.isActive });
}
if (query.canBeBookedAlone !== undefined) {
qb.andWhere('serviceType.canBeBookedAlone = :canBeBookedAlone', {
canBeBookedAlone: query.canBeBookedAlone,
});
}
if (query.search) {
qb.andWhere(
'(serviceType.serviceName ILIKE :search OR serviceType.code ILIKE :search OR serviceType.description ILIKE :search)',
{ search: `%${query.search}%` },
);
}
return paginateQuery(qb, query);
}
async create(data: Partial<ServiceType>): Promise<ServiceType> {
const entity = this.repo.create(data);
return this.repo.save(entity);

View File

@@ -1,5 +1,8 @@
import { PaginatedResponse } from '@edr/types';
import { Injectable } from '@nestjs/common';
import { DataSource, FindManyOptions, Repository } from 'typeorm';
import { paginateQuery } from '../../../common/utils/pagination.util';
import { ListRuleEngineQueryDto } from '../dto/list-rule-engine-query.dto';
import { ShippingLine } from '../entities/shipping-line.entity';
import { IShippingLinesRepository } from '../interfaces/shipping-lines.repository.interface';
@@ -27,6 +30,25 @@ export class ShippingLinesRepository implements IShippingLinesRepository {
return this.repo.findAndCount(options);
}
/** Paged list with server-side search (label/code/mappedToCode), ordered by code. */
findPaged(query: ListRuleEngineQueryDto): Promise<PaginatedResponse<ShippingLine>> {
const qb = this.repo
.createQueryBuilder('line')
.orderBy('line.code', query.sortOrder ?? 'ASC');
if (query.isActive !== undefined) {
qb.andWhere('line.isActive = :isActive', { isActive: query.isActive });
}
if (query.search) {
qb.andWhere(
'(line.label ILIKE :search OR line.code ILIKE :search OR line.mappedToCode ILIKE :search)',
{ search: `%${query.search}%` },
);
}
return paginateQuery(qb, query);
}
async create(data: Partial<ShippingLine>): Promise<ShippingLine> {
const entity = this.repo.create(data);
return this.repo.save(entity);

View File

@@ -1,5 +1,8 @@
import { PaginatedResponse } from '@edr/types';
import { Injectable } from '@nestjs/common';
import { DataSource, FindManyOptions, Repository } from 'typeorm';
import { paginateQuery } from '../../../common/utils/pagination.util';
import { ListWeightLimitRulesQueryDto } from '../dto/list-rule-engine-query.dto';
import { WeightLimitRule } from '../entities/weight-limit-rule.entity';
import { IWeightLimitRulesRepository } from '../interfaces/weight-limit-rules.repository.interface';
@@ -59,6 +62,34 @@ export class WeightLimitRulesRepository implements IWeightLimitRulesRepository {
return this.repo.findAndCount(options);
}
/**
* Paged list with the container relation loaded, newest first. `search`
* matches the trade direction and the joined container type's label/code.
*/
findPaged(query: ListWeightLimitRulesQueryDto): Promise<PaginatedResponse<WeightLimitRule>> {
const qb = this.repo
.createQueryBuilder('rule')
.leftJoinAndSelect('rule.containerType', 'containerType')
.orderBy('rule.createdAt', query.sortOrder ?? 'DESC');
if (query.containerTypeId) {
qb.andWhere('rule.containerTypeId = :containerTypeId', {
containerTypeId: query.containerTypeId,
});
}
if (query.tradeDirection) {
qb.andWhere('rule.tradeDirection = :tradeDirection', { tradeDirection: query.tradeDirection });
}
if (query.search) {
qb.andWhere(
'(rule.tradeDirection ILIKE :search OR containerType.label ILIKE :search OR containerType.code ILIKE :search)',
{ search: `%${query.search}%` },
);
}
return paginateQuery(qb, query);
}
async create(data: Partial<WeightLimitRule>): Promise<WeightLimitRule> {
const entity = this.repo.create(data);
return this.repo.save(entity);

View File

@@ -1,5 +1,8 @@
import { PaginatedResponse } from '@edr/types';
import { Injectable } from '@nestjs/common';
import { DataSource, FindManyOptions, Repository } from 'typeorm';
import { paginateQuery } from '../../../common/utils/pagination.util';
import { ListYardsQueryDto } from '../dto/list-rule-engine-query.dto';
import { Yard } from '../entities/yard.entity';
import { IYardsRepository } from '../interfaces/yards.repository.interface';
@@ -27,6 +30,29 @@ export class YardsRepository implements IYardsRepository {
return this.repo.findAndCount(options);
}
/** Paged list with server-side search (label/code/country) in the standard envelope. */
findPaged(query: ListYardsQueryDto): Promise<PaginatedResponse<Yard>> {
const qb = this.repo
.createQueryBuilder('yard')
.orderBy(`yard.${query.sortBy ?? 'displayOrder'}`, query.sortOrder ?? 'ASC')
.addOrderBy('yard.label', 'ASC');
if (query.isActive !== undefined) {
qb.andWhere('yard.isActive = :isActive', { isActive: query.isActive });
}
if (query.country) {
qb.andWhere('yard.country = :country', { country: query.country });
}
if (query.search) {
qb.andWhere(
'(yard.label ILIKE :search OR yard.code ILIKE :search OR yard.country ILIKE :search)',
{ search: `%${query.search}%` },
);
}
return paginateQuery(qb, query);
}
async create(data: Partial<Yard>): Promise<Yard> {
const entity = this.repo.create(data);
return this.repo.save(entity);

View File

@@ -1,5 +1,7 @@
import { PaginatedResponse } from '@edr/types';
import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { CreateApprovalRuleDto } from '../dto/create-approval-rule.dto';
import { ListApprovalRulesQueryDto } from '../dto/list-rule-engine-query.dto';
import { ReorderItemsDto } from '../dto/reorder-items.dto';
import { UpdateApprovalRuleDto } from '../dto/update-approval-rule.dto';
import { ApprovalRule } from '../entities/approval-rule.entity';
@@ -17,26 +19,9 @@ export class ApprovalRulesService {
private readonly displayOrder: DisplayOrderService,
) {}
/** List approval rules. */
async findAll(filter: {
requiresDirectorApproval?: boolean;
page?: number;
pageSize?: number;
}): Promise<{ data: ApprovalRule[]; meta: { total: number; page: number; pageSize: number; totalPages: number } }> {
const page = filter.page ?? 1;
const pageSize = filter.pageSize ?? 10;
const where: Record<string, unknown> = {};
if (filter.requiresDirectorApproval !== undefined) {
where.requiresDirectorApproval = filter.requiresDirectorApproval;
}
const [data, total] = await this.repository.findAndCount({
where,
order: { requiresDirectorApproval: 'ASC', stepOrder: 'ASC' },
skip: (page - 1) * pageSize,
take: pageSize,
});
return { data, meta: { total, page, pageSize, totalPages: Math.ceil(total / pageSize) } };
/** List approval rules — standard paginated envelope with server-side search. */
async findAll(query: ListApprovalRulesQueryDto): Promise<PaginatedResponse<ApprovalRule>> {
return this.repository.findPaged(query);
}
/** Get approval chain for a cargo type flag. */

View File

@@ -1,7 +1,8 @@
import { PaginatedResponse } from '@edr/types';
import { ConflictException, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { ILike } from 'typeorm';
import { generateCode } from '../../../common/utils/generate-code.util';
import { CreateCargoTypeDto } from '../dto/create-cargo-type.dto';
import { ListCargoTypesQueryDto } from '../dto/list-rule-engine-query.dto';
import { ReorderItemsDto } from '../dto/reorder-items.dto';
import { UpdateCargoTypeDto } from '../dto/update-cargo-type.dto';
import { CargoType } from '../entities/cargo-type.entity';
@@ -19,33 +20,9 @@ export class CargoTypesService {
private readonly displayOrder: DisplayOrderService,
) {}
/** List cargo types with pagination and optional filtering. */
async findAll(filter: {
isActive?: boolean;
requiresDirectorApproval?: boolean;
parentGroupId?: string;
search?: string;
page?: number;
pageSize?: number;
sortBy?: string;
sortOrder?: 'ASC' | 'DESC';
}): Promise<{ data: CargoType[]; meta: { total: number; page: number; pageSize: number; totalPages: number } }> {
const page = filter.page ?? 1;
const pageSize = filter.pageSize ?? 10;
const where: Record<string, unknown> = {};
if (filter.isActive !== undefined) where.isActive = filter.isActive;
if (filter.requiresDirectorApproval !== undefined) where.requiresDirectorApproval = filter.requiresDirectorApproval;
if (filter.parentGroupId !== undefined) where.parentGroupId = filter.parentGroupId;
if (filter.search) where.cargoTypeName = ILike(`%${filter.search}%`);
const [data, total] = await this.repository.findAndCount({
where,
order: { [filter.sortBy ?? 'displayOrder']: filter.sortOrder ?? 'ASC' },
skip: (page - 1) * pageSize,
take: pageSize,
relations: { parent: true },
});
return { data, meta: { total, page, pageSize, totalPages: Math.ceil(total / pageSize) } };
/** List cargo types — standard paginated envelope with server-side search. */
async findAll(query: ListCargoTypesQueryDto): Promise<PaginatedResponse<CargoType>> {
return this.repository.findPaged(query);
}
/** Get a single cargo type by ID. */

View File

@@ -1,6 +1,8 @@
import { PaginatedResponse } from '@edr/types';
import { ConflictException, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { generateCode } from '../../../common/utils/generate-code.util';
import { CreateContainerTypeDto } from '../dto/create-container-type.dto';
import { ListContainerTypesQueryDto } from '../dto/list-rule-engine-query.dto';
import { ReorderItemsDto } from '../dto/reorder-items.dto';
import { UpdateContainerTypeDto } from '../dto/update-container-type.dto';
import { ContainerType } from '../entities/container-type.entity';
@@ -18,24 +20,9 @@ export class ContainerTypesService {
private readonly displayOrder: DisplayOrderService,
) {}
/** List container types with pagination. */
async findAll(filter: {
isActive?: boolean;
page?: number;
pageSize?: number;
}): Promise<{ data: ContainerType[]; meta: { total: number; page: number; pageSize: number; totalPages: number } }> {
const page = filter.page ?? 1;
const pageSize = filter.pageSize ?? 10;
const where: Record<string, unknown> = {};
if (filter.isActive !== undefined) where.isActive = filter.isActive;
const [data, total] = await this.repository.findAndCount({
where,
order: { displayOrder: 'ASC' },
skip: (page - 1) * pageSize,
take: pageSize,
});
return { data, meta: { total, page, pageSize, totalPages: Math.ceil(total / pageSize) } };
/** List container types — standard paginated envelope with server-side search. */
async findAll(query: ListContainerTypesQueryDto): Promise<PaginatedResponse<ContainerType>> {
return this.repository.findPaged(query);
}
/** Get a single container type by ID. */

View File

@@ -1,5 +1,7 @@
import { PaginatedResponse } from '@edr/types';
import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { CreatePriorityConfigDto } from '../dto/create-priority-config.dto';
import { ListPriorityConfigsQueryDto } from '../dto/list-rule-engine-query.dto';
import { UpdatePriorityConfigDto } from '../dto/update-priority-config.dto';
import { PriorityConfig } from '../entities/priority-config.entity';
import {
@@ -16,25 +18,9 @@ export class PriorityConfigsService {
private readonly displayOrder: DisplayOrderService,
) {}
async findAll(filter: {
type?: 'WAGON' | 'CURRENCY' | 'CUSTOMS';
isActive?: boolean;
page?: number;
pageSize?: number;
}): Promise<{ data: PriorityConfig[]; meta: { total: number; page: number; pageSize: number; totalPages: number } }> {
const page = filter.page ?? 1;
const pageSize = filter.pageSize ?? 20;
const where: Record<string, unknown> = {};
if (filter.type !== undefined) where.type = filter.type;
if (filter.isActive !== undefined) where.isActive = filter.isActive;
const [data, total] = await this.repository.findAndCount({
where,
order: { displayOrder: 'ASC' },
skip: (page - 1) * pageSize,
take: pageSize,
});
return { data, meta: { total, page, pageSize, totalPages: Math.ceil(total / pageSize) } };
/** List priority configs — standard paginated envelope with server-side search. */
async findAll(query: ListPriorityConfigsQueryDto): Promise<PaginatedResponse<PriorityConfig>> {
return this.repository.findPaged(query);
}
async findById(id: string): Promise<PriorityConfig> {

View File

@@ -5,7 +5,9 @@ import {
Injectable,
NotFoundException,
} from '@nestjs/common';
import { PaginatedResponse } from '@edr/types';
import { CreateRateDto } from '../dto/create-rate.dto';
import { ListRatesQueryDto } from '../dto/list-rule-engine-query.dto';
import { UpdateRateDto } from '../dto/update-rate.dto';
import { Rate } from '../entities/rate.entity';
import { deriveRateType } from '../entities/rate-type.util';
@@ -19,26 +21,9 @@ export class RatesService {
private readonly repository: IRatesRepository,
) {}
/** List rates with pagination. */
async findAll(filter: {
status?: string;
rateType?: string;
page?: number;
pageSize?: number;
}): Promise<{ data: Rate[]; meta: { total: number; page: number; pageSize: number; totalPages: number } }> {
const page = filter.page ?? 1;
const pageSize = filter.pageSize ?? 20;
const where: Record<string, unknown> = {};
if (filter.status) where.status = filter.status;
if (filter.rateType) where.rateType = filter.rateType;
const [data, total] = await this.repository.findAndCount({
where,
order: { createdAt: 'DESC' },
skip: (page - 1) * pageSize,
take: pageSize,
});
return { data, meta: { total, page, pageSize, totalPages: Math.max(1, Math.ceil(total / pageSize)) } };
/** List rates — standard paginated envelope with server-side search. */
async findAll(query: ListRatesQueryDto): Promise<PaginatedResponse<Rate>> {
return this.repository.findPaged(query);
}
/** Return all currently LIVE rates. */

View File

@@ -1,7 +1,8 @@
import { PaginatedResponse } from '@edr/types';
import { ConflictException, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { ILike } from 'typeorm';
import { generateCode } from '../../../common/utils/generate-code.util';
import { CreateServiceTypeDto } from '../dto/create-service-type.dto';
import { ListServiceTypesQueryDto } from '../dto/list-rule-engine-query.dto';
import { ReorderItemsDto } from '../dto/reorder-items.dto';
import { UpdateServiceTypeDto } from '../dto/update-service-type.dto';
import { ServiceType } from '../entities/service-type.entity';
@@ -19,30 +20,9 @@ export class ServiceTypesService {
private readonly displayOrder: DisplayOrderService,
) {}
/** List service types with pagination and optional filtering. */
async findAll(filter: {
isActive?: boolean;
canBeBookedAlone?: boolean;
search?: string;
page?: number;
pageSize?: number;
sortBy?: string;
sortOrder?: 'ASC' | 'DESC';
}): Promise<{ data: ServiceType[]; meta: { total: number; page: number; pageSize: number; totalPages: number } }> {
const page = filter.page ?? 1;
const pageSize = filter.pageSize ?? 10;
const where: Record<string, unknown> = {};
if (filter.isActive !== undefined) where.isActive = filter.isActive;
if (filter.canBeBookedAlone !== undefined) where.canBeBookedAlone = filter.canBeBookedAlone;
if (filter.search) where.serviceName = ILike(`%${filter.search}%`);
const [data, total] = await this.repository.findAndCount({
where,
order: { [filter.sortBy ?? 'displayOrder']: filter.sortOrder ?? 'ASC' },
skip: (page - 1) * pageSize,
take: pageSize,
});
return { data, meta: { total, page, pageSize, totalPages: Math.ceil(total / pageSize) } };
/** List service types — standard paginated envelope with server-side search. */
async findAll(query: ListServiceTypesQueryDto): Promise<PaginatedResponse<ServiceType>> {
return this.repository.findPaged(query);
}
/** Get a single service type by ID. */

View File

@@ -1,5 +1,7 @@
import { PaginatedResponse } from '@edr/types';
import { ConflictException, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { CreateShippingLineDto } from '../dto/create-shipping-line.dto';
import { ListRuleEngineQueryDto } from '../dto/list-rule-engine-query.dto';
import { UpdateShippingLineDto } from '../dto/update-shipping-line.dto';
import { ShippingLine } from '../entities/shipping-line.entity';
import {
@@ -14,24 +16,9 @@ export class ShippingLinesService {
private readonly repository: IShippingLinesRepository,
) {}
/** List shipping lines with pagination. */
async findAll(filter: {
isActive?: boolean;
page?: number;
pageSize?: number;
}): Promise<{ data: ShippingLine[]; meta: { total: number; page: number; pageSize: number; totalPages: number } }> {
const page = filter.page ?? 1;
const pageSize = filter.pageSize ?? 20;
const where: Record<string, unknown> = {};
if (filter.isActive !== undefined) where.isActive = filter.isActive;
const [data, total] = await this.repository.findAndCount({
where,
order: { code: 'ASC' },
skip: (page - 1) * pageSize,
take: pageSize,
});
return { data, meta: { total, page, pageSize, totalPages: Math.ceil(total / pageSize) } };
/** List shipping lines — standard paginated envelope with server-side search. */
async findAll(query: ListRuleEngineQueryDto): Promise<PaginatedResponse<ShippingLine>> {
return this.repository.findPaged(query);
}
/** Get a shipping line by ID. */

View File

@@ -5,7 +5,9 @@ import {
Injectable,
NotFoundException,
} from '@nestjs/common';
import { PaginatedResponse } from '@edr/types';
import { CreateWeightLimitRuleDto } from '../dto/create-weight-limit-rule.dto';
import { ListWeightLimitRulesQueryDto } from '../dto/list-rule-engine-query.dto';
import { UpdateWeightLimitRuleDto } from '../dto/update-weight-limit-rule.dto';
import { WeightLimitRule } from '../entities/weight-limit-rule.entity';
import {
@@ -20,27 +22,9 @@ export class WeightLimitRulesService {
private readonly repository: IWeightLimitRulesRepository,
) {}
/** List weight limit rules with pagination. */
async findAll(filter: {
containerTypeId?: string;
tradeDirection?: string;
page?: number;
pageSize?: number;
}): Promise<{ data: WeightLimitRule[]; meta: { total: number; page: number; pageSize: number; totalPages: number } }> {
const page = filter.page ?? 1;
const pageSize = filter.pageSize ?? 20;
const where: Record<string, unknown> = {};
if (filter.containerTypeId) where.containerTypeId = filter.containerTypeId;
if (filter.tradeDirection) where.tradeDirection = filter.tradeDirection;
const [data, total] = await this.repository.findAndCount({
where,
relations: { containerType: true },
order: { createdAt: 'DESC' },
skip: (page - 1) * pageSize,
take: pageSize,
});
return { data, meta: { total, page, pageSize, totalPages: Math.ceil(total / pageSize) } };
/** List weight limit rules — standard paginated envelope with server-side search. */
async findAll(query: ListWeightLimitRulesQueryDto): Promise<PaginatedResponse<WeightLimitRule>> {
return this.repository.findPaged(query);
}
/** Get a single weight limit rule by ID. */

View File

@@ -1,6 +1,8 @@
import { PaginatedResponse } from '@edr/types';
import { ConflictException, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { generateCode } from '../../../common/utils/generate-code.util';
import { CreateYardDto } from '../dto/create-yard.dto';
import { ListYardsQueryDto } from '../dto/list-rule-engine-query.dto';
import { ReorderItemsDto } from '../dto/reorder-items.dto';
import { UpdateYardDto } from '../dto/update-yard.dto';
import { Yard } from '../entities/yard.entity';
@@ -15,26 +17,9 @@ export class YardsService {
private readonly displayOrder: DisplayOrderService,
) {}
/** List yards with pagination. */
async findAll(filter: {
isActive?: boolean;
country?: string;
page?: number;
pageSize?: number;
}): Promise<{ data: Yard[]; meta: { total: number; page: number; pageSize: number; totalPages: number } }> {
const page = filter.page ?? 1;
const pageSize = filter.pageSize ?? 10;
const where: Record<string, unknown> = {};
if (filter.isActive !== undefined) where.isActive = filter.isActive;
if (filter.country) where.country = filter.country;
const [data, total] = await this.repository.findAndCount({
where,
order: { displayOrder: 'ASC', label: 'ASC' },
skip: (page - 1) * pageSize,
take: pageSize,
});
return { data, meta: { total, page, pageSize, totalPages: Math.ceil(total / pageSize) } };
/** List yards — standard paginated envelope with server-side search. */
async findAll(query: ListYardsQueryDto): Promise<PaginatedResponse<Yard>> {
return this.repository.findPaged(query);
}
/** Get a yard by ID. */