mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
Merge pull request #60 from Tria-plc/freight_feature/booking/minio
add reference-data
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { In, Not } from 'typeorm';
|
||||
|
||||
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';
|
||||
|
||||
const LEGACY_YARD_CODES = ['LEGACY_ORIGIN', 'LEGACY_DEST'] as const;
|
||||
|
||||
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,
|
||||
show_free_text_box: child.showFreeTextBox,
|
||||
}),
|
||||
);
|
||||
|
||||
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,
|
||||
wagons_per_unit: Number(ct.wagonsPerUnit ?? 1),
|
||||
}),
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
@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,
|
||||
code: Not(In([...LEGACY_YARD_CODES])),
|
||||
},
|
||||
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 => ({
|
||||
id: s.id,
|
||||
name: s.serviceName,
|
||||
code: s.code,
|
||||
}),
|
||||
),
|
||||
shipping_line: shippingLines.map(
|
||||
(sl): BookingReferenceShippingLineDto => ({
|
||||
id: sl.id,
|
||||
name: sl.label,
|
||||
code: sl.code,
|
||||
}),
|
||||
),
|
||||
cargo_type: buildCargoTypeTree(cargoTypes),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,14 @@ import {
|
||||
ApiBearerAuth,
|
||||
ApiBody,
|
||||
ApiConsumes,
|
||||
ApiOkResponse,
|
||||
ApiOperation,
|
||||
ApiTags,
|
||||
} from "@nestjs/swagger";
|
||||
|
||||
import { BookingReferenceDataService } from "./booking-reference-data.service";
|
||||
import { BookingsService } from "./bookings.service";
|
||||
import { BookingReferenceDataDto } from "./dto/booking-reference-data.dto";
|
||||
import { CreateBookingDto } from "./dto/create-booking.dto";
|
||||
import { FilterBookingDto } from "./dto/filter-booking.dto";
|
||||
import { UpdateBookingDto } from "./dto/update-booking.dto";
|
||||
@@ -32,7 +35,10 @@ import { UpdateStatusDto } from "./dto/update-status.dto";
|
||||
@Controller("bookings")
|
||||
@ApiBearerAuth()
|
||||
export class BookingsController {
|
||||
constructor(private readonly bookingsService: BookingsService) { }
|
||||
constructor(
|
||||
private readonly bookingsService: BookingsService,
|
||||
private readonly bookingReferenceDataService: BookingReferenceDataService,
|
||||
) {}
|
||||
|
||||
// ── 1. Create booking (multipart/form-data) ──────────────────────────
|
||||
@Post()
|
||||
@@ -100,6 +106,19 @@ export class BookingsController {
|
||||
return this.bookingsService.findAll(filter);
|
||||
}
|
||||
|
||||
// ── Booking form catalog (must be before :id) ─────────────────────────
|
||||
@Get("reference-data")
|
||||
@ApiOperation({
|
||||
summary: "Booking form catalog",
|
||||
description:
|
||||
"Returns yards, container types (grouped by size), service types, shipping lines, " +
|
||||
"and hierarchical cargo types for the booking UI in a single payload.",
|
||||
})
|
||||
@ApiOkResponse({ type: BookingReferenceDataDto })
|
||||
getReferenceData(): Promise<BookingReferenceDataDto> {
|
||||
return this.bookingReferenceDataService.getReferenceData();
|
||||
}
|
||||
|
||||
// ── 5. Lookup by reference (must be before :id to avoid conflict) ─────
|
||||
@Get("by-reference/:reference")
|
||||
@ApiOperation({
|
||||
|
||||
@@ -5,6 +5,7 @@ import { CustomersModule } from '../customers/customers.module';
|
||||
import { FilesModule } from '../files/files.module';
|
||||
import { MinioModule } from '../minio/minio.module';
|
||||
import { RuleEngineModule } from '../rule-engine/rule-engine.module';
|
||||
import { BookingReferenceDataService } from './booking-reference-data.service';
|
||||
import { BookingsController } from './bookings.controller';
|
||||
import { BookingsRepository } from './bookings.repository';
|
||||
import { ConsolidationService } from './consolidation.service';
|
||||
@@ -30,7 +31,12 @@ import { Booking } from './entities/booking.entity';
|
||||
RuleEngineModule,
|
||||
],
|
||||
controllers: [BookingsController],
|
||||
providers: [BookingsService, BookingsRepository, ConsolidationService],
|
||||
providers: [
|
||||
BookingsService,
|
||||
BookingsRepository,
|
||||
ConsolidationService,
|
||||
BookingReferenceDataService,
|
||||
],
|
||||
exports: [BookingsService],
|
||||
})
|
||||
export class BookingsModule {}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
|
||||
export class BookingReferenceYardDto {
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
id!: string;
|
||||
|
||||
@ApiProperty({ example: 'Mojo Dry Port' })
|
||||
name!: string;
|
||||
|
||||
@ApiProperty({ example: 'MOJO' })
|
||||
code!: string;
|
||||
|
||||
@ApiProperty({ example: 'Ethiopia' })
|
||||
country!: string;
|
||||
}
|
||||
|
||||
export class BookingReferenceContainerTypeDto {
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
id!: string;
|
||||
|
||||
@ApiProperty({ example: 'Dry' })
|
||||
name!: string;
|
||||
|
||||
@ApiProperty({ example: '20GP' })
|
||||
code!: string;
|
||||
|
||||
@ApiProperty()
|
||||
is_reefer!: boolean;
|
||||
|
||||
@ApiProperty({ example: 0.5, description: 'Wagon fraction per container' })
|
||||
wagons_per_unit!: number;
|
||||
}
|
||||
|
||||
export class BookingReferenceContainerSizeGroupDto {
|
||||
@ApiProperty({ example: '20ft' })
|
||||
size!: string;
|
||||
|
||||
@ApiProperty({ type: [BookingReferenceContainerTypeDto] })
|
||||
types!: BookingReferenceContainerTypeDto[];
|
||||
}
|
||||
|
||||
export class BookingReferenceServiceDto {
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
id!: string;
|
||||
|
||||
@ApiProperty({ example: 'Rail Transport Only' })
|
||||
name!: string;
|
||||
|
||||
@ApiProperty({ example: 'RAIL' })
|
||||
code!: string;
|
||||
}
|
||||
|
||||
export class BookingReferenceShippingLineDto {
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
id!: string;
|
||||
|
||||
@ApiProperty({ example: 'MSC' })
|
||||
name!: string;
|
||||
|
||||
@ApiProperty({ example: 'MSC' })
|
||||
code!: string;
|
||||
}
|
||||
|
||||
export class BookingReferenceCargoTypeChildDto {
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
id!: string;
|
||||
|
||||
@ApiProperty({ example: 'Coffee' })
|
||||
name!: string;
|
||||
|
||||
@ApiProperty({ example: 'BULK_COFFEE' })
|
||||
code!: string;
|
||||
|
||||
@ApiProperty()
|
||||
show_free_text_box!: boolean;
|
||||
}
|
||||
|
||||
export class BookingReferenceCargoTypeGroupDto {
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
id!: string;
|
||||
|
||||
@ApiProperty({ example: 'Bulk Cargo' })
|
||||
name!: string;
|
||||
|
||||
@ApiProperty({ example: 'BULK' })
|
||||
code!: string;
|
||||
|
||||
@ApiPropertyOptional({ type: [BookingReferenceCargoTypeChildDto] })
|
||||
children?: BookingReferenceCargoTypeChildDto[];
|
||||
}
|
||||
|
||||
export class BookingReferenceDataDto {
|
||||
@ApiProperty({ type: [BookingReferenceYardDto] })
|
||||
yard!: BookingReferenceYardDto[];
|
||||
|
||||
@ApiProperty({ type: [BookingReferenceContainerSizeGroupDto] })
|
||||
containers!: BookingReferenceContainerSizeGroupDto[];
|
||||
|
||||
@ApiProperty({ type: [BookingReferenceServiceDto] })
|
||||
service!: BookingReferenceServiceDto[];
|
||||
|
||||
@ApiProperty({ type: [BookingReferenceShippingLineDto] })
|
||||
shipping_line!: BookingReferenceShippingLineDto[];
|
||||
|
||||
@ApiProperty({ type: [BookingReferenceCargoTypeGroupDto] })
|
||||
cargo_type!: BookingReferenceCargoTypeGroupDto[];
|
||||
}
|
||||
@@ -140,6 +140,11 @@ import { BookingRateSnapshot } from '../bookings/entities/booking-rate-snapshot.
|
||||
ShippingLinesService,
|
||||
RatesService,
|
||||
ApprovalRulesService,
|
||||
CARGO_TYPES_REPOSITORY,
|
||||
CONTAINER_TYPES_REPOSITORY,
|
||||
SERVICE_TYPES_REPOSITORY,
|
||||
SHIPPING_LINES_REPOSITORY,
|
||||
YARDS_REPOSITORY,
|
||||
],
|
||||
})
|
||||
export class RuleEngineModule {}
|
||||
|
||||
Reference in New Issue
Block a user