mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 09:00:57 +00:00
fix
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
import { Body, Controller, Param, ParseUUIDPipe, Post } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { BookingsService } from './bookings.service';
|
||||
import { AllocateContainersDto } from './dto/allocate-containers.dto';
|
||||
|
||||
@ApiTags('bookings')
|
||||
@Controller('bookings')
|
||||
@ApiBearerAuth()
|
||||
export class BookingAllocationController {
|
||||
constructor(private readonly bookingsService: BookingsService) {}
|
||||
|
||||
@Post(':bookingId/allocate-containers')
|
||||
@ApiOperation({ summary: 'Allocate containers to vehicles' })
|
||||
async allocateContainers(
|
||||
@Param('bookingId', ParseUUIDPipe) bookingId: string,
|
||||
@Body() dto: AllocateContainersDto,
|
||||
) {
|
||||
return this.bookingsService.allocateContainers(bookingId, dto.allocations);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import { BookingPricingService } from './booking-pricing.service';
|
||||
import { BookingReferenceDataService } from './booking-reference-data.service';
|
||||
import { BookingTransitionService } from './booking-transition.service';
|
||||
import { BookingsController } from './bookings.controller';
|
||||
import { BookingAllocationController } from './booking-allocation.controller';
|
||||
import { PayController } from './pay.controller';
|
||||
import { BookingsRepository } from './bookings.repository';
|
||||
import { ConsolidationService } from './consolidation.service';
|
||||
@@ -28,6 +29,7 @@ import { BookingRateSnapshot } from './entities/booking-rate-snapshot.entity';
|
||||
import { BookingContractSignature } from './entities/booking-contract-signature.entity';
|
||||
import { BookingReviewNote } from './entities/booking-review-note.entity';
|
||||
import { Booking } from './entities/booking.entity';
|
||||
import { BookingContainerAllocation } from './entities/booking-container-allocation.entity';
|
||||
import { ContractPdfService } from '../../contracts/contract-pdf.service';
|
||||
import { ContractPricingScheduleBuilder } from '../../contracts/contract-pricing-schedule.builder';
|
||||
import { ContractRendererService } from '../../contracts/contract-renderer.service';
|
||||
@@ -47,6 +49,7 @@ import { TrainSchedulingModule } from '../train-scheduling/train-scheduling.modu
|
||||
BookingRateSnapshot,
|
||||
BookingReviewNote,
|
||||
BookingContractSignature,
|
||||
BookingContainerAllocation,
|
||||
]),
|
||||
PaymentModule,
|
||||
forwardRef(() => TrainSchedulingModule),
|
||||
@@ -63,7 +66,7 @@ import { TrainSchedulingModule } from '../train-scheduling/train-scheduling.modu
|
||||
config.get<ExchangeOptions>('app.cbeExchange') ?? {},
|
||||
}),
|
||||
],
|
||||
controllers: [BookingsController, PayController],
|
||||
controllers: [BookingsController, BookingAllocationController, PayController],
|
||||
providers: [
|
||||
BookingsService,
|
||||
BookingsRepository,
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
FreightType,
|
||||
} from './entities/booking.entity';
|
||||
import { Booking } from './entities/booking.entity';
|
||||
import { BookingContainerAllocation } from './entities/booking-container-allocation.entity';
|
||||
import { FileRecord } from '../files/entities/file.entity';
|
||||
|
||||
/** Paginated booking list: flat `total` (backoffice) + `meta` block (portal). */
|
||||
@@ -1305,4 +1306,35 @@ export class BookingsService {
|
||||
createdAt: b.createdAt,
|
||||
}));
|
||||
}
|
||||
|
||||
async allocateContainers(
|
||||
bookingId: string,
|
||||
allocations: Array<{ containerId: string; vehicleId: string }>,
|
||||
) {
|
||||
const booking = await this.findById(bookingId);
|
||||
if (!booking) {
|
||||
throw new NotFoundException(`Booking ${bookingId} not found`);
|
||||
}
|
||||
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
for (const allocation of allocations) {
|
||||
await manager.delete(BookingContainerAllocation, {
|
||||
bookingId,
|
||||
containerId: allocation.containerId,
|
||||
});
|
||||
await manager.insert(BookingContainerAllocation, {
|
||||
bookingId,
|
||||
containerId: allocation.containerId,
|
||||
vehicleId: allocation.vehicleId,
|
||||
containerType: 'CONTAINER',
|
||||
quantity: 1,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
allocated: allocations.length,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export class ContainerAllocationDto {
|
||||
containerId!: string;
|
||||
vehicleId!: string;
|
||||
}
|
||||
|
||||
export class AllocateContainersDto {
|
||||
allocations!: ContainerAllocationDto[];
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { BaseEntity } from '@edr/api-common';
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
|
||||
import { Booking } from './booking.entity';
|
||||
import { Vehicle } from '../../vehicles/entities/vehicle.entity';
|
||||
|
||||
@Entity({ schema: 'freight', name: 'booking_container_allocations' })
|
||||
@Index(['bookingId'])
|
||||
@Index(['vehicleId'])
|
||||
export class BookingContainerAllocation extends BaseEntity {
|
||||
@ManyToOne(() => Booking, (b) => b.containerAllocations)
|
||||
@JoinColumn({ name: 'booking_id' })
|
||||
booking!: Booking;
|
||||
|
||||
@Column('uuid', { name: 'booking_id' })
|
||||
bookingId!: string;
|
||||
|
||||
@Column('uuid', { name: 'container_id' })
|
||||
containerId!: string;
|
||||
|
||||
@ManyToOne(() => Vehicle)
|
||||
@JoinColumn({ name: 'vehicle_id' })
|
||||
vehicle!: Vehicle;
|
||||
|
||||
@Column('uuid', { name: 'vehicle_id', nullable: true })
|
||||
vehicleId?: string;
|
||||
|
||||
@Column('text')
|
||||
containerType!: string; // CONTAINER, BULK_DRY, etc
|
||||
|
||||
@Column('integer', { default: 1 })
|
||||
quantity!: number;
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import { FileRecord } from '../../files/entities/file.entity';
|
||||
import { BookingApprovalStep } from './booking-approval-step.entity';
|
||||
import { BookingCargoModifier } from './booking-cargo-modifier.entity';
|
||||
import { BookingContainer } from './booking-container.entity';
|
||||
import { BookingContainerAllocation } from './booking-container-allocation.entity';
|
||||
import { BookingRateSnapshot } from './booking-rate-snapshot.entity';
|
||||
import { BookingReviewNote } from './booking-review-note.entity';
|
||||
|
||||
@@ -441,6 +442,9 @@ export class Booking extends BaseEntity {
|
||||
@OneToMany(() => BookingContainer, (bc) => bc.booking)
|
||||
bookingContainers?: BookingContainer[];
|
||||
|
||||
@OneToMany(() => BookingContainerAllocation, (ca) => ca.booking)
|
||||
containerAllocations?: BookingContainerAllocation[];
|
||||
|
||||
@OneToMany(() => BookingCargoModifier, (m) => m.booking)
|
||||
cargoModifiers?: BookingCargoModifier[];
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export class FirstMileContainerAllocationDto {
|
||||
containerId!: string;
|
||||
vehicleId!: string;
|
||||
}
|
||||
|
||||
export class AllocateFirstMileContainersDto {
|
||||
allocations!: FirstMileContainerAllocationDto[];
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { BaseEntity } from '@edr/api-common';
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
|
||||
|
||||
import { FirstMile } from './first-mile.entity';
|
||||
import { Vehicle } from '../../vehicles/entities/vehicle.entity';
|
||||
|
||||
@Entity({ name: 'first_mile_container_allocations', schema: 'freight' })
|
||||
@Index(['firstMileId'])
|
||||
@Index(['vehicleId'])
|
||||
export class FirstMileContainerAllocation extends BaseEntity {
|
||||
@Column({ name: 'first_mile_id', type: 'uuid' })
|
||||
firstMileId!: string;
|
||||
|
||||
@ManyToOne(() => FirstMile, (firstMile) => firstMile.containerAllocations, {
|
||||
nullable: false,
|
||||
eager: false,
|
||||
})
|
||||
@JoinColumn({ name: 'first_mile_id' })
|
||||
firstMile?: FirstMile;
|
||||
|
||||
@Column({ name: 'container_id', type: 'uuid' })
|
||||
containerId!: string;
|
||||
|
||||
@Column({ name: 'vehicle_id', type: 'uuid', nullable: true })
|
||||
vehicleId?: string | null;
|
||||
|
||||
@ManyToOne(() => Vehicle, { nullable: true, eager: false })
|
||||
@JoinColumn({ name: 'vehicle_id' })
|
||||
vehicle?: Vehicle | null;
|
||||
|
||||
@Column({ name: 'container_type', type: 'text' })
|
||||
containerType!: string;
|
||||
|
||||
@Column({ name: 'quantity', type: 'int', default: 1 })
|
||||
quantity!: number;
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { BaseEntity } from '@edr/api-common';
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
|
||||
|
||||
import { Booking } from '../../bookings/entities/booking.entity';
|
||||
import { Vehicle } from '../../vehicles/entities/vehicle.entity';
|
||||
import { FirstMileContainerAllocation } from './first-mile-container-allocation.entity';
|
||||
|
||||
export const FIRST_MILE_STATUSES = [
|
||||
'PAYMENT_PENDING',
|
||||
@@ -49,4 +50,11 @@ export class FirstMile extends BaseEntity {
|
||||
@ManyToOne(() => Vehicle, { nullable: true, eager: false })
|
||||
@JoinColumn({ name: 'vehicle_id' })
|
||||
vehicle?: Vehicle | null;
|
||||
|
||||
@OneToMany(
|
||||
() => FirstMileContainerAllocation,
|
||||
(containerAllocation) => containerAllocation.firstMile,
|
||||
{ eager: false },
|
||||
)
|
||||
containerAllocations!: FirstMileContainerAllocation[];
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { TrainSchedulingManage, TrainSchedulingView } from '../../common/booking
|
||||
|
||||
import { CreateFirstMileDto } from './dto/create-first-mile.dto';
|
||||
import { UpdateFirstMileDto } from './dto/update-first-mile.dto';
|
||||
import { AllocateFirstMileContainersDto } from './dto/allocate-containers.dto';
|
||||
import { FirstMileStatus } from './entities/first-mile.entity';
|
||||
import { FirstMileService } from './first-mile.service';
|
||||
|
||||
@@ -83,4 +84,14 @@ export class FirstMileController {
|
||||
remove(@Param('id', ParseUUIDPipe) id: string) {
|
||||
return this.firstMileService.remove(id);
|
||||
}
|
||||
|
||||
@Post(':firstMileId/allocate-containers')
|
||||
@TrainSchedulingManage()
|
||||
@ApiOperation({ summary: 'Allocate containers to vehicles for a first-mile leg' })
|
||||
allocateContainers(
|
||||
@Param('firstMileId', ParseUUIDPipe) firstMileId: string,
|
||||
@Body() dto: AllocateFirstMileContainersDto,
|
||||
) {
|
||||
return this.firstMileService.allocateContainers(firstMileId, dto.allocations);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@ import { DriversModule } from '../drivers/drivers.module';
|
||||
import { NotificationsModule } from '../notifications/notifications.module';
|
||||
import { VehiclesModule } from '../vehicles/vehicles.module';
|
||||
import { FirstMile } from './entities/first-mile.entity';
|
||||
import { FirstMileContainerAllocation } from './entities/first-mile-container-allocation.entity';
|
||||
import { FirstMileController } from './first-mile.controller';
|
||||
import { FirstMileRepository } from './first-mile.repository';
|
||||
import { FirstMileService } from './first-mile.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([FirstMile]),
|
||||
TypeOrmModule.forFeature([FirstMile, FirstMileContainerAllocation]),
|
||||
forwardRef(() => BookingsModule),
|
||||
VehiclesModule,
|
||||
DriversModule,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { BadRequestException, ConflictException, Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||
import { FindOptionsWhere } from 'typeorm';
|
||||
import { InjectDataSource } from '@nestjs/typeorm';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { BookingsRepository } from '../bookings/bookings.repository';
|
||||
import { DriversService } from '../drivers/drivers.service';
|
||||
@@ -8,6 +10,7 @@ import { VehiclesService } from '../vehicles/vehicles.service';
|
||||
import { CreateFirstMileDto } from './dto/create-first-mile.dto';
|
||||
import { UpdateFirstMileDto } from './dto/update-first-mile.dto';
|
||||
import { FirstMile, FirstMileStatus } from './entities/first-mile.entity';
|
||||
import { FirstMileContainerAllocation } from './entities/first-mile-container-allocation.entity';
|
||||
import { FirstMileRepository } from './first-mile.repository';
|
||||
|
||||
type FirstMileListFilter = {
|
||||
@@ -32,6 +35,7 @@ export class FirstMileService {
|
||||
private readonly logger = new Logger(FirstMileService.name);
|
||||
|
||||
constructor(
|
||||
@InjectDataSource() private readonly dataSource: DataSource,
|
||||
private readonly firstMileRepository: FirstMileRepository,
|
||||
private readonly bookingsRepository: BookingsRepository,
|
||||
private readonly vehiclesService: VehiclesService,
|
||||
@@ -273,4 +277,35 @@ export class FirstMileService {
|
||||
await this.findById(id);
|
||||
await this.firstMileRepository.softDelete(id);
|
||||
}
|
||||
|
||||
async allocateContainers(
|
||||
firstMileId: string,
|
||||
allocations: Array<{ containerId: string; vehicleId: string }>,
|
||||
) {
|
||||
const firstMile = await this.findById(firstMileId);
|
||||
if (!firstMile) {
|
||||
throw new NotFoundException(`First-mile record ${firstMileId} not found`);
|
||||
}
|
||||
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
for (const allocation of allocations) {
|
||||
await manager.delete(FirstMileContainerAllocation, {
|
||||
firstMileId,
|
||||
containerId: allocation.containerId,
|
||||
});
|
||||
await manager.insert(FirstMileContainerAllocation, {
|
||||
firstMileId,
|
||||
containerId: allocation.containerId,
|
||||
vehicleId: allocation.vehicleId,
|
||||
containerType: 'CONTAINER',
|
||||
quantity: 1,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
allocated: allocations.length,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export class LastMileContainerAllocationDto {
|
||||
containerId!: string;
|
||||
vehicleId!: string;
|
||||
}
|
||||
|
||||
export class AllocateLastMileContainersDto {
|
||||
allocations!: LastMileContainerAllocationDto[];
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { BaseEntity } from '@edr/api-common';
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
|
||||
import { LastMile } from './last-mile.entity';
|
||||
import { Vehicle } from '../../vehicles/entities/vehicle.entity';
|
||||
|
||||
@Entity({ schema: 'freight', name: 'last_mile_container_allocations' })
|
||||
@Index(['lastMileId'])
|
||||
@Index(['vehicleId'])
|
||||
export class LastMileContainerAllocation extends BaseEntity {
|
||||
@ManyToOne(() => LastMile, (lm) => lm.containerAllocations)
|
||||
@JoinColumn({ name: 'last_mile_id' })
|
||||
lastMile!: LastMile;
|
||||
|
||||
@Column('uuid', { name: 'last_mile_id' })
|
||||
lastMileId!: string;
|
||||
|
||||
@Column('uuid', { name: 'container_id' })
|
||||
containerId!: string;
|
||||
|
||||
@ManyToOne(() => Vehicle)
|
||||
@JoinColumn({ name: 'vehicle_id' })
|
||||
vehicle?: Vehicle | null;
|
||||
|
||||
@Column('uuid', { name: 'vehicle_id', nullable: true })
|
||||
vehicleId?: string | null;
|
||||
|
||||
@Column('text')
|
||||
containerType!: string;
|
||||
|
||||
@Column('integer', { default: 1 })
|
||||
quantity!: number;
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { BaseEntity } from '@edr/api-common';
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
|
||||
|
||||
import { Booking } from '../../bookings/entities/booking.entity';
|
||||
import { Vehicle } from '../../vehicles/entities/vehicle.entity';
|
||||
import { LastMileContainerAllocation } from './last-mile-container-allocation.entity';
|
||||
|
||||
export const LAST_MILE_STATUSES = [
|
||||
'PAYMENT_PENDING',
|
||||
@@ -49,4 +50,7 @@ export class LastMile extends BaseEntity {
|
||||
@ManyToOne(() => Vehicle, { nullable: true, eager: false })
|
||||
@JoinColumn({ name: 'vehicle_id' })
|
||||
vehicle?: Vehicle | null;
|
||||
|
||||
@OneToMany(() => LastMileContainerAllocation, (ca) => ca.lastMile)
|
||||
containerAllocations?: LastMileContainerAllocation[];
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { TrainSchedulingManage, TrainSchedulingView } from '../../common/booking
|
||||
|
||||
import { CreateLastMileDto } from './dto/create-last-mile.dto';
|
||||
import { UpdateLastMileDto } from './dto/update-last-mile.dto';
|
||||
import { AllocateLastMileContainersDto } from './dto/allocate-containers.dto';
|
||||
import { LastMileStatus } from './entities/last-mile.entity';
|
||||
import { LastMileService } from './last-mile.service';
|
||||
|
||||
@@ -83,4 +84,14 @@ export class LastMileController {
|
||||
remove(@Param('id', ParseUUIDPipe) id: string) {
|
||||
return this.lastMileService.remove(id);
|
||||
}
|
||||
|
||||
@Post(':id/allocate-containers')
|
||||
@TrainSchedulingManage()
|
||||
@ApiOperation({ summary: 'Allocate containers to vehicles' })
|
||||
async allocateContainers(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Body() dto: AllocateLastMileContainersDto,
|
||||
) {
|
||||
return this.lastMileService.allocateContainers(id, dto.allocations);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@ import { DriversModule } from '../drivers/drivers.module';
|
||||
import { NotificationsModule } from '../notifications/notifications.module';
|
||||
import { VehiclesModule } from '../vehicles/vehicles.module';
|
||||
import { LastMile } from './entities/last-mile.entity';
|
||||
import { LastMileContainerAllocation } from './entities/last-mile-container-allocation.entity';
|
||||
import { LastMileController } from './last-mile.controller';
|
||||
import { LastMileRepository } from './last-mile.repository';
|
||||
import { LastMileService } from './last-mile.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([LastMile]),
|
||||
TypeOrmModule.forFeature([LastMile, LastMileContainerAllocation]),
|
||||
forwardRef(() => BookingsModule),
|
||||
VehiclesModule,
|
||||
DriversModule,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||
import { FindOptionsWhere } from 'typeorm';
|
||||
import { DataSource, FindOptionsWhere } from 'typeorm';
|
||||
|
||||
import { BookingsRepository } from '../bookings/bookings.repository';
|
||||
import { DriversService } from '../drivers/drivers.service';
|
||||
@@ -8,6 +8,7 @@ import { VehiclesService } from '../vehicles/vehicles.service';
|
||||
import { CreateLastMileDto } from './dto/create-last-mile.dto';
|
||||
import { UpdateLastMileDto } from './dto/update-last-mile.dto';
|
||||
import { LastMile, LastMileStatus } from './entities/last-mile.entity';
|
||||
import { LastMileContainerAllocation } from './entities/last-mile-container-allocation.entity';
|
||||
import { LastMileRepository } from './last-mile.repository';
|
||||
|
||||
type LastMileListFilter = {
|
||||
@@ -37,6 +38,7 @@ export class LastMileService {
|
||||
private readonly vehiclesService: VehiclesService,
|
||||
private readonly driversService: DriversService,
|
||||
private readonly smsClient: SmsClientService,
|
||||
private readonly dataSource: DataSource,
|
||||
) {}
|
||||
|
||||
async acceptBooking(bookingReference: string): Promise<LastMile | null> {
|
||||
@@ -206,4 +208,35 @@ export class LastMileService {
|
||||
await this.findById(id);
|
||||
await this.lastMileRepository.softDelete(id);
|
||||
}
|
||||
|
||||
async allocateContainers(
|
||||
lastMileId: string,
|
||||
allocations: Array<{ containerId: string; vehicleId: string }>,
|
||||
) {
|
||||
const lastMile = await this.findById(lastMileId);
|
||||
if (!lastMile) {
|
||||
throw new NotFoundException(`Last-mile record ${lastMileId} not found`);
|
||||
}
|
||||
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
for (const allocation of allocations) {
|
||||
await manager.delete(LastMileContainerAllocation, {
|
||||
lastMileId,
|
||||
containerId: allocation.containerId,
|
||||
});
|
||||
await manager.insert(LastMileContainerAllocation, {
|
||||
lastMileId,
|
||||
containerId: allocation.containerId,
|
||||
vehicleId: allocation.vehicleId,
|
||||
containerType: 'CONTAINER',
|
||||
quantity: 1,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
allocated: allocations.length,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user