mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 09:58:12 +00:00
fix wagon cncellation
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
/**
|
||||
* Partial wagon cancellation with rebooking credit.
|
||||
*
|
||||
* One row per cancellation cycle on a PAID booking: the customer asks to drop
|
||||
* N wagons, pays a per-wagon cancellation fee (rates row
|
||||
* rate_type = 'CANCELLATION_FEE', rate_unit = 'PER_WAGON'), and the dropped cargo becomes a
|
||||
* rebookable credit. The credit is redeemed by creating a fresh booking
|
||||
* through the normal under-contract create path (which re-checks contract
|
||||
* validity and caps), immediately marked PAID — the freight was already paid
|
||||
* on the original booking, only the fee is new money.
|
||||
*
|
||||
* cancelled_quantities carries what was cut, in the booking's own terms:
|
||||
* `{ bulkTons }` for bulk, `{ bySize: { "20": 4, "40": 3 } }` for container.
|
||||
* Container numbers are NOT stored here — they are recovered at rebook time
|
||||
* from the unit rows the reduction soft-deleted (same hybrid pattern as
|
||||
* RemainderPlacementService).
|
||||
*/
|
||||
export class BookingWagonCancellations3300000000000 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS freight.booking_wagon_cancellations (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
booking_id uuid NOT NULL REFERENCES freight.bookings(id),
|
||||
rebooked_booking_id uuid REFERENCES freight.bookings(id),
|
||||
wagons_cancelled numeric(6,2) NOT NULL CHECK (wagons_cancelled > 0),
|
||||
weight_tons numeric(12,3) NOT NULL DEFAULT 0,
|
||||
cancelled_quantities jsonb NOT NULL,
|
||||
credit_amount numeric(14,2) NOT NULL DEFAULT 0,
|
||||
fee_rate_id uuid REFERENCES freight.rates(id),
|
||||
fee_amount numeric(14,2) NOT NULL CHECK (fee_amount >= 0),
|
||||
fee_currency varchar(8) NOT NULL DEFAULT 'ETB',
|
||||
fee_invoice_id uuid REFERENCES freight.invoices(id),
|
||||
fee_paid_at timestamptz,
|
||||
status varchar(30) NOT NULL DEFAULT 'FEE_PENDING',
|
||||
reason text,
|
||||
requested_by_user_id uuid,
|
||||
rebooked_at timestamptz,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
deleted_at timestamptz
|
||||
)
|
||||
`);
|
||||
// One open (fee-unpaid) cancellation per booking — closes the double-click
|
||||
// race without app-level locking.
|
||||
await queryRunner.query(`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_open_wagon_cancellation_per_booking
|
||||
ON freight.booking_wagon_cancellations (booking_id)
|
||||
WHERE status = 'FEE_PENDING' AND deleted_at IS NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS idx_bwc_booking
|
||||
ON freight.booking_wagon_cancellations (booking_id)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS idx_bwc_status
|
||||
ON freight.booking_wagon_cancellations (status)
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS freight.booking_wagon_cancellations`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user