mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 17:08:18 +00:00
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:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user