feat(warehouse): batch 3 — loading, dispatch & train-departure visibility

- WarehouseLoading entity + Batch3 migration (wagon loading records)
- wagon-aware load() with validation; dispatch moved to PATCH
- read-only SchedulingReadFacade (schedule/wagon/departure) — never writes scheduling
- new endpoints: GET /warehouse-loadings, loadable-wagons, booking schedule
- Loading Queue / Loaded Inventory / Dispatch Queue pages + routes + sidebar
- FreightVisual illustrations (page heroes + empty states)
- booking detail: loaded/dispatched/wagon + read-only train schedule

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hagernesh
2026-06-12 12:44:40 +00:00
parent 72e2e3c985
commit 95c6a71438
27 changed files with 1193 additions and 30 deletions

View File

@@ -1,18 +1,23 @@
import { Body, Controller, Get, Param, ParseUUIDPipe, Post, Query } from '@nestjs/common';
import { Body, Controller, Get, Param, ParseUUIDPipe, Patch, Post, Query } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { FilterWarehouseInventoryDto } from './dto/filter-inventory.dto';
import { InquiryWarehouseInventoryDto } from './dto/inquiry-inventory.dto';
import { LoadInventoryDto } from './dto/load-inventory.dto';
import { MoveInventoryDto } from './dto/move-inventory.dto';
import { ReceiveWarehouseInventoryDto } from './dto/receive-inventory.dto';
import { ReserveInventoryDto } from './dto/reserve-inventory.dto';
import { SchedulingReadFacade } from './scheduling-read.facade';
import { WarehouseInventoryService } from './warehouse-inventory.service';
@ApiTags('warehouse-inventory')
@ApiBearerAuth()
@Controller('warehouse-inventory')
export class WarehouseInventoryController {
constructor(private readonly inventoryService: WarehouseInventoryService) {}
constructor(
private readonly inventoryService: WarehouseInventoryService,
private readonly scheduling: SchedulingReadFacade,
) {}
@Get()
@ApiOperation({ summary: 'List warehouse inventory' })
@@ -32,6 +37,18 @@ export class WarehouseInventoryController {
return this.inventoryService.inquiry(filter);
}
@Get('loadable-wagons')
@ApiOperation({ summary: 'List wagons usable for loading (read-only from scheduling)' })
loadableWagons() {
return this.scheduling.listLoadableWagons();
}
@Get('booking/:bookingId/schedule')
@ApiOperation({ summary: 'Read-only schedule + wagon + departure status for a booking' })
bookingSchedule(@Param('bookingId', ParseUUIDPipe) bookingId: string) {
return this.scheduling.getBookingSchedule(bookingId);
}
@Post('receive')
@ApiOperation({ summary: 'Receive inventory at a warehouse location' })
receive(@Body() dto: ReceiveWarehouseInventoryDto) {
@@ -56,6 +73,12 @@ export class WarehouseInventoryController {
return this.inventoryService.findActivity(id);
}
@Get(':id/loadings')
@ApiOperation({ summary: 'Loading records for an inventory item' })
loadings(@Param('id', ParseUUIDPipe) id: string) {
return this.inventoryService.findLoadingsByInventory(id);
}
@Post(':id/move')
@ApiOperation({ summary: 'Move inventory to another warehouse/yard/zone' })
move(@Param('id', ParseUUIDPipe) id: string, @Body() dto: MoveInventoryDto) {
@@ -75,13 +98,13 @@ export class WarehouseInventoryController {
}
@Post(':id/load')
@ApiOperation({ summary: 'Mark inventory LOADED' })
load(@Param('id', ParseUUIDPipe) id: string, @Body('performedBy') performedBy?: string) {
return this.inventoryService.load(id, performedBy);
@ApiOperation({ summary: 'Load READY_FOR_LOADING inventory onto a wagon' })
load(@Param('id', ParseUUIDPipe) id: string, @Body() dto: LoadInventoryDto) {
return this.inventoryService.load(id, dto);
}
@Post(':id/dispatch')
@ApiOperation({ summary: 'Mark inventory DISPATCHED' })
@Patch(':id/dispatch')
@ApiOperation({ summary: 'Mark loaded inventory DISPATCHED (left the terminal)' })
dispatch(@Param('id', ParseUUIDPipe) id: string, @Body('performedBy') performedBy?: string) {
return this.inventoryService.dispatch(id, performedBy);
}