feat(warehouse): add warehouse management module (batch 1)

Backend (edr-freight-api):
- Warehouse, WarehouseYard, WarehouseZone, WarehouseInventory entities
- CRUD for warehouses/yards/zones with scoped code uniqueness
- Inventory receive with location-hierarchy + capacity validation and
  transactional capacity-counter updates
- inspect / ready-for-loading status transitions
- inventory inquiry (booking/customer/container/cargo-type joins)
- migration creating freight.warehouse* tables

Frontend (freight backoffice):
- types, service, react-query hooks, URL constants
- reusable components: badges, filters, table/card views, inventory and
  inquiry tables, create/receive modals
- pages: Warehouse list, detail (overview/yards/zones/inventory tabs),
  inventory, inventory inquiry
- Warehouse Information card + Receive At Warehouse on booking detail
- routes + sidebar nav; app-wide ErrorBoundary

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hagernesh
2026-06-11 15:54:56 +00:00
parent 7facbeda22
commit c86118cd8d
54 changed files with 4257 additions and 1 deletions

View File

@@ -0,0 +1,37 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsEnum, IsNumber, IsOptional, IsString, IsUUID, MaxLength, Min } from 'class-validator';
import { WAREHOUSE_YARD_TYPES, WarehouseYardType } from '../entities/warehouse-yard.entity';
export class CreateWarehouseYardDto {
@ApiPropertyOptional({ format: 'uuid', description: 'Optional — taken from the route param when omitted' })
@IsOptional()
@IsUUID()
warehouseId?: string;
@ApiProperty()
@IsString()
@MaxLength(160)
name!: string;
@ApiProperty()
@IsString()
@MaxLength(40)
code!: string;
@ApiProperty({ enum: WAREHOUSE_YARD_TYPES })
@IsEnum(WAREHOUSE_YARD_TYPES)
type!: WarehouseYardType;
@ApiPropertyOptional()
@IsOptional()
@IsNumber()
@Min(0)
capacityWeight?: number;
@ApiPropertyOptional()
@IsOptional()
@IsNumber()
@Min(0)
capacityContainers?: number;
}

View File

@@ -0,0 +1,37 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsEnum, IsNumber, IsOptional, IsString, IsUUID, MaxLength, Min } from 'class-validator';
import { WAREHOUSE_ZONE_TYPES, WarehouseZoneType } from '../entities/warehouse-zone.entity';
export class CreateWarehouseZoneDto {
@ApiPropertyOptional({ format: 'uuid', description: 'Optional — taken from the route param when omitted' })
@IsOptional()
@IsUUID()
yardId?: string;
@ApiProperty()
@IsString()
@MaxLength(160)
name!: string;
@ApiProperty()
@IsString()
@MaxLength(40)
code!: string;
@ApiProperty({ enum: WAREHOUSE_ZONE_TYPES })
@IsEnum(WAREHOUSE_ZONE_TYPES)
type!: WarehouseZoneType;
@ApiPropertyOptional()
@IsOptional()
@IsNumber()
@Min(0)
capacityWeight?: number;
@ApiPropertyOptional()
@IsOptional()
@IsNumber()
@Min(0)
capacityContainers?: number;
}

View File

@@ -0,0 +1,43 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsEnum, IsNumber, IsOptional, IsString, IsUUID, MaxLength, Min } from 'class-validator';
import { WAREHOUSE_TYPES, WarehouseType } from '../entities/warehouse.entity';
export class CreateWarehouseDto {
@ApiProperty()
@IsString()
@MaxLength(160)
name!: string;
@ApiProperty()
@IsString()
@MaxLength(40)
code!: string;
@ApiProperty({ enum: WAREHOUSE_TYPES })
@IsEnum(WAREHOUSE_TYPES)
type!: WarehouseType;
@ApiPropertyOptional({ format: 'uuid' })
@IsOptional()
@IsUUID()
stationId?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
@MaxLength(200)
locationName?: string;
@ApiPropertyOptional()
@IsOptional()
@IsNumber()
@Min(0)
capacityWeight?: number;
@ApiPropertyOptional()
@IsOptional()
@IsNumber()
@Min(0)
capacityContainers?: number;
}

View File

@@ -0,0 +1,54 @@
import { ApiPropertyOptional } from '@nestjs/swagger';
import { IsEnum, IsOptional, IsString, IsUUID } from 'class-validator';
import {
WAREHOUSE_INVENTORY_STATUSES,
WarehouseInventoryStatus,
} from '../entities/warehouse-inventory.entity';
export class FilterWarehouseInventoryDto {
@ApiPropertyOptional({ format: 'uuid' })
@IsOptional()
@IsUUID()
warehouseId?: string;
@ApiPropertyOptional({ format: 'uuid' })
@IsOptional()
@IsUUID()
yardId?: string;
@ApiPropertyOptional({ format: 'uuid' })
@IsOptional()
@IsUUID()
zoneId?: string;
@ApiPropertyOptional({ format: 'uuid' })
@IsOptional()
@IsUUID()
bookingId?: string;
@ApiPropertyOptional({ format: 'uuid' })
@IsOptional()
@IsUUID()
cargoId?: string;
@ApiPropertyOptional({ format: 'uuid' })
@IsOptional()
@IsUUID()
containerId?: string;
@ApiPropertyOptional({ format: 'uuid' })
@IsOptional()
@IsUUID()
goodsId?: string;
@ApiPropertyOptional({ enum: WAREHOUSE_INVENTORY_STATUSES })
@IsOptional()
@IsEnum(WAREHOUSE_INVENTORY_STATUSES)
status?: WarehouseInventoryStatus;
@ApiPropertyOptional()
@IsOptional()
@IsString()
search?: string;
}

View File

@@ -0,0 +1,26 @@
import { ApiPropertyOptional } from '@nestjs/swagger';
import { IsEnum, IsOptional, IsString, IsUUID } from 'class-validator';
import { WAREHOUSE_STATUSES, WAREHOUSE_TYPES, WarehouseStatus, WarehouseType } from '../entities/warehouse.entity';
export class FilterWarehouseDto {
@ApiPropertyOptional()
@IsOptional()
@IsString()
search?: string;
@ApiPropertyOptional({ enum: WAREHOUSE_TYPES })
@IsOptional()
@IsEnum(WAREHOUSE_TYPES)
type?: WarehouseType;
@ApiPropertyOptional({ format: 'uuid' })
@IsOptional()
@IsUUID()
stationId?: string;
@ApiPropertyOptional({ enum: WAREHOUSE_STATUSES })
@IsOptional()
@IsEnum(WAREHOUSE_STATUSES)
status?: WarehouseStatus;
}

View File

@@ -0,0 +1,49 @@
import { ApiPropertyOptional } from '@nestjs/swagger';
import { IsEnum, IsOptional, IsString, IsUUID } from 'class-validator';
import {
WAREHOUSE_INVENTORY_STATUSES,
WarehouseInventoryStatus,
} from '../entities/warehouse-inventory.entity';
export class InquiryWarehouseInventoryDto {
@ApiPropertyOptional()
@IsOptional()
@IsString()
bookingNumber?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
containerNumber?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
cargoType?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
goodsName?: string;
@ApiPropertyOptional({ format: 'uuid' })
@IsOptional()
@IsUUID()
warehouseId?: string;
@ApiPropertyOptional({ format: 'uuid' })
@IsOptional()
@IsUUID()
yardId?: string;
@ApiPropertyOptional({ format: 'uuid' })
@IsOptional()
@IsUUID()
zoneId?: string;
@ApiPropertyOptional({ enum: WAREHOUSE_INVENTORY_STATUSES })
@IsOptional()
@IsEnum(WAREHOUSE_INVENTORY_STATUSES)
status?: WarehouseInventoryStatus;
}

View File

@@ -0,0 +1,56 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsNumber, IsOptional, IsString, IsUUID, Min } from 'class-validator';
export class ReceiveWarehouseInventoryDto {
@ApiProperty({ format: 'uuid' })
@IsUUID()
warehouseId!: string;
@ApiProperty({ format: 'uuid' })
@IsUUID()
yardId!: string;
@ApiProperty({ format: 'uuid' })
@IsUUID()
zoneId!: string;
@ApiProperty({ format: 'uuid' })
@IsUUID()
bookingId!: string;
@ApiPropertyOptional({ format: 'uuid' })
@IsOptional()
@IsUUID()
cargoId?: string;
@ApiPropertyOptional({ format: 'uuid' })
@IsOptional()
@IsUUID()
containerId?: string;
@ApiPropertyOptional({ format: 'uuid' })
@IsOptional()
@IsUUID()
goodsId?: string;
@ApiProperty()
@IsNumber()
@Min(0)
quantity!: number;
@ApiProperty()
@IsNumber()
@Min(0)
weight!: number;
@ApiPropertyOptional()
@IsOptional()
@IsNumber()
@Min(0)
volume?: number;
@ApiPropertyOptional()
@IsOptional()
@IsString()
notes?: string;
}

View File

@@ -0,0 +1,12 @@
import { ApiPropertyOptional, PartialType } from '@nestjs/swagger';
import { IsEnum, IsOptional } from 'class-validator';
import { WAREHOUSE_YARD_STATUSES, WarehouseYardStatus } from '../entities/warehouse-yard.entity';
import { CreateWarehouseYardDto } from './create-warehouse-yard.dto';
export class UpdateWarehouseYardDto extends PartialType(CreateWarehouseYardDto) {
@ApiPropertyOptional({ enum: WAREHOUSE_YARD_STATUSES })
@IsOptional()
@IsEnum(WAREHOUSE_YARD_STATUSES)
status?: WarehouseYardStatus;
}

View File

@@ -0,0 +1,12 @@
import { ApiPropertyOptional, PartialType } from '@nestjs/swagger';
import { IsEnum, IsOptional } from 'class-validator';
import { WAREHOUSE_ZONE_STATUSES, WarehouseZoneStatus } from '../entities/warehouse-zone.entity';
import { CreateWarehouseZoneDto } from './create-warehouse-zone.dto';
export class UpdateWarehouseZoneDto extends PartialType(CreateWarehouseZoneDto) {
@ApiPropertyOptional({ enum: WAREHOUSE_ZONE_STATUSES })
@IsOptional()
@IsEnum(WAREHOUSE_ZONE_STATUSES)
status?: WarehouseZoneStatus;
}

View File

@@ -0,0 +1,12 @@
import { ApiPropertyOptional, PartialType } from '@nestjs/swagger';
import { IsEnum, IsOptional } from 'class-validator';
import { WAREHOUSE_STATUSES, WarehouseStatus } from '../entities/warehouse.entity';
import { CreateWarehouseDto } from './create-warehouse.dto';
export class UpdateWarehouseDto extends PartialType(CreateWarehouseDto) {
@ApiPropertyOptional({ enum: WAREHOUSE_STATUSES })
@IsOptional()
@IsEnum(WAREHOUSE_STATUSES)
status?: WarehouseStatus;
}