mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 14:08:11 +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:
@@ -10,12 +10,14 @@ import {
|
||||
Patch,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
} from "@nestjs/common";
|
||||
import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||
|
||||
import { FreightAdmin } from "../../common/booking-guards";
|
||||
import { CreateDropdownOptionDto } from "./dto/create-dropdown-option.dto";
|
||||
import { CreateDropdownSettingDto } from "./dto/create-dropdown-setting.dto";
|
||||
import { ListDropdownSettingsQueryDto } from "./dto/list-dropdown-settings-query.dto";
|
||||
import { UpdateDropdownOptionDto } from "./dto/update-dropdown-option.dto";
|
||||
import { UpdateDropdownSettingDto } from "./dto/update-dropdown-setting.dto";
|
||||
import { DropdownSettingsService } from "./dropdown-settings.service";
|
||||
@@ -34,6 +36,15 @@ export class DropdownSettingsController {
|
||||
return this.service.list();
|
||||
}
|
||||
|
||||
// Must be declared before @Get(":id") so "paged" isn't captured as an id.
|
||||
@Get("paged")
|
||||
@ApiOperation({
|
||||
summary: "Paged admin listing of dropdown settings (server-side search)",
|
||||
})
|
||||
listPaged(@Query() query: ListDropdownSettingsQueryDto) {
|
||||
return this.service.listPaged(query);
|
||||
}
|
||||
|
||||
@Get(":id")
|
||||
@ApiOperation({ summary: "Get a dropdown setting by ID" })
|
||||
getById(@Param("id", ParseUUIDPipe) id: string) {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { BaseRepository } from "@edr/api-common";
|
||||
import { PaginatedResponse } from "@edr/types";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
|
||||
import { paginateQuery } from "../../common/utils/pagination.util";
|
||||
import { ListDropdownSettingsQueryDto } from "./dto/list-dropdown-settings-query.dto";
|
||||
import { DropdownOption } from "./entities/dropdown-option.entity";
|
||||
import { DropdownSetting } from "./entities/dropdown-setting.entity";
|
||||
import type { IDropdownSettingsRepository } from "./interfaces/dropdown-settings.repository.interface";
|
||||
@@ -44,6 +47,27 @@ export class DropdownSettingsRepository
|
||||
});
|
||||
}
|
||||
|
||||
findPaged(
|
||||
query: ListDropdownSettingsQueryDto,
|
||||
): Promise<PaginatedResponse<DropdownSetting>> {
|
||||
// Soft-deleted rows are excluded automatically by the query builder
|
||||
// (BaseEntity's deletedAt column). Ordering mirrors findAll (label ASC).
|
||||
const qb = this.repository
|
||||
.createQueryBuilder("setting")
|
||||
.leftJoinAndSelect("setting.children", "option")
|
||||
.orderBy("setting.label", query.sortOrder ?? "ASC")
|
||||
.addOrderBy("option.order", "ASC");
|
||||
|
||||
if (query.search) {
|
||||
qb.andWhere(
|
||||
"(setting.code ILIKE :search OR setting.label ILIKE :search OR setting.description ILIKE :search)",
|
||||
{ search: `%${query.search}%` },
|
||||
);
|
||||
}
|
||||
|
||||
return paginateQuery(qb, query);
|
||||
}
|
||||
|
||||
async replaceOptions(
|
||||
settingId: string,
|
||||
options: Array<Partial<DropdownOption>>,
|
||||
|
||||
@@ -5,8 +5,11 @@ import {
|
||||
NotFoundException,
|
||||
} from "@nestjs/common";
|
||||
|
||||
import { PaginatedResponse } from "@edr/types";
|
||||
|
||||
import { CreateDropdownOptionDto } from "./dto/create-dropdown-option.dto";
|
||||
import { CreateDropdownSettingDto } from "./dto/create-dropdown-setting.dto";
|
||||
import { ListDropdownSettingsQueryDto } from "./dto/list-dropdown-settings-query.dto";
|
||||
import { UpdateDropdownOptionDto } from "./dto/update-dropdown-option.dto";
|
||||
import { UpdateDropdownSettingDto } from "./dto/update-dropdown-setting.dto";
|
||||
import { DropdownOption } from "./entities/dropdown-option.entity";
|
||||
@@ -16,71 +19,6 @@ import {
|
||||
IDropdownSettingsRepository,
|
||||
} from "./interfaces/dropdown-settings.repository.interface";
|
||||
|
||||
const STATIONS_TER_CODE = "stations_ter";
|
||||
|
||||
const DEFAULT_STATION_OPTIONS: CreateDropdownOptionDto[] = [
|
||||
{
|
||||
value: "inside_addis_ababa",
|
||||
label: "Addis Ababa",
|
||||
note: "Inside country",
|
||||
order: 1,
|
||||
},
|
||||
{
|
||||
value: "inside_adama",
|
||||
label: "Adama",
|
||||
note: "Inside country",
|
||||
order: 2,
|
||||
},
|
||||
{
|
||||
value: "inside_mojo",
|
||||
label: "Mojo",
|
||||
note: "Inside country",
|
||||
order: 3,
|
||||
},
|
||||
{
|
||||
value: "inside_awash",
|
||||
label: "Awash",
|
||||
note: "Inside country",
|
||||
order: 4,
|
||||
},
|
||||
{
|
||||
value: "inside_mieso",
|
||||
label: "Mieso",
|
||||
note: "Inside country",
|
||||
order: 5,
|
||||
},
|
||||
{
|
||||
value: "inside_dire_dawa",
|
||||
label: "Dire Dawa",
|
||||
note: "Inside country",
|
||||
order: 6,
|
||||
},
|
||||
{
|
||||
value: "outside_ali_sabieh",
|
||||
label: "Ali Sabieh",
|
||||
note: "Outside country",
|
||||
order: 7,
|
||||
},
|
||||
{
|
||||
value: "outside_holhol",
|
||||
label: "Holhol",
|
||||
note: "Outside country",
|
||||
order: 8,
|
||||
},
|
||||
{
|
||||
value: "outside_djibouti_city",
|
||||
label: "Djibouti City",
|
||||
note: "Outside country",
|
||||
order: 9,
|
||||
},
|
||||
{
|
||||
value: "outside_doraleh_terminal",
|
||||
label: "Doraleh Terminal",
|
||||
note: "Outside country",
|
||||
order: 10,
|
||||
},
|
||||
];
|
||||
|
||||
@Injectable()
|
||||
export class DropdownSettingsService {
|
||||
constructor(
|
||||
@@ -92,6 +30,12 @@ export class DropdownSettingsService {
|
||||
return this.repository.findAll();
|
||||
}
|
||||
|
||||
listPaged(
|
||||
query: ListDropdownSettingsQueryDto,
|
||||
): Promise<PaginatedResponse<DropdownSetting>> {
|
||||
return this.repository.findPaged(query);
|
||||
}
|
||||
|
||||
async getById(id: string): Promise<DropdownSetting> {
|
||||
const setting = await this.repository.findById(id);
|
||||
if (!setting) throw new NotFoundException(`Setting ${id} not found`);
|
||||
@@ -127,34 +71,6 @@ export class DropdownSettingsService {
|
||||
return this.getById(setting.id);
|
||||
}
|
||||
|
||||
async seedDefaultStations(): Promise<void> {
|
||||
const existing = await this.repository.findByCode(STATIONS_TER_CODE);
|
||||
|
||||
if (!existing) {
|
||||
await this.create({
|
||||
code: STATIONS_TER_CODE,
|
||||
label: "Stations TER",
|
||||
description:
|
||||
"Temporary freight station list used by booking origin and destination yards.",
|
||||
multiple: false,
|
||||
meta: {
|
||||
searchable: true,
|
||||
clearable: true,
|
||||
version: "temporary",
|
||||
},
|
||||
children: DEFAULT_STATION_OPTIONS,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if ((existing.children?.length ?? 0) === 0) {
|
||||
await this.repository.replaceOptions(
|
||||
existing.id,
|
||||
DEFAULT_STATION_OPTIONS,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
dto: UpdateDropdownSettingDto,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { PaginationQueryDto } from "../../../common/dto/pagination-query.dto";
|
||||
|
||||
/**
|
||||
* Query params for the paged admin listing (`GET /dropdown-settings/paged`).
|
||||
* `search` matches code, label and description server-side. The entity has no
|
||||
* status/isActive flag, so the base pagination fields are all that's needed.
|
||||
*/
|
||||
export class ListDropdownSettingsQueryDto extends PaginationQueryDto {}
|
||||
@@ -1,3 +1,6 @@
|
||||
import { PaginatedResponse } from "@edr/types";
|
||||
|
||||
import { ListDropdownSettingsQueryDto } from "../dto/list-dropdown-settings-query.dto";
|
||||
import { DropdownOption } from "../entities/dropdown-option.entity";
|
||||
import { DropdownSetting } from "../entities/dropdown-setting.entity";
|
||||
|
||||
@@ -11,6 +14,9 @@ export const DROPDOWN_SETTINGS_REPOSITORY = Symbol(
|
||||
|
||||
export interface IDropdownSettingsRepository {
|
||||
findAll(): Promise<DropdownSetting[]>;
|
||||
findPaged(
|
||||
query: ListDropdownSettingsQueryDto,
|
||||
): Promise<PaginatedResponse<DropdownSetting>>;
|
||||
findById(id: string): Promise<DropdownSetting | null>;
|
||||
findByCode(code: string): Promise<DropdownSetting | null>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user