diff --git a/apps/edr-freight-api/src/migrations/3820000000000-WarehouseFreightType.ts b/apps/edr-freight-api/src/migrations/3820000000000-WarehouseFreightType.ts new file mode 100644 index 000000000..563128411 --- /dev/null +++ b/apps/edr-freight-api/src/migrations/3820000000000-WarehouseFreightType.ts @@ -0,0 +1,21 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +/** + * `freight.warehouses.freight_type` — CONTAINER or BULK, or null for a site + * that takes both. + * + * Nullable with no backfill on purpose: every existing warehouse predates the + * field and is unrestricted today, so writing a value would narrow live + * allocation behind the operator's back. + */ +export class WarehouseFreightType3820000000000 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE freight.warehouses ADD COLUMN IF NOT EXISTS freight_type varchar(16)`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE freight.warehouses DROP COLUMN IF EXISTS freight_type`); + } +} diff --git a/apps/edr-freight-api/src/modules/warehouses/dto/create-warehouse.dto.ts b/apps/edr-freight-api/src/modules/warehouses/dto/create-warehouse.dto.ts index a99ca4f46..3928b6118 100644 --- a/apps/edr-freight-api/src/modules/warehouses/dto/create-warehouse.dto.ts +++ b/apps/edr-freight-api/src/modules/warehouses/dto/create-warehouse.dto.ts @@ -1,7 +1,14 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsEnum, IsNumber, IsOptional, IsString, IsUUID, Matches, MaxLength, Min } from 'class-validator'; -import { WAREHOUSE_STATUSES, WAREHOUSE_TYPES, WarehouseStatus, WarehouseType } from '../entities/warehouse.entity'; +import { + FREIGHT_TYPES, + FreightType, + WAREHOUSE_STATUSES, + WAREHOUSE_TYPES, + WarehouseStatus, + WarehouseType, +} from '../entities/warehouse.entity'; export class CreateWarehouseDto { @ApiProperty() @@ -19,6 +26,11 @@ export class CreateWarehouseDto { @IsEnum(WAREHOUSE_TYPES) type!: WarehouseType; + @ApiPropertyOptional({ enum: FREIGHT_TYPES, description: 'Omit for a warehouse that takes both.' }) + @IsOptional() + @IsEnum(FREIGHT_TYPES) + freightType?: FreightType; + @ApiPropertyOptional({ format: 'uuid' }) @IsOptional() @IsUUID() diff --git a/apps/edr-freight-api/src/modules/warehouses/entities/warehouse.entity.ts b/apps/edr-freight-api/src/modules/warehouses/entities/warehouse.entity.ts index 267fcc8af..a67986ad1 100644 --- a/apps/edr-freight-api/src/modules/warehouses/entities/warehouse.entity.ts +++ b/apps/edr-freight-api/src/modules/warehouses/entities/warehouse.entity.ts @@ -1,12 +1,16 @@ import { BaseEntity } from '@edr/api-common'; import { Column, Entity, Index, JoinColumn, ManyToOne, OneToMany } from 'typeorm'; +import { FREIGHT_TYPES, FreightType } from '../../bookings/entities/booking.entity'; import { Facility } from '../../facilities/entities/facility.entity'; import { WarehouseYard } from './warehouse-yard.entity'; export const WAREHOUSE_TYPES = ['OPEN_WAREHOUSE', 'CLOSED_WAREHOUSE'] as const; export type WarehouseType = (typeof WAREHOUSE_TYPES)[number]; +export { FREIGHT_TYPES }; +export type { FreightType }; + export const WAREHOUSE_STATUSES = ['ACTIVE', 'INACTIVE'] as const; export type WarehouseStatus = (typeof WAREHOUSE_STATUSES)[number]; @@ -25,6 +29,14 @@ export class Warehouse extends BaseEntity { @Column({ name: 'type', type: 'varchar', length: 32 }) type!: WarehouseType; + /** + * What the warehouse handles. Null means unrestricted — the pre-existing + * behaviour for every warehouse created before this field existed, so it + * never narrows an already-configured site. + */ + @Column({ name: 'freight_type', type: 'varchar', length: 16, nullable: true }) + freightType?: FreightType | null; + @Column({ name: 'station_id', type: 'uuid', nullable: true }) stationId?: string | null; diff --git a/apps/edr-freight-api/src/modules/warehouses/warehouses.service.ts b/apps/edr-freight-api/src/modules/warehouses/warehouses.service.ts index dd209dec5..43284ca8f 100644 --- a/apps/edr-freight-api/src/modules/warehouses/warehouses.service.ts +++ b/apps/edr-freight-api/src/modules/warehouses/warehouses.service.ts @@ -54,6 +54,7 @@ export class WarehousesService { name: dto.name.trim(), code: dto.code.trim(), type: dto.type, + freightType: dto.freightType ?? null, stationId: dto.stationId ?? null, facilityId: dto.facilityId ?? null, locationName: dto.locationName?.trim() ?? null, @@ -87,6 +88,7 @@ export class WarehousesService { name: dto.name?.trim() ?? existing.name, code: dto.code?.trim() ?? existing.code, type: dto.type ?? existing.type, + freightType: dto.freightType ?? existing.freightType, stationId: dto.stationId ?? existing.stationId, facilityId: dto.facilityId ?? existing.facilityId, locationName: dto.locationName?.trim() ?? existing.locationName, diff --git a/apps/edr-freight-web/backoffice/src/components/warehouses/CreateWarehouseModal.tsx b/apps/edr-freight-web/backoffice/src/components/warehouses/CreateWarehouseModal.tsx index 8c8560442..ca199e0cd 100644 --- a/apps/edr-freight-web/backoffice/src/components/warehouses/CreateWarehouseModal.tsx +++ b/apps/edr-freight-web/backoffice/src/components/warehouses/CreateWarehouseModal.tsx @@ -13,8 +13,19 @@ import { useMutation, useQuery } from '@tanstack/react-query'; import { api } from '@/services/api'; import { useToast } from '@/hooks/use-toast'; -import type { SaveWarehousePayload, Warehouse, WarehouseType } from '@/types/warehouse'; -import { extractErrorMessage, lettersOnly, statusOptions, warehouseTypeOptions } from './options'; +import type { + SaveWarehousePayload, + Warehouse, + WarehouseFreightType, + WarehouseType, +} from '@/types/warehouse'; +import { + extractErrorMessage, + lettersOnly, + statusOptions, + warehouseFreightTypeOptions, + warehouseTypeOptions, +} from './options'; interface CreateWarehouseModalProps { opened: boolean; @@ -26,6 +37,7 @@ interface FormState { name: string; code: string; type: WarehouseType; + freightType: WarehouseFreightType | null; stationId: string | null; locationName: string; capacityWeight: number | ''; @@ -38,6 +50,7 @@ const emptyForm = (): FormState => ({ name: '', code: '', type: 'OPEN_WAREHOUSE', + freightType: null, stationId: null, locationName: '', capacityWeight: '', @@ -66,6 +79,7 @@ export function CreateWarehouseModal({ opened, onClose, warehouse }: CreateWareh name: warehouse.name, code: warehouse.code, type: warehouse.type, + freightType: warehouse.freightType ?? null, stationId: warehouse.stationId ?? null, locationName: warehouse.locationName ?? '', capacityWeight: warehouse.capacityWeight ?? '', @@ -90,6 +104,7 @@ export function CreateWarehouseModal({ opened, onClose, warehouse }: CreateWareh name: form.name.trim(), code: form.code.trim(), type: form.type, + freightType: form.freightType, stationId: form.stationId ?? undefined, locationName: form.locationName.trim() || undefined, capacityWeight: form.capacityWeight === '' ? undefined : Number(form.capacityWeight), @@ -142,6 +157,14 @@ export function CreateWarehouseModal({ opened, onClose, warehouse }: CreateWareh /> +