mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 19:28:17 +00:00
feat(warehouses): track physical container stack and slot positions
Extends the warehouse hierarchy below zone with ground stacks and vertical slots, so a container's exact position is recorded rather than only its zone. - freight.warehouse_zone_stacks / warehouse_zone_slots, plus nullable stack_id / slot_id on warehouse_inventory (existing rows stay valid) - slot occupancy is derived from inventory status, guarded by a partial unique index, so no exit path has to remember to free a slot - placement service: hierarchy validation, bottom-up stacking rules, accessibility/blocking-container reads, capacity vs slot summaries - stack CRUD with auto-generated slots; reuses warehouse-zone permissions - slot support folded into the existing move()/store() paths - fix: validateLocation now rejects a mismatched warehouse/yard/zone triple - seed:warehouse-layout builds the layout from a JSON config
This commit is contained in:
@@ -14,6 +14,14 @@ export class MoveInventoryDto {
|
||||
@IsUUID()
|
||||
zoneId!: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
format: 'uuid',
|
||||
description: 'Exact physical slot in the destination zone. Container yards only.',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
slotId?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsIn, IsOptional, IsUUID } from 'class-validator';
|
||||
|
||||
export class FindAvailableSlotDto {
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
@IsUUID()
|
||||
yardId!: string;
|
||||
|
||||
@ApiPropertyOptional({ format: 'uuid', description: 'Narrow the search to one zone.' })
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
zoneId?: string;
|
||||
|
||||
@ApiPropertyOptional({ enum: ['IMPORT', 'EXPORT', 'BOTH'], description: 'Null/BOTH matches any yard direction.' })
|
||||
@IsOptional()
|
||||
@IsIn(['IMPORT', 'EXPORT', 'BOTH'])
|
||||
direction?: string;
|
||||
|
||||
@ApiPropertyOptional({ format: 'uuid' })
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
cargoTypeId?: string;
|
||||
}
|
||||
|
||||
export class AssignSlotDto {
|
||||
@ApiPropertyOptional({
|
||||
format: 'uuid',
|
||||
description: 'Target slot. Omit to let the placement engine pick the lowest free level.',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
slotId?: string;
|
||||
}
|
||||
@@ -22,6 +22,15 @@ export class StoreInventoryDto {
|
||||
@IsUUID()
|
||||
zoneId?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
format: 'uuid',
|
||||
description:
|
||||
'Exact physical slot. Container yards only; omit to let the placement engine pick the lowest free level.',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
slotId?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsBoolean, IsEnum, IsInt, IsOptional, IsString, IsUUID, Max, MaxLength, Min } from 'class-validator';
|
||||
|
||||
import {
|
||||
DEFAULT_MAX_STACK_HEIGHT,
|
||||
WAREHOUSE_ZONE_STACK_STATUSES,
|
||||
WarehouseZoneStackStatus,
|
||||
} from '../entities/warehouse-zone-stack.entity';
|
||||
import {
|
||||
WAREHOUSE_ZONE_SLOT_STATUSES,
|
||||
WarehouseZoneSlotStatus,
|
||||
} from '../entities/warehouse-zone-slot.entity';
|
||||
|
||||
/** Nobody stacks boxes this high; the cap is here to catch a typo'd 30. */
|
||||
const MAX_SUPPORTED_STACK_HEIGHT = 10;
|
||||
|
||||
export class CreateWarehouseZoneStackDto {
|
||||
@ApiPropertyOptional({ format: 'uuid', description: 'Optional — taken from the route param when omitted' })
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
zoneId?: string;
|
||||
|
||||
@ApiProperty({ example: 'ZA-001' })
|
||||
@IsString()
|
||||
@MaxLength(40)
|
||||
code!: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(160)
|
||||
name?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Physical row label' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
row?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Physical bay label' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
bay?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Physical position label' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
position?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
default: DEFAULT_MAX_STACK_HEIGHT,
|
||||
description: 'One slot is generated per level, 1 to this height.',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(MAX_SUPPORTED_STACK_HEIGHT)
|
||||
maxStackHeight?: number;
|
||||
}
|
||||
|
||||
export class UpdateWarehouseZoneStackDto {
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(40)
|
||||
code?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(160)
|
||||
name?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
row?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
bay?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
position?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Raising it adds slots; lowering it removes the empty top levels.' })
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(MAX_SUPPORTED_STACK_HEIGHT)
|
||||
maxStackHeight?: number;
|
||||
|
||||
@ApiPropertyOptional({ enum: WAREHOUSE_ZONE_STACK_STATUSES })
|
||||
@IsOptional()
|
||||
@IsEnum(WAREHOUSE_ZONE_STACK_STATUSES)
|
||||
status?: WarehouseZoneStackStatus;
|
||||
}
|
||||
|
||||
export class UpdateWarehouseZoneSlotDto {
|
||||
@ApiPropertyOptional({
|
||||
enum: WAREHOUSE_ZONE_SLOT_STATUSES,
|
||||
description: 'Operator intent only. OCCUPIED is derived from inventory and cannot be set here.',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsEnum(WAREHOUSE_ZONE_SLOT_STATUSES)
|
||||
status?: WarehouseZoneSlotStatus;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user