import { BaseEntity } from '@edr/api-common'; import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; import { Booking } from '../../bookings/entities/booking.entity'; import { TrainSchedule } from '../../train-schedules/entities/train-schedule.entity'; export const BOOKING_BATCH_OFFER_STATUSES = ['OFFERED', 'APPLIED', 'EXPIRED'] as const; export type BookingBatchOfferStatus = (typeof BOOKING_BATCH_OFFER_STATUSES)[number]; /** One reduced container line of a partial offer (per original booking_container row). */ export interface OfferedLine { bookingContainerId: string; /** Units of this line that ride the offered train (≤ original quantity). */ quantity: number; wagonsRequired: number; totalVgmTons: number; } /** * A partial-capacity payment offer made by the batch when a booking needs more * wagons than the train has left (e.g. needs 20, 3 free). The booking itself is * NOT mutated at offer time — paying inside the window accepts the split * (BookingSplitService.applySplit reduces the booking to the offered lines and * the remainder returns to the contract's quantity cap); letting the deadline * pass expires the offer and the booking stays whole. */ @Entity({ schema: 'freight', name: 'booking_batch_offers' }) @Index(['bookingId']) @Index(['trainScheduleId']) @Index(['status']) export class BookingBatchOffer extends BaseEntity { @Column({ name: 'booking_id', type: 'uuid' }) bookingId!: string; @ManyToOne(() => Booking, { onDelete: 'CASCADE' }) @JoinColumn({ name: 'booking_id' }) booking?: Booking; @Column({ name: 'train_schedule_id', type: 'uuid' }) trainScheduleId!: string; @ManyToOne(() => TrainSchedule, { onDelete: 'CASCADE' }) @JoinColumn({ name: 'train_schedule_id' }) trainSchedule?: TrainSchedule; @Column({ name: 'offered_wagons', type: 'int' }) offeredWagons!: number; /** Booking's full wagon need at offer time (for messaging / audit). */ @Column({ name: 'total_wagons', type: 'int' }) totalWagons!: number; /** Reduced container lines (null for bulk offers — bulk splits by weight). */ @Column({ name: 'offered_lines', type: 'jsonb', nullable: true }) offeredLines?: OfferedLine[] | null; @Column({ name: 'offered_weight_tons', type: 'numeric', precision: 12, scale: 3 }) offeredWeightTons!: number; @Column({ name: 'offered_amount', type: 'numeric', precision: 14, scale: 2 }) offeredAmount!: number; @Column({ name: 'offered_pricing_breakdown', type: 'jsonb', nullable: true }) offeredPricingBreakdown?: Record | null; /** The partial PREPAID invoice generated for the offered part. */ @Column({ name: 'invoice_id', type: 'uuid', nullable: true }) invoiceId?: string | null; @Column({ name: 'payment_deadline', type: 'timestamptz' }) paymentDeadline!: Date; @Column({ name: 'status', type: 'varchar', length: 10, default: 'OFFERED' }) status!: BookingBatchOfferStatus; }