add dispute functionality for contract duty and implement collection dates

This commit is contained in:
Marshal
2026-07-26 16:58:51 +00:00
parent 9b13fa2ac6
commit 5e10c97294
74 changed files with 3684 additions and 342 deletions

View File

@@ -0,0 +1,22 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
/**
* Store WHO made a contract edit as a name, not just an id. Denormalised on
* purpose: an audit trail must still read correctly after the user is renamed,
* deactivated or deleted, and `iam.users` lives outside this module's schema.
*/
export class AddRevisionActorName2920000000000 implements MigrationInterface {
name = 'AddRevisionActorName2920000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE freight.contract_document_revisions ADD COLUMN IF NOT EXISTS actor_name varchar(200);`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE freight.contract_document_revisions DROP COLUMN IF EXISTS actor_name;`,
);
}
}

View File

@@ -0,0 +1,42 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
/**
* Djibouti GL must record WHEN the vessel arrived and WHEN the Delivery Order
* was collected, not just attach the DO file. Both are mandatory on DO upload
* (enforced in the clearance services), so the columns are new and nullable —
* DOs uploaded before this change have no dates to backfill.
*
* `vessel_departure_date` is the EXPORT Release-Order date and stays as-is; the
* import arrival date gets its own column rather than overloading it.
*/
export class AddDoCollectionDates2930000000000 implements MigrationInterface {
name = 'AddDoCollectionDates2930000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
for (const table of [
'freight.contract_clearance_cycles',
'freight.bookings',
]) {
await queryRunner.query(
`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS vessel_arrival_date date;`,
);
await queryRunner.query(
`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS do_collected_date date;`,
);
}
}
public async down(queryRunner: QueryRunner): Promise<void> {
for (const table of [
'freight.contract_clearance_cycles',
'freight.bookings',
]) {
await queryRunner.query(
`ALTER TABLE ${table} DROP COLUMN IF EXISTS do_collected_date;`,
);
await queryRunner.query(
`ALTER TABLE ${table} DROP COLUMN IF EXISTS vessel_arrival_date;`,
);
}
}
}

View File

@@ -0,0 +1,26 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
/**
* Currency moved from the contract to the shipment: a contract now quotes in
* USD and the customer picks the billing currency per booking. On a customs
* contract GL books on the customer's behalf, so the shipment request is where
* the customer states the currency — GL reads it when creating the booking.
*
* Nullable: requests submitted before this change fall back to the contract's
* own currency, which is exactly what their bookings already used.
*/
export class AddBookingRequestCurrency2940000000000 implements MigrationInterface {
name = 'AddBookingRequestCurrency2940000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE freight.booking_requests ADD COLUMN IF NOT EXISTS payment_currency varchar(5);`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE freight.booking_requests DROP COLUMN IF EXISTS payment_currency;`,
);
}
}

View File

@@ -0,0 +1,34 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
/**
* Keep every version of a stored document.
*
* Replacing a file used to DELETE the previous row outright, so a staff
* correction erased the customer's original upload with no trail. Superseded
* versions are now soft-deleted (already excluded from every read by TypeORM's
* soft-delete filter) and stamped with who replaced them and why, which is what
* the document's version history reads back.
*/
export class AddFileVersionHistory2940000000000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.files
ADD COLUMN IF NOT EXISTS replaced_by_user_id uuid NULL,
ADD COLUMN IF NOT EXISTS replace_reason text NULL
`);
// History reads walk one document's versions, deleted rows included.
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS "IDX_files_version_history"
ON freight.files (resource, resource_id, code, created_at DESC)
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX IF EXISTS freight."IDX_files_version_history"`);
await queryRunner.query(`
ALTER TABLE freight.files
DROP COLUMN IF EXISTS replaced_by_user_id,
DROP COLUMN IF EXISTS replace_reason
`);
}
}

View File

@@ -0,0 +1,37 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
/**
* Transit-assignee handshake before the customs declaration.
*
* GL Ethiopia must ask GL Djibouti who will handle the shipment in transit, and
* Djibouti answers with a name, before the declaration can be filed. The whole
* exchange lives on the clearance cycle so it repeats naturally with each cycle
* of a GENERAL contract.
*/
export class AddTransitAssigneeHandshake2950000000000
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.contract_clearance_cycles
ADD COLUMN IF NOT EXISTS transit_assignee_requested_at timestamptz NULL,
ADD COLUMN IF NOT EXISTS transit_assignee_requested_by_user_id uuid NULL,
ADD COLUMN IF NOT EXISTS transit_assignee_request_note text NULL,
ADD COLUMN IF NOT EXISTS transit_assignee_name text NULL,
ADD COLUMN IF NOT EXISTS transit_assignee_assigned_at timestamptz NULL,
ADD COLUMN IF NOT EXISTS transit_assignee_assigned_by_user_id uuid NULL
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.contract_clearance_cycles
DROP COLUMN IF EXISTS transit_assignee_requested_at,
DROP COLUMN IF EXISTS transit_assignee_requested_by_user_id,
DROP COLUMN IF EXISTS transit_assignee_request_note,
DROP COLUMN IF EXISTS transit_assignee_name,
DROP COLUMN IF EXISTS transit_assignee_assigned_at,
DROP COLUMN IF EXISTS transit_assignee_assigned_by_user_id
`);
}
}