import { BaseEntity } from '@edr/api-common'; import { SchedulingStatus } from '@edr/types'; import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm'; // import { Customer } from '../../customers/entities/customer.entity'; import { Company } from '../../companies/entities/company.entity'; import { CargoType } from '../../rule-engine/entities/cargo-type.entity'; import { ServiceType } from '../../rule-engine/entities/service-type.entity'; import { ShippingLine } from '../../rule-engine/entities/shipping-line.entity'; import { Yard } from '../../rule-engine/entities/yard.entity'; import { Train } from '../../trains/entities/train.entity'; 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 { BookingRateSnapshot } from './booking-rate-snapshot.entity'; import { BookingReviewNote } from './booking-review-note.entity'; export const BOOKING_STATUSES = [ 'DRAFT', 'SUBMITTED', 'PRICE_CHANGED_PENDING_CONFIRM', 'CHANGES_REQUESTED', 'PENDING_APPROVAL', 'APPROVED_PENDING_SIGNATURE', 'APPROVED', 'CONTRACT_READY', 'SIGNED_CUSTOMER', 'FULLY_EXECUTED', 'PNR_GENERATED', 'PAYMENT_VERIFICATION_IN_PROGRESS', 'PAID', 'IN_TRANSIT', 'COMPLETED', 'REJECTED', 'CANCELLED', 'PENDING_CONSOLIDATION', 'CONSOLIDATED', ] as const; export type BookingStatus = (typeof BOOKING_STATUSES)[number]; export const PAYMENT_STATUSES = [ 'PENDING', 'PNR_GENERATED', 'VERIFICATION_IN_PROGRESS', 'PAID', 'FAILED', ] as const; export type PaymentStatus = (typeof PAYMENT_STATUSES)[number]; export const FREIGHT_TYPES = ['CONTAINER', 'BULK'] as const; export type FreightType = (typeof FREIGHT_TYPES)[number]; export const SCHEDULING_STATUSES = [ SchedulingStatus.NotScheduled, SchedulingStatus.Holding, SchedulingStatus.Eligible, SchedulingStatus.Scheduled, SchedulingStatus.Dispatched, ] as const; export type BookingSchedulingStatus = (typeof SCHEDULING_STATUSES)[number]; /** Statuses where the customer may edit booking fields. */ export const CUSTOMER_EDITABLE_STATUSES: BookingStatus[] = [ 'DRAFT', 'CHANGES_REQUESTED', ]; @Entity({ schema: 'freight', name: 'bookings' }) export class Booking extends BaseEntity { @Column({ name: 'reference', type: 'varchar', length: 64, unique: true }) reference!: string; // Legacy — superseded by companyId (column kept in DB) // @Column({ name: 'customer_id', type: 'uuid' }) // customerId!: string; // @ManyToOne(() => Customer) // @JoinColumn({ name: 'customer_id' }) // customer?: Customer; @Column({ name: 'company_id', type: 'uuid', nullable: true }) companyId?: string | null; @ManyToOne(() => Company, { nullable: true }) @JoinColumn({ name: 'company_id' }) company?: Company | null; @Column({ name: 'is_government', type: 'boolean', default: false }) isGovernment!: boolean; @Column({ name: 'government_institution', type: 'varchar', length: 255, nullable: true }) governmentInstitution?: string | null; /** @deprecated Fleet master data link — scheduling uses train_schedule_bookings instead. */ @Column({ name: 'train_id', type: 'uuid', nullable: true }) trainId?: string | null; /** @deprecated Use train_schedule_bookings for operational scheduling. */ @ManyToOne(() => Train, { nullable: true }) @JoinColumn({ name: 'train_id' }) train?: Train | null; @Column({ name: 'status', type: 'varchar', length: 40, default: 'DRAFT' }) status!: string; @Column({ name: 'scheduled_date', type: 'timestamptz' }) scheduledDate!: Date; @Column({ name: 'total_amount', type: 'numeric', precision: 14, scale: 2, default: 0 }) totalAmount!: number; @Column({ name: 'payment_status', type: 'varchar', length: 20, default: 'PENDING' }) paymentStatus!: string; @Column({ name: 'contract_type', type: 'varchar', length: 20 }) contractType!: string; @Column({ name: 'previous_contract_id', type: 'uuid', nullable: true }) previousContractId?: string | null; @ManyToOne(() => Booking, { nullable: true }) @JoinColumn({ name: 'previous_contract_id' }) previousContract?: Booking | null; @Column({ name: 'service_type_id', type: 'uuid' }) serviceTypeId!: string; @ManyToOne(() => ServiceType) @JoinColumn({ name: 'service_type_id' }) serviceType?: ServiceType; @Column({ name: 'first_mile_pickup_address', type: 'text', nullable: true }) firstMilePickupAddress?: string | null; @Column({ name: 'last_mile_delivery_address', type: 'text', nullable: true }) lastMileDeliveryAddress?: string | null; @Column({ name: 'equipment_return', type: 'varchar', length: 20 }) equipmentReturn!: string; @Column({ name: 'origin_yard_id', type: 'uuid' }) originYardId!: string; @ManyToOne(() => Yard) @JoinColumn({ name: 'origin_yard_id' }) originYard?: Yard; @Column({ name: 'destination_yard_id', type: 'uuid' }) destinationYardId!: string; @ManyToOne(() => Yard) @JoinColumn({ name: 'destination_yard_id' }) destinationYard?: Yard; @Column({ name: 'trade_direction', type: 'varchar', length: 10 }) tradeDirection!: string; @Column({ name: 'freight_type', type: 'varchar', length: 20 }) freightType!: string; @Column({ name: 'cargo_type_id', type: 'uuid', nullable: true }) cargoTypeId?: string | null; @ManyToOne(() => CargoType) @JoinColumn({ name: 'cargo_type_id' }) cargoType?: CargoType; @Column({ name: 'cargo_free_text', type: 'varchar', length: 200, nullable: true }) cargoFreeText?: string | null; @Column({ name: 'shipping_line_id', type: 'uuid', nullable: true }) shippingLineId?: string | null; @ManyToOne(() => ShippingLine, { nullable: true }) @JoinColumn({ name: 'shipping_line_id' }) shippingLine?: ShippingLine | null; @Column({ name: 'cargo_total_weight_vgm', type: 'numeric', precision: 12, scale: 3 }) cargoTotalWeightVgm!: number; @Column({ name: 'is_hazardous', type: 'boolean', default: false }) isHazardous!: boolean; @Column({ name: 'payment_currency', type: 'varchar', length: 5 }) paymentCurrency!: string; @Column({ name: 'pnr_code', type: 'varchar', length: 50, nullable: true }) pnrCode?: string | null; @Column({ name: 'start_date', type: 'date', nullable: true }) startDate?: Date | null; @Column({ name: 'end_date', type: 'date', nullable: true }) endDate?: Date | null; @Column({ name: 'financial_terms', type: 'text', nullable: true }) financialTerms?: string | null; @Column({ name: 'version_number', type: 'int', default: 1 }) versionNumber!: number; @Column({ name: 'approved_by_staff_id', type: 'uuid', nullable: true }) approvedByStaffId?: string | null; @Column({ name: 'approved_by_staff_at', type: 'timestamptz', nullable: true }) approvedByStaffAt?: Date | null; @Column({ name: 'signed_by_director_id', type: 'uuid', nullable: true }) signedByDirectorId?: string | null; @Column({ name: 'signed_by_director_at', type: 'timestamptz', nullable: true }) signedByDirectorAt?: Date | null; @Column({ name: 'signed_by_ceo_id', type: 'uuid', nullable: true }) signedByCeoId?: string | null; @Column({ name: 'signed_by_ceo_at', type: 'timestamptz', nullable: true }) signedByCeoAt?: Date | null; @Column({ name: 'customer_signed_at', type: 'timestamptz', nullable: true }) customerSignedAt?: Date | null; @Column({ name: 'fully_executed_at', type: 'timestamptz', nullable: true }) fullyExecutedAt?: Date | null; @Column({ name: 'marketing_approved_by_id', type: 'uuid', nullable: true }) marketingApprovedById?: string | null; @Column({ name: 'marketing_approved_at', type: 'timestamptz', nullable: true }) marketingApprovedAt?: Date | null; @Column({ name: 'contract_summary', type: 'text', nullable: true }) contractSummary?: string | null; @Column({ name: 'contract_template_key', type: 'varchar', length: 80, nullable: true }) contractTemplateKey?: string | null; @Column({ name: 'contract_generated_at', type: 'timestamptz', nullable: true }) contractGeneratedAt?: Date | null; @Column({ name: 'pricing_breakdown', type: 'jsonb', nullable: true }) pricingBreakdown?: Record | null; @Column({ name: 'locked_at', type: 'timestamptz', nullable: true }) lockedAt?: Date | null; @Column({ name: 'priority_score', type: 'int', default: 0 }) priorityScore!: number; @Column({ name: 'allow_consolidation', type: 'boolean', default: false }) allowConsolidation!: boolean; @Column({ name: 'consolidation_partner_id', type: 'uuid', nullable: true }) consolidationPartnerId?: string | null; @ManyToOne(() => Booking, { nullable: true }) @JoinColumn({ name: 'consolidation_partner_id' }) consolidationPartner?: Booking | null; @Column({ name: 'wagons_required', type: 'numeric', precision: 6, scale: 2, nullable: true }) wagonsRequired?: number | null; @Column({ name: 'scheduling_status', type: 'varchar', length: 30, default: 'NOT_SCHEDULED' }) schedulingStatus!: string; @Column({ name: 'hold_started_at', type: 'timestamptz', nullable: true }) holdStartedAt?: Date | null; @Column({ name: 'hold_expires_at', type: 'timestamptz', nullable: true }) holdExpiresAt?: Date | null; @Column({ name: 'scheduled_at', type: 'timestamptz', nullable: true }) scheduledAt?: Date | null; @OneToMany(() => BookingContainer, (bc) => bc.booking) bookingContainers?: BookingContainer[]; @OneToMany(() => BookingCargoModifier, (m) => m.booking) cargoModifiers?: BookingCargoModifier[]; @OneToMany(() => BookingApprovalStep, (s) => s.booking) approvalSteps?: BookingApprovalStep[]; @OneToMany(() => BookingRateSnapshot, (s) => s.booking) rateSnapshots?: BookingRateSnapshot[]; @OneToMany(() => BookingReviewNote, (n) => n.booking) reviewNotes?: BookingReviewNote[]; @OneToMany(() => FileRecord, (file) => file.resourceId, { createForeignKeyConstraints: false, }) files?: FileRecord[]; }