feat(warehouses): record container vs bulk freight type

Nullable freight_type on the warehouse, null meaning it takes both.
No backfill: every existing warehouse is unrestricted today and
writing a value would narrow allocation behind the operator's back.
Reuses FREIGHT_TYPES from the booking entity rather than a third copy
of the same two values.
This commit is contained in:
Hagernesh
2026-08-28 15:04:39 +00:00
parent d4373dfbe4
commit ba79b36d90
7 changed files with 81 additions and 3 deletions

View File

@@ -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<void> {
await queryRunner.query(
`ALTER TABLE freight.warehouses ADD COLUMN IF NOT EXISTS freight_type varchar(16)`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE freight.warehouses DROP COLUMN IF EXISTS freight_type`);
}
}

View File

@@ -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()

View File

@@ -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;

View File

@@ -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,