This commit is contained in:
natib21
2026-06-29 15:09:34 +00:00
77 changed files with 4308 additions and 1839 deletions

View File

@@ -12,7 +12,7 @@ import { Freight, SchedulingStatus } from '@edr/types';
// import { CustomersService } from '../customers/customers.service';
import { CompaniesService } from '../companies/companies.service';
import { ProfileType } from '../companies/entities/company-profile.entity';
import { CompanyStatus } from '../companies/entities/company.entity';
import { CompanyKind, CompanyStatus } from '../companies/entities/company.entity';
import { TrainSchedulingService } from '../train-scheduling/train-scheduling.service';
import { eatDay } from '../train-scheduling/batch-window.util';
import { FilesService } from '../files/files.service';
@@ -308,10 +308,23 @@ export class BookingsService {
let companyId: string | null | undefined = dto.companyId;
if (isGovernment) {
if (!dto.governmentInstitution?.trim()) {
throw new BadRequestException('governmentInstitution is required for government bookings');
// Government bookings bill to a real seeded government company + an
// explicitly-chosen importer/exporter profile (no more null company +
// free-text institution).
if (!dto.companyId) {
throw new BadRequestException('A government company is required for government bookings');
}
companyId = dto.companyId ?? null;
const govCompany = await this.companiesService.findCompanyById(dto.companyId);
if (govCompany.kind !== CompanyKind.Government) {
throw new BadRequestException('Selected company is not a government entity');
}
if (govCompany.status !== CompanyStatus.Active) {
throw new BadRequestException('Selected government company is not active');
}
if (!dto.companyProfileId) {
throw new BadRequestException('A government company profile is required for government bookings');
}
companyId = govCompany.id;
} else if (!companyId) {
if (!userId) {
throw new BadRequestException(
@@ -384,7 +397,16 @@ export class BookingsService {
// so the customer portal can scope lists/KPIs to the active mode. Best-effort
// for non-government bookings with a resolved company; never blocks creation.
let companyProfileId: string | null = null;
if (!isGovernment && companyId) {
if (dto.companyProfileId && companyId) {
// Explicit profile pin (government booking, or staff booking on behalf):
// must belong to the chosen company and be active.
const profile =
await this.companiesService.getActiveCompanyProfileForBooking(
companyId,
dto.companyProfileId,
);
companyProfileId = profile.id;
} else if (companyId) {
let fallbackType: ProfileType | null = null;
if (userId) {
try {
@@ -414,6 +436,16 @@ export class BookingsService {
}
}
// Every booking must link to a company and a company profile.
if (!companyId) {
throw new BadRequestException('A company is required to create a booking');
}
if (!companyProfileId) {
throw new BadRequestException(
'A company profile is required to create a booking — none could be resolved for this company',
);
}
const needsConsolidation =
dto.freightType === 'CONTAINER'
? await this.needsConsolidation(containers)
@@ -444,10 +476,10 @@ export class BookingsService {
const booking = await this.bookingsRepository.create({
reference,
companyId: companyId ?? null,
companyId,
companyProfileId,
isGovernment,
governmentInstitution: isGovernment ? dto.governmentInstitution!.trim() : null,
governmentInstitution: dto.governmentInstitution?.trim() || null,
trainId: dto.trainId,
trainScheduleId: dto.trainScheduleId ?? null,
contractType: dto.contractType,