import { BadRequestException, Body, Controller, Delete, 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 { CreateWarehouseZoneStackDto, UpdateWarehouseZoneSlotDto, UpdateWarehouseZoneStackDto, } from './dto/warehouse-zone-stack.dto'; import { WarehouseZoneStacksService } from './warehouse-zone-stacks.service'; /** * Stacks and slots are zone configuration, so they ride the warehouse-zone * permissions rather than introducing new keys — a new key needs a matching * `iam.permissions` row in every environment or boot fails. */ @ApiTags('warehouse-zone-stacks') @ApiBearerAuth() @Controller('warehouse-zone-stacks') // Class gate lists every key its routes use: Nest runs class AND method guards. @BookingStaff([ FREIGHT_PERMS.warehouseZones.view, FREIGHT_PERMS.warehouseInventory.view, FREIGHT_PERMS.warehouseZones.create, FREIGHT_PERMS.warehouseZones.update, FREIGHT_PERMS.warehouseZones.delete, ]) export class WarehouseZoneStacksController { constructor(private readonly stacksService: WarehouseZoneStacksService) {} @Get() @ApiOperation({ summary: 'List the ground stacks configured in a zone' }) findByZone(@Query('zoneId', ParseUUIDPipe) zoneId: string) { return this.stacksService.findByZone(zoneId); } @Post() @BookingStaff(FREIGHT_PERMS.warehouseZones.create) @ApiOperation({ summary: 'Create a ground stack', description: 'One slot per level is generated automatically, from 1 to maxStackHeight (default 3).', }) create(@Body() dto: CreateWarehouseZoneStackDto, @Query('zoneId') zoneIdQuery?: string) { const zoneId = dto.zoneId ?? zoneIdQuery; if (!zoneId) { throw new BadRequestException('zoneId is required'); } return this.stacksService.create(zoneId, dto); } // Declared before ':id' so 'slots' is never swallowed as a stack id. @Patch('slots/:slotId') @BookingStaff(FREIGHT_PERMS.warehouseZones.update) @ApiOperation({ summary: 'Block, reserve, or reactivate one slot', description: 'Occupancy is derived from inventory and cannot be set here.', }) updateSlot(@Param('slotId', ParseUUIDPipe) slotId: string, @Body() dto: UpdateWarehouseZoneSlotDto) { return this.stacksService.updateSlot(slotId, dto); } @Get(':id') @ApiOperation({ summary: 'Get one stack with its slots' }) findOne(@Param('id', ParseUUIDPipe) id: string) { return this.stacksService.findById(id); } @Get(':id/occupancy') @ApiOperation({ summary: 'Level-by-level occupancy of one stack' }) occupancy(@Param('id', ParseUUIDPipe) id: string) { return this.stacksService.slotOccupancy(id); } @Patch(':id') @BookingStaff(FREIGHT_PERMS.warehouseZones.update) @ApiOperation({ summary: 'Update a stack', description: 'Raising maxStackHeight adds slots; lowering it trims the empty top levels.', }) update(@Param('id', ParseUUIDPipe) id: string, @Body() dto: UpdateWarehouseZoneStackDto) { return this.stacksService.update(id, dto); } @Delete(':id') @BookingStaff(FREIGHT_PERMS.warehouseZones.delete) @ApiOperation({ summary: 'Delete a stack', description: 'Refused while containers still stand in it.' }) remove(@Param('id', ParseUUIDPipe) id: string) { return this.stacksService.remove(id); } }