mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 21:08:12 +00:00
feat: add government support to the companies. add seeding and constrain on booking
This commit is contained in:
@@ -11,7 +11,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';
|
||||
@@ -299,10 +299,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(
|
||||
@@ -375,7 +388,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 {
|
||||
@@ -405,6 +427,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)
|
||||
@@ -435,10 +467,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,
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
Max,
|
||||
MaxLength,
|
||||
Min,
|
||||
MinLength,
|
||||
Validate,
|
||||
ValidateIf,
|
||||
ValidateNested,
|
||||
@@ -104,19 +103,31 @@ export class CreateBookingDto {
|
||||
@Transform(({ value }) => value === 'true' || value === true)
|
||||
isGovernment?: boolean;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Required when isGovernment is true' })
|
||||
@ValidateIf((o) => o.isGovernment === true)
|
||||
/** @deprecated Government bookings now bill to a real government company. */
|
||||
@ApiPropertyOptional({ description: 'Deprecated: free-text institution (superseded by companyId)' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(2)
|
||||
@Transform(({ value }) => (typeof value === 'string' ? value.trim() : value))
|
||||
governmentInstitution?: string;
|
||||
|
||||
@ApiPropertyOptional({ format: 'uuid', description: 'Admin only: target company' })
|
||||
@ValidateIf((o) => o.isGovernment !== true)
|
||||
@ApiPropertyOptional({
|
||||
format: 'uuid',
|
||||
description:
|
||||
'Target company. Required for staff/government bookings; resolved from the auth token for customer self-bookings.',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
companyId?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
format: 'uuid',
|
||||
description:
|
||||
'Explicit company profile (importer/exporter). Required for government bookings; commercial bookings auto-resolve from trade direction.',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
companyProfileId?: string;
|
||||
|
||||
@ApiPropertyOptional({ format: 'uuid' })
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
|
||||
@@ -105,8 +105,10 @@ export class Booking extends BaseEntity {
|
||||
// @JoinColumn({ name: 'customer_id' })
|
||||
// customer?: Customer;
|
||||
|
||||
@Column({ name: 'company_id', type: 'uuid', nullable: true })
|
||||
companyId?: string | null;
|
||||
// Every booking is billed to a company — government bookings bill to a seeded
|
||||
// government company (companies.kind = 'government'). Enforced NOT NULL.
|
||||
@Column({ name: 'company_id', type: 'uuid' })
|
||||
companyId!: string;
|
||||
|
||||
@ManyToOne(() => Company, { nullable: true })
|
||||
@JoinColumn({ name: 'company_id' })
|
||||
@@ -116,11 +118,12 @@ export class Booking extends BaseEntity {
|
||||
* The operational profile (importer/exporter/forwarder) this booking belongs
|
||||
* to. Stamped at creation from the booking's trade direction (IMPORT→importer,
|
||||
* EXPORT→exporter) or the user's active profile for DOMESTIC/forwarder.
|
||||
* Customer portal lists and dashboard KPIs are scoped by this. Nullable for
|
||||
* legacy/government/staff-created bookings.
|
||||
* Customer portal lists and dashboard KPIs are scoped by this. Required:
|
||||
* commercial bookings resolve it from trade direction / active mode;
|
||||
* government bookings carry the explicitly-picked government profile.
|
||||
*/
|
||||
@Column({ name: 'company_profile_id', type: 'uuid', nullable: true })
|
||||
companyProfileId?: string | null;
|
||||
@Column({ name: 'company_profile_id', type: 'uuid' })
|
||||
companyProfileId!: string;
|
||||
|
||||
@ManyToOne(() => CompanyProfile, { nullable: true })
|
||||
@JoinColumn({ name: 'company_profile_id' })
|
||||
|
||||
Reference in New Issue
Block a user