Files
edr-platform/apps/edr-freight-api/src/migrations/3420000000000-BulkTemplateTradeDirection.ts
Marshal 4efd65c105 feat: key bulk templates by trade direction
Bulk contract templates are now unique per (cargo type, trade direction,
customs option) instead of (cargo type, customs option). Intercity is
domestic and crosses no border, so it carries no customs variant:
with_customs stays null there, enforced by a check constraint.

Contract resolution already passed the trade direction through but the
bulk lookup dropped it, so one template served all three directions.

Container templates are unchanged — cargo_type_id is null on those rows,
which keeps them out of both the new index and the constraint.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 09:20:28 +00:00

70 lines
2.7 KiB
TypeScript

import { MigrationInterface, QueryRunner } from 'typeorm';
/**
* Bulk contract templates gain trade direction, so the unique key becomes
* (cargo type, direction, customs option) instead of (cargo type, customs).
*
* Intercity is domestic and crosses no border, so it has no customs variant at
* all: with_customs stays NULL there, enforced by ck_bulk_intercity_no_customs.
* The unique index coalesces that NULL so two intercity templates for the same
* cargo type still collide (plain NULLs never do).
*
* No backfill: staff-created bulk templates are keyed by cargo_type_id and no
* such row exists yet — the seeded direction-keyed bulk rows were retired by
* 3320000000000 and carry a NULL cargo_type_id. The five system container
* templates are untouched: cargo_type_id IS NULL keeps them out of both the
* index and the check.
*/
export class BulkTemplateTradeDirection3420000000000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.contract_templates
ADD COLUMN IF NOT EXISTS trade_direction varchar(20)
`);
await queryRunner.query(
`DROP INDEX IF EXISTS freight.uq_contract_templates_cargo_customs`,
);
await queryRunner.query(`
CREATE UNIQUE INDEX IF NOT EXISTS uq_contract_templates_cargo_dir_customs
ON freight.contract_templates
(cargo_type_id, trade_direction, COALESCE(with_customs, false))
WHERE deleted_at IS NULL AND cargo_type_id IS NOT NULL
`);
await queryRunner.query(`
ALTER TABLE freight.contract_templates
DROP CONSTRAINT IF EXISTS ck_bulk_intercity_no_customs
`);
await queryRunner.query(`
ALTER TABLE freight.contract_templates
ADD CONSTRAINT ck_bulk_intercity_no_customs CHECK (
cargo_type_id IS NULL
OR (
trade_direction IN ('IMPORT', 'EXPORT', 'INTERCITY')
AND (trade_direction = 'INTERCITY') = (with_customs IS NULL)
)
)
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.contract_templates
DROP CONSTRAINT IF EXISTS ck_bulk_intercity_no_customs
`);
await queryRunner.query(
`DROP INDEX IF EXISTS freight.uq_contract_templates_cargo_dir_customs`,
);
await queryRunner.query(`
CREATE UNIQUE INDEX IF NOT EXISTS uq_contract_templates_cargo_customs
ON freight.contract_templates (cargo_type_id, with_customs)
WHERE deleted_at IS NULL AND cargo_type_id IS NOT NULL
`);
await queryRunner.query(`
ALTER TABLE freight.contract_templates DROP COLUMN IF EXISTS trade_direction
`);
}
}