Files
edr-platform/apps/edr-freight-api/src/modules/bookings/booking-reference-data.service.ts
Marshal 771aa2a605 Add train deactivation feature and import train number management
- Introduced DEACTIVATED status for trains, allowing staff to park trains indefinitely.
- Implemented methods to deactivate and reactivate trains in the TrainBuilderService.
- Added UI components for train deactivation and reactivation in TrainBuilderDetailPage.
- Created a dropdown setting for admin-managed import train numbers, with corresponding migrations.
- Updated yard code length to accommodate soft-delete suffix.
- Enhanced train status handling to include DEACTIVATED state.
2026-07-20 07:04:23 +00:00

182 lines
5.4 KiB
TypeScript

import { Inject, Injectable } from "@nestjs/common";
import { CargoType } from "../rule-engine/entities/cargo-type.entity";
import { ContainerType } from "../rule-engine/entities/container-type.entity";
import {
CARGO_TYPES_REPOSITORY,
ICargoTypesRepository,
} from "../rule-engine/interfaces/cargo-types.repository.interface";
import {
CONTAINER_TYPES_REPOSITORY,
IContainerTypesRepository,
} from "../rule-engine/interfaces/container-types.repository.interface";
import {
IServiceTypesRepository,
SERVICE_TYPES_REPOSITORY,
} from "../rule-engine/interfaces/service-types.repository.interface";
import {
IShippingLinesRepository,
SHIPPING_LINES_REPOSITORY,
} from "../rule-engine/interfaces/shipping-lines.repository.interface";
import {
IYardsRepository,
YARDS_REPOSITORY,
} from "../rule-engine/interfaces/yards.repository.interface";
import {
BookingReferenceCargoTypeChildDto,
BookingReferenceCargoTypeGroupDto,
BookingReferenceContainerSizeGroupDto,
BookingReferenceContainerTypeDto,
BookingReferenceDataDto,
BookingReferenceServiceDto,
BookingReferenceShippingLineDto,
BookingReferenceYardDto,
} from "./dto/booking-reference-data.dto";
export function buildCargoTypeTree(
rows: CargoType[],
): BookingReferenceCargoTypeGroupDto[] {
const active = rows.filter((r) => r.isActive);
const parents = active
.filter((r) => !r.parentGroupId)
.sort(
(a, b) => a.displayOrder - b.displayOrder || a.code.localeCompare(b.code),
);
return parents.map((parent) => {
const children = active
.filter((r) => r.parentGroupId === parent.id)
.sort(
(a, b) =>
a.displayOrder - b.displayOrder || a.code.localeCompare(b.code),
)
.map(
(child): BookingReferenceCargoTypeChildDto => ({
id: child.id,
name: child.cargoTypeName,
code: child.code,
unit_of_measure: child.unitOfMeasure ?? null,
}),
);
const group: BookingReferenceCargoTypeGroupDto = {
id: parent.id,
name: parent.cargoTypeName,
code: parent.code,
};
if (children.length > 0) {
group.children = children;
}
return group;
});
}
export function groupContainersBySize(
rows: ContainerType[],
): BookingReferenceContainerSizeGroupDto[] {
const active = rows.filter((r) => r.isActive);
const bySize = new Map<string, ContainerType[]>();
for (const ct of active) {
const sizeKey =
ct.sizeFt != null && ct.sizeFt > 0 ? `${ct.sizeFt}ft` : "other";
const list = bySize.get(sizeKey) ?? [];
list.push(ct);
bySize.set(sizeKey, list);
}
const sortSizeKey = (key: string): number => {
if (key === "other") return Number.MAX_SAFE_INTEGER;
const n = parseInt(key, 10);
return Number.isNaN(n) ? Number.MAX_SAFE_INTEGER - 1 : n;
};
return [...bySize.entries()]
.sort(([a], [b]) => sortSizeKey(a) - sortSizeKey(b))
.map(([size, types]) => ({
size,
types: types
.sort(
(a, b) =>
(a.displayOrder ?? 0) - (b.displayOrder ?? 0) ||
a.code.localeCompare(b.code),
)
.map(
(ct): BookingReferenceContainerTypeDto => ({
id: ct.id,
name: ct.label?.trim() ? ct.label : ct.code,
code: ct.code,
is_reefer: ct.isReefer ?? false,
}),
),
}));
}
@Injectable()
export class BookingReferenceDataService {
constructor(
@Inject(YARDS_REPOSITORY)
private readonly yardsRepository: IYardsRepository,
@Inject(CONTAINER_TYPES_REPOSITORY)
private readonly containerTypesRepository: IContainerTypesRepository,
@Inject(SERVICE_TYPES_REPOSITORY)
private readonly serviceTypesRepository: IServiceTypesRepository,
@Inject(SHIPPING_LINES_REPOSITORY)
private readonly shippingLinesRepository: IShippingLinesRepository,
@Inject(CARGO_TYPES_REPOSITORY)
private readonly cargoTypesRepository: ICargoTypesRepository,
) { }
async getReferenceData(): Promise<BookingReferenceDataDto> {
const [yards, containerTypes, serviceTypes, shippingLines, cargoTypes] =
await Promise.all([
this.yardsRepository.findAll({
where: { isActive: true },
order: { displayOrder: "ASC", code: "ASC" },
}),
this.containerTypesRepository.findAll({
where: { isActive: true },
order: { displayOrder: "ASC", code: "ASC" },
}),
this.serviceTypesRepository.findAll({
where: { isActive: true },
order: { displayOrder: "ASC", code: "ASC" },
}),
this.shippingLinesRepository.findAll({
where: { isActive: true },
order: { label: "ASC", code: "ASC" },
}),
this.cargoTypesRepository.findAll({
where: { isActive: true },
order: { displayOrder: "ASC", code: "ASC" },
}),
]);
return {
yard: yards.map(
(y): BookingReferenceYardDto => ({
id: y.id,
name: y.label,
code: y.code,
country: y.country,
}),
),
containers: groupContainersBySize(containerTypes),
service: serviceTypes.map(
(s): BookingReferenceServiceDto => ({
name: s.serviceName,
...s,
}),
),
shipping_line: shippingLines.map(
(sl): BookingReferenceShippingLineDto => ({
id: sl.id,
name: sl.label,
code: sl.code,
}),
),
cargo_type: buildCargoTypeTree(cargoTypes),
};
}
}