feat(import-operations): bulk Excel upload for yard-resident empty containers

Empties already sitting in an EDR yard but never entered in the system had
to be typed one at a time. Adds a bulk path: parse the sheet in the browser
(all-or-nothing, row-numbered errors), preview it, then POST one batch.

The server rejects the batch if any container already has a non-COMPLETED
return, so re-uploading the same sheet cannot duplicate boxes. No interchange
notification fires — these are historical rows, not a live handover.

Company is an Autocomplete over registered customers that also accepts a
typed name, since a backfilled box may belong to a company that is not a
customer yet. Exact name match sets customer_id; the name always lands in the
new empty_container_returns.company_name.

Also fixes the single Record Return modal, which collected Yard and Zone and
then dropped them before the API call, and did not invalidate the returns
list after a standalone return.
This commit is contained in:
Hagernesh
2026-08-28 11:31:50 +00:00
parent 132374ed03
commit 3f4fd8648a
16 changed files with 1075 additions and 44 deletions

View File

@@ -0,0 +1,24 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
/**
* Empties backfilled into the yard belong to a company that may not be a
* registered customer yet, so `customer_id` cannot hold it. `company_name` is
* the typed fallback, and the display label when the customer IS registered.
*/
export class EmptyContainerReturnCompanyName3790000000000 implements MigrationInterface {
name = 'EmptyContainerReturnCompanyName3790000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.empty_container_returns
ADD COLUMN IF NOT EXISTS company_name varchar(200)
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.empty_container_returns
DROP COLUMN IF EXISTS company_name
`);
}
}