Files
edr-platform/apps/edr-freight-api/src/migrations/2430000000000-AddFileReviewStatus.ts
Nathnael fd5aedcec7 feat(companies): per-document change requests and resubmission review queue
Two review-workflow gaps for freight customer onboarding:

Request for change per document. Backoffice can now flag a single uploaded
document (company document, profile licence, or POA delegation letter) with a
note the customer sees, instead of rejecting the whole role over it. Adds
review_status/review_note/reviewed_by/reviewed_at to freight.files (migration
AddFileReviewStatus, partial index for the gate), a POST
documents/:fileId/request-change endpoint, the backoffice action + modal, and a
portal banner/badge so the customer knows what to re-upload. Re-uploading clears
the flag. Approving a role is blocked while any of its documents has an open
correction; the gate check and the status write share a pessimistic write lock
on the company row (as does the change-request write) so a correction can never
slip in between the check and the profile going Active.

Resubmission is visible to reviewers. When a customer resubmits a rejected role
or amends a change request, backoffice staff are notified (allBackoffice inbox
item, deep-linked to the customer) and the resubmission surfaces in a new
"Pending changes" list view + KPI, since such companies are status = active and
never matched the pending-approval filter.
2026-07-21 07:34:40 +00:00

50 lines
2.0 KiB
TypeScript

import { MigrationInterface, QueryRunner } from 'typeorm';
/**
* Per-document review state, so a backoffice reviewer can request a correction
* on one specific onboarding document instead of rejecting the whole role.
*
* Until now `freight.files` carried no status at all: the `pending_add` /
* `pending_remove` badges the portal shows are derived by diffing live rows
* against an open company change request, which says nothing about whether a
* reviewer is happy with a given document. `review_status` is that missing
* verdict — NULL means never reviewed, which is the state every existing row
* correctly starts in, so no backfill is needed.
*
* The partial index serves the approval gate, which asks "does this company (or
* profile) still have any document with an open change request?" on every
* role-status write.
*/
export class AddFileReviewStatus2430000000000 implements MigrationInterface {
name = 'AddFileReviewStatus2430000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.files
ADD COLUMN IF NOT EXISTS review_status varchar(32) NULL,
ADD COLUMN IF NOT EXISTS review_note text NULL,
ADD COLUMN IF NOT EXISTS reviewed_by uuid NULL,
ADD COLUMN IF NOT EXISTS reviewed_at timestamptz NULL
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS "IDX_files_open_change_request"
ON freight.files (resource, resource_id)
WHERE review_status = 'change_requested' AND deleted_at IS NULL
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DROP INDEX IF EXISTS freight."IDX_files_open_change_request"`,
);
await queryRunner.query(`
ALTER TABLE freight.files
DROP COLUMN IF EXISTS review_status,
DROP COLUMN IF EXISTS review_note,
DROP COLUMN IF EXISTS reviewed_by,
DROP COLUMN IF EXISTS reviewed_at
`);
}
}