mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
141 lines
5.2 KiB
TypeScript
141 lines
5.2 KiB
TypeScript
import { BaseEntity } from '@edr/api-common';
|
|
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
|
|
|
|
import { Invoice } from '../../billing/entities/invoice.entity';
|
|
import { Rate } from '../../rule-engine/entities/rate.entity';
|
|
import { Booking } from './booking.entity';
|
|
|
|
/** `invoices.type` of the wagon-cancellation fee invoice — the settlement branch key in BookingInvoiceService. */
|
|
export const WAGON_CANCEL_FEE_INVOICE_TYPE = 'WAGON_CANCEL_FEE';
|
|
|
|
export const WAGON_CANCELLATION_STATUSES = [
|
|
// Requested; fee invoice open; wagons still allocated to the customer.
|
|
'FEE_PENDING',
|
|
// Fee settled; booking reduced, wagons freed; credit waiting for a rebook.
|
|
'CREDIT_AVAILABLE',
|
|
// Credit redeemed into a new PAID booking (rebookedBookingId).
|
|
'REBOOKED',
|
|
// Customer/staff voided the request before paying the fee. Nothing changed.
|
|
'WITHDRAWN',
|
|
// Reserved for a future expiry policy; not set by code today.
|
|
'EXPIRED',
|
|
] as const;
|
|
|
|
export type WagonCancellationStatus = (typeof WAGON_CANCELLATION_STATUSES)[number];
|
|
|
|
/** Snapshot of one physical container unit cut by the cancellation. */
|
|
export interface CancelledUnitSnapshot {
|
|
containerSize: string;
|
|
containerNumber: string;
|
|
sealNumber?: string | null;
|
|
vgmTons: number;
|
|
isHazardous: boolean;
|
|
isReefer: boolean;
|
|
}
|
|
|
|
/** What the cancellation cut, in the booking's own quantity terms. */
|
|
export interface CancelledQuantities {
|
|
/** Bulk bookings: tons cut (PER_ITEM cargo: item count, matching cargoTotalWeightVgm). */
|
|
bulkTons?: number;
|
|
/** Container bookings: units cut per container size. */
|
|
bySize?: Record<string, number>;
|
|
/**
|
|
* Container bookings: the exact physical units cut. Snapshotted at request
|
|
* time when the customer picked specific wagons, otherwise at fee settlement
|
|
* (LIFO trim). The rebook reconstructs the new booking from THESE — never
|
|
* from a soft-deleted-row scan, which could pick up units dropped by an
|
|
* unrelated batch split on the same booking.
|
|
*/
|
|
units?: CancelledUnitSnapshot[];
|
|
/**
|
|
* Specific-wagon cancellation: the wagon_booking_allocation ids the customer
|
|
* picked in the Wagons tab. T2 releases exactly these (fallback to
|
|
* newest-first for any id that no longer exists, e.g. after a re-batch).
|
|
*/
|
|
allocationIds?: string[];
|
|
/**
|
|
* The wagon allocations were already released from the schedule at REQUEST
|
|
* time (policy: wagons free up immediately; the fee is still owed before the
|
|
* credit can be rebooked). Tells T2 to skip its release step so it never
|
|
* deletes wagons the batch engine re-assigned in between.
|
|
*/
|
|
releasedAtRequest?: boolean;
|
|
}
|
|
|
|
/**
|
|
* One partial-wagon-cancellation cycle on a PAID booking — the audit trail and
|
|
* the state machine. The credit itself is not a wallet balance: redeeming it
|
|
* creates a real booking through the under-contract create path and marks it
|
|
* PAID (see BookingWagonCancellationService).
|
|
*/
|
|
@Entity({ schema: 'freight', name: 'booking_wagon_cancellations' })
|
|
@Index(['bookingId'])
|
|
@Index(['status'])
|
|
export class BookingWagonCancellation extends BaseEntity {
|
|
@Column({ name: 'booking_id', type: 'uuid' })
|
|
bookingId!: string;
|
|
|
|
@ManyToOne(() => Booking)
|
|
@JoinColumn({ name: 'booking_id' })
|
|
booking?: Booking;
|
|
|
|
@Column({ name: 'rebooked_booking_id', type: 'uuid', nullable: true })
|
|
rebookedBookingId?: string | null;
|
|
|
|
@ManyToOne(() => Booking, { nullable: true })
|
|
@JoinColumn({ name: 'rebooked_booking_id' })
|
|
rebookedBooking?: Booking | null;
|
|
|
|
@Column({ name: 'wagons_cancelled', type: 'numeric', precision: 6, scale: 2 })
|
|
wagonsCancelled!: number;
|
|
|
|
@Column({ name: 'weight_tons', type: 'numeric', precision: 12, scale: 3, default: 0 })
|
|
weightTons!: number;
|
|
|
|
@Column({ name: 'cancelled_quantities', type: 'jsonb' })
|
|
cancelledQuantities!: CancelledQuantities;
|
|
|
|
/**
|
|
* The freight value of the cancelled part at the ORIGINAL booking's price —
|
|
* informational (shown to the customer as "credit worth"); no refund is ever
|
|
* issued from it, the credit is redeemed by rebooking.
|
|
*/
|
|
@Column({ name: 'credit_amount', type: 'numeric', precision: 14, scale: 2, default: 0 })
|
|
creditAmount!: number;
|
|
|
|
@Column({ name: 'fee_rate_id', type: 'uuid', nullable: true })
|
|
feeRateId?: string | null;
|
|
|
|
@ManyToOne(() => Rate, { nullable: true })
|
|
@JoinColumn({ name: 'fee_rate_id' })
|
|
feeRate?: Rate | null;
|
|
|
|
@Column({ name: 'fee_amount', type: 'numeric', precision: 14, scale: 2 })
|
|
feeAmount!: number;
|
|
|
|
@Column({ name: 'fee_currency', type: 'varchar', length: 8, default: 'ETB' })
|
|
feeCurrency!: string;
|
|
|
|
@Column({ name: 'fee_invoice_id', type: 'uuid', nullable: true })
|
|
feeInvoiceId?: string | null;
|
|
|
|
@ManyToOne(() => Invoice, { nullable: true })
|
|
@JoinColumn({ name: 'fee_invoice_id' })
|
|
feeInvoice?: Invoice | null;
|
|
|
|
@Column({ name: 'fee_paid_at', type: 'timestamptz', nullable: true })
|
|
feePaidAt?: Date | null;
|
|
|
|
@Column({ name: 'status', type: 'varchar', length: 30, default: 'FEE_PENDING' })
|
|
status!: string;
|
|
|
|
@Column({ name: 'reason', type: 'text', nullable: true })
|
|
reason?: string | null;
|
|
|
|
@Column({ name: 'requested_by_user_id', type: 'uuid', nullable: true })
|
|
requestedByUserId?: string | null;
|
|
|
|
@Column({ name: 'rebooked_at', type: 'timestamptz', nullable: true })
|
|
rebookedAt?: Date | null;
|
|
}
|