Files
edr-platform/apps/edr-freight-api/src/migrations/3470000000000-ShippingLineRates.ts
marshalyordanos 9fff469ffa feat: implement shipping line bookings management
- Add ShippingLineBookingsPage for listing and managing shipping line bookings.
- Create ShippingLineDocumentsModal for document uploads related to bookings.
- Introduce ShippingLineInitiateModal for initiating new shipping line bookings.
- Implement booking document state management with booking-doc-state utility.
- Add shipping line bookings service for API interactions.
- Update index to export new components and services.
- Enhance types for freight to include shipping line credits.
2026-08-13 15:54:40 +03:00

128 lines
5.7 KiB
TypeScript

import { MigrationInterface, QueryRunner } from 'typeorm';
/**
* Per-shipping-line rates.
*
* A shipping line books rail capacity directly (see BookingShippingLine3450000000000)
* and negotiates its own prices, so the rate table gains an owner column:
* `shipping_line_company_id` NULL = the standard rate every customer pays,
* NOT NULL = a rate that only that line's bookings resolve.
*
* Points at `freight.shipping_line_companies` (the portal account that owns the
* booking), NOT `freight.shipping_lines` — the latter is carrier reference data
* naming who physically moves the goods, and the existing SHIPPING_LINE trigger
* already keys off it. Both stay independent.
*
* Line rates OVERRIDE rather than stack: a booking owned by a line prices off
* that line's rate for the lane, and is hard-blocked when none exists (the
* standard rate is deliberately not a fallback — see RuleEngineService).
*
* Every existing row keeps a NULL owner, so nothing needs backfilling and the
* standard-rate lookups behave exactly as before.
*/
export class ShippingLineRates3470000000000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.rates
ADD COLUMN IF NOT EXISTS shipping_line_company_id uuid
`);
await queryRunner.query(`
ALTER TABLE freight.rates
DROP CONSTRAINT IF EXISTS "FK_rates_shipping_line_company"
`);
await queryRunner.query(`
ALTER TABLE freight.rates
ADD CONSTRAINT "FK_rates_shipping_line_company"
FOREIGN KEY (shipping_line_company_id)
REFERENCES freight.shipping_line_companies (id)
ON DELETE RESTRICT
`);
// Rate resolution always filters by owner, so the lookups this column
// participates in are (owner, lane) — indexed together with rate_type,
// which every lookup also pins.
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_rates_shipping_line_company_id
ON freight.rates (shipping_line_company_id)
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_rates_shipping_line_lane
ON freight.rates (shipping_line_company_id, rate_type, origin_yard_id, destination_yard_id)
WHERE shipping_line_company_id IS NOT NULL
`);
// A shipping line sells import freight only — the export leg is contracted
// through the customer, not the carrier. Enforced here so a line rate can
// never be filed against an export lane regardless of which API path wrote
// it. Surcharges carry no direction and are unaffected.
await queryRunner.query(`
ALTER TABLE freight.rates
DROP CONSTRAINT IF EXISTS "CK_rates_shipping_line_import_only"
`);
await queryRunner.query(`
ALTER TABLE freight.rates
ADD CONSTRAINT "CK_rates_shipping_line_import_only" CHECK (
deleted_at IS NOT NULL OR status = 'SUPERSEDED' OR
shipping_line_company_id IS NULL OR
trade_direction IS NULL OR trade_direction = 'IMPORT'
)
`);
// The owner joins the rate's identity. Without it MSC's 20ft Djibouti→Modjo
// rate collides with the standard rate for the same lane — same rate_type,
// same scope, same unit — and the insert fails on UQ_rates_pattern. NULL
// (the standard rate) collapses to the zero uuid like every other nullable
// scope column, so existing rows keep their current uniqueness exactly.
await queryRunner.query(`DROP INDEX IF EXISTS freight."UQ_rates_pattern"`);
await queryRunner.query(`
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_rates_pattern" ON freight.rates USING btree (
rate_type,
COALESCE(shipping_line_company_id, '00000000-0000-0000-0000-000000000000'::uuid),
COALESCE(container_type_id, '00000000-0000-0000-0000-000000000000'::uuid),
COALESCE(cargo_type_id, '00000000-0000-0000-0000-000000000000'::uuid),
COALESCE(trade_direction, ''::character varying),
COALESCE(origin_yard_id, '00000000-0000-0000-0000-000000000000'::uuid),
COALESCE(destination_yard_id, '00000000-0000-0000-0000-000000000000'::uuid),
rate_unit,
COALESCE(min_km, '-1'::numeric)
) WHERE ((deleted_at IS NULL) AND ((status)::text <> 'SUPERSEDED'::text))
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
// Restore the pre-owner pattern index (as left by LastMileRateBands).
await queryRunner.query(`DROP INDEX IF EXISTS freight."UQ_rates_pattern"`);
await queryRunner.query(`
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_rates_pattern" ON freight.rates USING btree (
rate_type,
COALESCE(container_type_id, '00000000-0000-0000-0000-000000000000'::uuid),
COALESCE(cargo_type_id, '00000000-0000-0000-0000-000000000000'::uuid),
COALESCE(trade_direction, ''::character varying),
COALESCE(origin_yard_id, '00000000-0000-0000-0000-000000000000'::uuid),
COALESCE(destination_yard_id, '00000000-0000-0000-0000-000000000000'::uuid),
rate_unit,
COALESCE(min_km, '-1'::numeric)
) WHERE ((deleted_at IS NULL) AND ((status)::text <> 'SUPERSEDED'::text))
`);
await queryRunner.query(`
ALTER TABLE freight.rates
DROP CONSTRAINT IF EXISTS "CK_rates_shipping_line_import_only"
`);
await queryRunner.query(
`DROP INDEX IF EXISTS freight.idx_rates_shipping_line_lane`,
);
await queryRunner.query(
`DROP INDEX IF EXISTS freight.idx_rates_shipping_line_company_id`,
);
await queryRunner.query(`
ALTER TABLE freight.rates
DROP CONSTRAINT IF EXISTS "FK_rates_shipping_line_company"
`);
await queryRunner.query(`
ALTER TABLE freight.rates DROP COLUMN IF EXISTS shipping_line_company_id
`);
}
}