mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 22:58:17 +00:00
booking operations and trains scheduling also allocations
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { SchedulingStatus } from '@edr/types';
|
||||
// import { CustomersService } from '../customers/customers.service';
|
||||
import { CompaniesService } from '../companies/companies.service';
|
||||
import { FilesService } from '../files/files.service';
|
||||
@@ -64,6 +65,7 @@ export class BookingsService {
|
||||
paymentCurrency: string;
|
||||
tradeDirection: string;
|
||||
isHazardous?: boolean;
|
||||
isGovernment?: boolean;
|
||||
allowConsolidation?: boolean;
|
||||
shippingLineId?: string | null;
|
||||
containers: CreateBookingContainerDto[];
|
||||
@@ -92,6 +94,7 @@ export class BookingsService {
|
||||
paymentCurrency: dto.paymentCurrency,
|
||||
tradeDirection: dto.tradeDirection,
|
||||
isHazardous: dto.isHazardous ?? false,
|
||||
isGovernment: dto.isGovernment ?? false,
|
||||
allowConsolidation:
|
||||
dto.freightType === 'CONTAINER' ? dto.allowConsolidation : false,
|
||||
shippingLineId: dto.shippingLineId,
|
||||
@@ -178,8 +181,15 @@ export class BookingsService {
|
||||
// customerId = customer.id;
|
||||
// }
|
||||
|
||||
let companyId = dto.companyId;
|
||||
if (!companyId) {
|
||||
const isGovernment = dto.isGovernment === true;
|
||||
|
||||
let companyId: string | null | undefined = dto.companyId;
|
||||
if (isGovernment) {
|
||||
if (!dto.governmentInstitution?.trim()) {
|
||||
throw new BadRequestException('governmentInstitution is required for government bookings');
|
||||
}
|
||||
companyId = dto.companyId ?? null;
|
||||
} else if (!companyId) {
|
||||
if (!userId) {
|
||||
throw new BadRequestException(
|
||||
'companyId is required or must be resolvable from auth token',
|
||||
@@ -209,6 +219,7 @@ export class BookingsService {
|
||||
paymentCurrency: dto.paymentCurrency,
|
||||
tradeDirection: dto.tradeDirection,
|
||||
isHazardous: dto.isHazardous,
|
||||
isGovernment,
|
||||
allowConsolidation,
|
||||
shippingLineId: dto.shippingLineId,
|
||||
containers,
|
||||
@@ -220,7 +231,9 @@ export class BookingsService {
|
||||
|
||||
const booking = await this.bookingsRepository.create({
|
||||
reference,
|
||||
companyId,
|
||||
companyId: companyId ?? null,
|
||||
isGovernment,
|
||||
governmentInstitution: isGovernment ? dto.governmentInstitution!.trim() : null,
|
||||
trainId: dto.trainId,
|
||||
contractType: dto.contractType,
|
||||
previousContractId: dto.previousContractId,
|
||||
@@ -300,12 +313,13 @@ export class BookingsService {
|
||||
const freightType = (dto.freightType ?? existing.freightType) as FreightType;
|
||||
let containers =
|
||||
dto.containers ??
|
||||
existing.bookingContainers?.map((bc) => ({
|
||||
containerTypeId: bc.containerTypeId,
|
||||
quantity: bc.quantity,
|
||||
vgmPerUnitTons: Number(bc.vgmPerUnitTons),
|
||||
})) ??
|
||||
[];
|
||||
(existing.bookingContainers ?? [])
|
||||
.filter((bc): bc is typeof bc & { containerTypeId: string } => bc.containerTypeId != null)
|
||||
.map((bc) => ({
|
||||
containerTypeId: bc.containerTypeId,
|
||||
quantity: bc.quantity,
|
||||
vgmPerUnitTons: Number(bc.vgmPerUnitTons),
|
||||
}));
|
||||
|
||||
let cargoTypeId =
|
||||
dto.cargoTypeId !== undefined ? dto.cargoTypeId : existing.cargoTypeId;
|
||||
@@ -403,6 +417,19 @@ export class BookingsService {
|
||||
return { booking, warnings };
|
||||
}
|
||||
|
||||
/** Parse comma-separated scheduling status query values. */
|
||||
private parseSchedulingStatusFilter(filter: FilterBookingDto): {
|
||||
schedulingStatuses?: string[];
|
||||
} {
|
||||
const raw = filter.schedulingStatuses;
|
||||
if (!raw) return {};
|
||||
const schedulingStatuses = raw
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
return schedulingStatuses.length ? { schedulingStatuses } : {};
|
||||
}
|
||||
|
||||
/** Parse comma-separated or repeated status query values. */
|
||||
private parseStatusFilter(filter: FilterBookingDto): {
|
||||
statuses?: string[];
|
||||
@@ -433,11 +460,14 @@ export class BookingsService {
|
||||
const page = filter.page ?? 1;
|
||||
const pageSize = filter.pageSize ?? 20;
|
||||
const statusFilter = this.parseStatusFilter(filter);
|
||||
const schedulingStatusFilter = this.parseSchedulingStatusFilter(filter);
|
||||
|
||||
return this.bookingsRepository.findAllPaginated({
|
||||
page,
|
||||
pageSize,
|
||||
...statusFilter,
|
||||
...schedulingStatusFilter,
|
||||
assignedToSchedule: filter.assignedToSchedule,
|
||||
companyId: filter.companyId,
|
||||
contractType: filter.contractType,
|
||||
serviceTypeId: filter.serviceTypeId,
|
||||
@@ -695,12 +725,13 @@ export class BookingsService {
|
||||
return true;
|
||||
}
|
||||
if (dto.containers !== undefined) {
|
||||
const existingContainers =
|
||||
existing.bookingContainers?.map((bc) => ({
|
||||
const existingContainers = (existing.bookingContainers ?? [])
|
||||
.filter((bc): bc is typeof bc & { containerTypeId: string } => bc.containerTypeId != null)
|
||||
.map((bc) => ({
|
||||
containerTypeId: bc.containerTypeId,
|
||||
quantity: bc.quantity,
|
||||
vgmPerUnitTons: Number(bc.vgmPerUnitTons),
|
||||
})) ?? [];
|
||||
}));
|
||||
if (JSON.stringify(existingContainers) !== JSON.stringify(containers)) {
|
||||
return true;
|
||||
}
|
||||
@@ -714,4 +745,32 @@ export class BookingsService {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Staff expedite: mark a government booking PAID and ready for scheduling (no commercial hold). */
|
||||
async governmentExpedite(id: string, staffUserId: string): Promise<Booking> {
|
||||
const booking = await this.findById(id);
|
||||
if (!booking.isGovernment) {
|
||||
throw new BadRequestException('Only government bookings can be expedited');
|
||||
}
|
||||
const blocked = ['PAID', 'IN_TRANSIT', 'COMPLETED', 'CANCELLED', 'REJECTED'];
|
||||
if (blocked.includes(booking.status)) {
|
||||
throw new BadRequestException(`Cannot expedite booking in status ${booking.status}`);
|
||||
}
|
||||
|
||||
await this.bookingsRepository.update(id, {
|
||||
status: 'PAID',
|
||||
paymentStatus: 'PAID',
|
||||
schedulingStatus: SchedulingStatus.Eligible,
|
||||
holdStartedAt: null,
|
||||
holdExpiresAt: null,
|
||||
});
|
||||
await this.bookingsRepository.createReviewNote(
|
||||
id,
|
||||
`Government booking expedited to PAID by staff (${staffUserId})`,
|
||||
'STAFF_NOTE',
|
||||
staffUserId,
|
||||
);
|
||||
|
||||
return this.findById(id);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user