import { Body, Controller, Get, Param, ParseUUIDPipe, Patch, Post, Query } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { BookingStaff } from '../../common/booking-guards'; import { FREIGHT_PERMS } from '../../seed/freight-permissions.registry'; import { CreateWarehouseDto } from './dto/create-warehouse.dto'; import { CreateWarehouseYardDto } from './dto/create-warehouse-yard.dto'; import { FilterWarehouseDto } from './dto/filter-warehouse.dto'; import { UpdateWarehouseDto } from './dto/update-warehouse.dto'; import { WarehouseDashboardService } from './warehouse-dashboard.service'; import { WarehouseYardsService } from './warehouse-yards.service'; import { WarehousesService } from './warehouses.service'; @ApiTags('warehouses') @ApiBearerAuth() // Baseline read: warehouse reference data is consumed by inventory/dashboard // flows too, so any of the view permissions grants reads. Writes stack their // specific create/update permission per route on top — which means every key // used by a route below must also appear here, or the class guard denies // before the route's own key is ever consulted (Nest ANDs the two). @Controller('warehouses') @BookingStaff([ FREIGHT_PERMS.warehouses.view, FREIGHT_PERMS.warehouseInventory.view, FREIGHT_PERMS.warehouseDashboard.view, FREIGHT_PERMS.warehouses.create, FREIGHT_PERMS.warehouses.update, FREIGHT_PERMS.warehouseYards.view, FREIGHT_PERMS.warehouseYards.create, ]) export class WarehousesController { constructor( private readonly warehousesService: WarehousesService, private readonly yardsService: WarehouseYardsService, private readonly dashboardService: WarehouseDashboardService, ) {} @Get() @ApiOperation({ summary: 'List warehouses' }) findAll(@Query() filter: FilterWarehouseDto) { return this.warehousesService.findAll(filter); } @Get('dashboard') @BookingStaff(FREIGHT_PERMS.warehouseDashboard.view) @ApiOperation({ summary: 'Warehouse dashboard metrics', description: 'dateFrom/dateTo scope only the activity counters (currently just "received"); ' + 'status-backlog and fleet counters are always current. Omit both for "received today" ' + '(the original default). warehouseId scopes every warehouse_inventory-derived counter; ' + 'totalWarehouses/emptyContainers/importTrains/exportTrains are never warehouse-scoped.', }) dashboard( @Query('dateFrom') dateFrom?: string, @Query('dateTo') dateTo?: string, @Query('warehouseId') warehouseId?: string, ) { return this.dashboardService.getDashboard({ dateFrom, dateTo, warehouseId }); } @Post() @BookingStaff(FREIGHT_PERMS.warehouses.create) @ApiOperation({ summary: 'Create warehouse' }) create(@Body() dto: CreateWarehouseDto) { return this.warehousesService.create(dto); } @Get(':id') @ApiOperation({ summary: 'Get warehouse by ID' }) findOne(@Param('id', ParseUUIDPipe) id: string) { return this.warehousesService.findById(id); } @Patch(':id') @BookingStaff(FREIGHT_PERMS.warehouses.update) @ApiOperation({ summary: 'Update warehouse' }) update(@Param('id', ParseUUIDPipe) id: string, @Body() dto: UpdateWarehouseDto) { return this.warehousesService.update(id, dto); } @Get(':warehouseId/yards') @BookingStaff(FREIGHT_PERMS.warehouseYards.view) @ApiOperation({ summary: 'List yards within a warehouse' }) listYards(@Param('warehouseId', ParseUUIDPipe) warehouseId: string) { return this.yardsService.findByWarehouse(warehouseId); } @Post(':warehouseId/yards') @BookingStaff(FREIGHT_PERMS.warehouseYards.create) @ApiOperation({ summary: 'Create a yard within a warehouse' }) createYard( @Param('warehouseId', ParseUUIDPipe) warehouseId: string, @Body() dto: CreateWarehouseYardDto, ) { return this.yardsService.create(warehouseId, dto); } }