mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 02:58:11 +00:00
- 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.
118 lines
4.8 KiB
TypeScript
118 lines
4.8 KiB
TypeScript
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
|
|
/**
|
|
* Shipping lines book rail capacity directly, without a contract.
|
|
*
|
|
* A booking has always been owned by `company_id` (a customer `companies` row),
|
|
* but a shipping line is a `shipping_line_companies` row and deliberately NOT a
|
|
* company — it carries no TIN, licence or operational profiles. So it gets its
|
|
* own nullable owner column rather than a synthetic company row.
|
|
*
|
|
* Exactly one of the two is set: `company_id` for a customer booking,
|
|
* `shipping_line_company_id` for a shipping-line one. Existing rows keep
|
|
* `company_id` and a NULL `shipping_line_company_id`, so nothing needs
|
|
* backfilling and every customer query filtering on `company_id` behaves
|
|
* exactly as before. Government bookings already bill to a seeded government
|
|
* company, so they satisfy the CHECK unchanged.
|
|
*
|
|
* NOTE: not to be confused with the existing `bookings.shipping_line_id`, which
|
|
* is cargo metadata naming the carrier line that moves the goods
|
|
* (`freight.shipping_lines`, reference data). This column points at
|
|
* `freight.shipping_line_companies` — the portal account — and is unrelated.
|
|
*/
|
|
export class BookingShippingLine3450000000000 implements MigrationInterface {
|
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
await queryRunner.query(`
|
|
ALTER TABLE freight.bookings
|
|
ADD COLUMN IF NOT EXISTS shipping_line_company_id uuid
|
|
`);
|
|
|
|
await queryRunner.query(`
|
|
CREATE INDEX IF NOT EXISTS idx_bookings_shipping_line_company_id
|
|
ON freight.bookings (shipping_line_company_id)
|
|
`);
|
|
|
|
// `company_id` / `company_profile_id` are NOT NULL and point at the customer
|
|
// tables, so a shipping-line booking could not be inserted at all. Relax
|
|
// them to nullable; their foreign keys are left in place and keep validating
|
|
// every non-NULL value, so a customer booking is constrained exactly as
|
|
// before. The CHECK below is what now guarantees an owner is present.
|
|
await queryRunner.query(`
|
|
ALTER TABLE freight.bookings ALTER COLUMN company_id DROP NOT NULL
|
|
`);
|
|
await queryRunner.query(`
|
|
ALTER TABLE freight.bookings ALTER COLUMN company_profile_id DROP NOT NULL
|
|
`);
|
|
|
|
// Route and service are inherited from the contract on a customer booking.
|
|
// A shipping line initiates before any of that is known — the bare booking
|
|
// exists only to hang documents off — so these are relaxed too and filled
|
|
// in when the booking is completed. Existing rows all have values, and the
|
|
// customer paths still always set them.
|
|
await queryRunner.query(`
|
|
ALTER TABLE freight.bookings ALTER COLUMN origin_yard_id DROP NOT NULL
|
|
`);
|
|
await queryRunner.query(`
|
|
ALTER TABLE freight.bookings ALTER COLUMN destination_yard_id DROP NOT NULL
|
|
`);
|
|
await queryRunner.query(`
|
|
ALTER TABLE freight.bookings ALTER COLUMN service_type_id DROP NOT NULL
|
|
`);
|
|
await queryRunner.query(`
|
|
ALTER TABLE freight.bookings ALTER COLUMN freight_type DROP NOT NULL
|
|
`);
|
|
|
|
// No FK: kept consistent with how the column is populated at the service
|
|
// layer, and avoids a lock on shipping_line_companies during deploy.
|
|
await queryRunner.query(`
|
|
ALTER TABLE freight.bookings
|
|
DROP CONSTRAINT IF EXISTS chk_bookings_single_owner
|
|
`);
|
|
await queryRunner.query(`
|
|
ALTER TABLE freight.bookings
|
|
ADD CONSTRAINT chk_bookings_single_owner
|
|
CHECK (
|
|
(company_id IS NOT NULL AND shipping_line_company_id IS NULL)
|
|
OR (company_id IS NULL AND shipping_line_company_id IS NOT NULL)
|
|
)
|
|
`);
|
|
}
|
|
|
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
await queryRunner.query(`
|
|
ALTER TABLE freight.bookings
|
|
DROP CONSTRAINT IF EXISTS chk_bookings_single_owner
|
|
`);
|
|
|
|
// Only reinstate NOT NULL if no shipping-line booking exists; those rows
|
|
// have a NULL company_id by design and would make the ALTER fail. Leaving
|
|
// the columns nullable is the safe outcome — the constraint is additive.
|
|
const [{ count }] = (await queryRunner.query(`
|
|
SELECT COUNT(*)::int AS count FROM freight.bookings
|
|
WHERE shipping_line_company_id IS NOT NULL
|
|
`)) as Array<{ count: number }>;
|
|
|
|
if (count === 0) {
|
|
for (const column of [
|
|
"company_id",
|
|
"company_profile_id",
|
|
"origin_yard_id",
|
|
"destination_yard_id",
|
|
"service_type_id",
|
|
"freight_type",
|
|
]) {
|
|
await queryRunner.query(`
|
|
ALTER TABLE freight.bookings ALTER COLUMN ${column} SET NOT NULL
|
|
`);
|
|
}
|
|
}
|
|
|
|
await queryRunner.query(`
|
|
DROP INDEX IF EXISTS freight.idx_bookings_shipping_line_company_id
|
|
`);
|
|
await queryRunner.query(`
|
|
ALTER TABLE freight.bookings DROP COLUMN IF EXISTS shipping_line_company_id
|
|
`);
|
|
}
|
|
}
|