mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
feat(warehouse): stamp authenticated user as action actor (performedBy)
Warehouse mutation endpoints now record the JWT-authenticated user as the actor (user.id) instead of trusting a client-supplied performedBy string, unlocking per-operator productivity metrics and a trustworthy audit trail. Covers receive, receive-bulk, reserve, store, ready-for-loading, ready-for-pickup, load-onto- train, bulk-dispatch, dispatch, deliver, gate-clearance, approve-delivery, the Djibouti/import auto-unload actions, and fee-invoice generation. The prior client value is kept only as a fallback for unauthenticated/internal calls. move/load do not yet carry an actor (their DTOs have no performedBy) — separate follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { Body, Controller, Get, Param, ParseUUIDPipe, Patch, Post, Query, Request, Res } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import type { Response } from 'express';
|
||||
import { CurrentUser } from '@edr/api-common';
|
||||
import type { TCurrentUser } from '@tria-plc/api-common/modules/auth/types/current-user.type';
|
||||
|
||||
import { BookingStaff } from '../../common/booking-guards';
|
||||
import { FREIGHT_PERMS } from '../../seed/freight-permissions.registry';
|
||||
@@ -127,7 +129,8 @@ export class WarehouseInventoryController {
|
||||
@Post('receive-bulk')
|
||||
@BookingStaff(FREIGHT_PERMS.warehouseInventory.receive)
|
||||
@ApiOperation({ summary: 'Bulk-receive selected eligible PAID bookings into a location' })
|
||||
receiveBulk(@Body() dto: BulkReceiveDto) {
|
||||
receiveBulk(@Body() dto: BulkReceiveDto, @CurrentUser() user: TCurrentUser) {
|
||||
dto.performedBy = user?.id ?? dto.performedBy;
|
||||
return this.inventoryService.bulkReceive(dto);
|
||||
}
|
||||
|
||||
@@ -173,15 +176,16 @@ export class WarehouseInventoryController {
|
||||
loadItemsOntoTrain(
|
||||
@Param('scheduleId', ParseUUIDPipe) scheduleId: string,
|
||||
@Body() dto: { inventoryIds: string[]; performedBy?: string },
|
||||
@CurrentUser() user: TCurrentUser,
|
||||
) {
|
||||
return this.inventoryService.loadItemsOntoTrain(scheduleId, dto.inventoryIds ?? [], dto.performedBy);
|
||||
return this.inventoryService.loadItemsOntoTrain(scheduleId, dto.inventoryIds ?? [], user?.id ?? dto.performedBy);
|
||||
}
|
||||
|
||||
@Post('bulk-dispatch-export')
|
||||
@BookingStaff(FREIGHT_PERMS.warehouseInventory.dispatch)
|
||||
@ApiOperation({ summary: 'Bulk-dispatch loaded EXPORT inventory (LOADED → DISPATCHED)' })
|
||||
bulkDispatchExport(@Body() dto: { inventoryIds: string[]; performedBy?: string }) {
|
||||
return this.inventoryService.bulkDispatchExport(dto.inventoryIds ?? [], dto.performedBy);
|
||||
bulkDispatchExport(@Body() dto: { inventoryIds: string[]; performedBy?: string }, @CurrentUser() user: TCurrentUser) {
|
||||
return this.inventoryService.bulkDispatchExport(dto.inventoryIds ?? [], user?.id ?? dto.performedBy);
|
||||
}
|
||||
|
||||
@Post('bulk-mark-inspected')
|
||||
@@ -204,8 +208,12 @@ export class WarehouseInventoryController {
|
||||
@Post(':id/gate-clearance')
|
||||
@BookingStaff(FREIGHT_PERMS.warehouseInventory.gatePass)
|
||||
@ApiOperation({ summary: 'Final terminal release / gate clearance (blocked while fees unpaid)' })
|
||||
gateClearance(@Param('id', ParseUUIDPipe) id: string, @Body('performedBy') performedBy?: string) {
|
||||
return this.inventoryService.gateClearance(id, performedBy);
|
||||
gateClearance(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Body('performedBy') performedBy: string | undefined,
|
||||
@CurrentUser() user: TCurrentUser,
|
||||
) {
|
||||
return this.inventoryService.gateClearance(id, user?.id ?? performedBy);
|
||||
}
|
||||
|
||||
@Get('import/arrive-queue')
|
||||
@@ -230,10 +238,10 @@ export class WarehouseInventoryController {
|
||||
warehouseId?: string;
|
||||
performedBy?: string;
|
||||
assignments?: { bookingId: string; warehouseId: string; yardId: string; zoneId: string }[];
|
||||
}) {
|
||||
}, @CurrentUser() user: TCurrentUser) {
|
||||
return this.inventoryService.autoUnloadArrivedBookings(
|
||||
dto.scheduleId,
|
||||
dto.performedBy,
|
||||
user?.id ?? dto.performedBy,
|
||||
dto.warehouseId,
|
||||
dto.assignments,
|
||||
);
|
||||
@@ -275,8 +283,8 @@ export class WarehouseInventoryController {
|
||||
@Post('export/auto-unload-at-djibouti')
|
||||
@BookingStaff(FREIGHT_PERMS.warehouseInventory.unload)
|
||||
@ApiOperation({ summary: 'Unload all eligible export items assigned to an arrived Djibouti-side train' })
|
||||
autoUnloadExportAtDjibouti(@Body() dto: { scheduleId: string; performedBy?: string }) {
|
||||
return this.inventoryService.autoUnloadExportAtDjibouti(dto.scheduleId, dto.performedBy);
|
||||
autoUnloadExportAtDjibouti(@Body() dto: { scheduleId: string; performedBy?: string }, @CurrentUser() user: TCurrentUser) {
|
||||
return this.inventoryService.autoUnloadExportAtDjibouti(dto.scheduleId, user?.id ?? dto.performedBy);
|
||||
}
|
||||
|
||||
@Get('import/pickup-ready-queue')
|
||||
@@ -303,14 +311,16 @@ export class WarehouseInventoryController {
|
||||
@Post('receive')
|
||||
@BookingStaff(FREIGHT_PERMS.warehouseInventory.receive)
|
||||
@ApiOperation({ summary: 'Receive inventory at a warehouse location' })
|
||||
receive(@Body() dto: ReceiveWarehouseInventoryDto) {
|
||||
receive(@Body() dto: ReceiveWarehouseInventoryDto, @CurrentUser() user: TCurrentUser) {
|
||||
dto.performedBy = user?.id ?? dto.performedBy;
|
||||
return this.inventoryService.receive(dto);
|
||||
}
|
||||
|
||||
@Post('reserve')
|
||||
@BookingStaff(FREIGHT_PERMS.warehouseInventory.move)
|
||||
@ApiOperation({ summary: 'Reserve stored inventory for a PAID booking' })
|
||||
reserve(@Body() dto: ReserveInventoryDto) {
|
||||
reserve(@Body() dto: ReserveInventoryDto, @CurrentUser() user: TCurrentUser) {
|
||||
dto.performedBy = user?.id ?? dto.performedBy;
|
||||
return this.inventoryService.reserve(dto);
|
||||
}
|
||||
|
||||
@@ -345,15 +355,19 @@ export class WarehouseInventoryController {
|
||||
@Post(':id/store')
|
||||
@BookingStaff(FREIGHT_PERMS.warehouseInventory.move)
|
||||
@ApiOperation({ summary: 'Mark received inventory as STORED (optional explicit warehouse/yard/zone)' })
|
||||
store(@Param('id', ParseUUIDPipe) id: string, @Body() dto: StoreInventoryDto) {
|
||||
return this.inventoryService.store(id, dto.performedBy, dto);
|
||||
store(@Param('id', ParseUUIDPipe) id: string, @Body() dto: StoreInventoryDto, @CurrentUser() user: TCurrentUser) {
|
||||
return this.inventoryService.store(id, user?.id ?? dto.performedBy, dto);
|
||||
}
|
||||
|
||||
@Post(':id/ready-for-loading')
|
||||
@BookingStaff(FREIGHT_PERMS.warehouseInventory.move)
|
||||
@ApiOperation({ summary: 'Mark reserved inventory READY_FOR_LOADING' })
|
||||
readyForLoading(@Param('id', ParseUUIDPipe) id: string, @Body('performedBy') performedBy?: string) {
|
||||
return this.inventoryService.readyForLoading(id, performedBy);
|
||||
readyForLoading(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Body('performedBy') performedBy: string | undefined,
|
||||
@CurrentUser() user: TCurrentUser,
|
||||
) {
|
||||
return this.inventoryService.readyForLoading(id, user?.id ?? performedBy);
|
||||
}
|
||||
|
||||
@Post(':id/load')
|
||||
@@ -366,8 +380,12 @@ export class WarehouseInventoryController {
|
||||
@Post(':id/ready-for-pickup')
|
||||
@BookingStaff(FREIGHT_PERMS.warehouseInventory.move)
|
||||
@ApiOperation({ summary: 'Mark inspected IMPORT inventory READY_FOR_PICKUP' })
|
||||
readyForPickup(@Param('id', ParseUUIDPipe) id: string, @Body('performedBy') performedBy?: string) {
|
||||
return this.inventoryService.readyForPickup(id, performedBy);
|
||||
readyForPickup(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Body('performedBy') performedBy: string | undefined,
|
||||
@CurrentUser() user: TCurrentUser,
|
||||
) {
|
||||
return this.inventoryService.readyForPickup(id, user?.id ?? performedBy);
|
||||
}
|
||||
|
||||
@Post(':id/release')
|
||||
@@ -429,10 +447,11 @@ export class WarehouseInventoryController {
|
||||
@Param('bookingId', ParseUUIDPipe) bookingId: string,
|
||||
@Body() dto: ApproveDeliveryDto,
|
||||
@Request() req: { user?: { id?: string; sub?: string } },
|
||||
@CurrentUser() user: TCurrentUser,
|
||||
) {
|
||||
return this.inventoryService.approveDeliveryForBooking(
|
||||
bookingId,
|
||||
req.user?.id ?? req.user?.sub,
|
||||
user?.id ?? req.user?.id ?? req.user?.sub,
|
||||
dto.signerName,
|
||||
);
|
||||
}
|
||||
@@ -494,14 +513,19 @@ export class WarehouseInventoryController {
|
||||
@Post(':id/deliver')
|
||||
@BookingStaff(FREIGHT_PERMS.warehouseInventory.deliver)
|
||||
@ApiOperation({ summary: 'Deliver import goods to the customer + capture proof of delivery' })
|
||||
deliver(@Param('id', ParseUUIDPipe) id: string, @Body() dto: DeliverInventoryDto) {
|
||||
deliver(@Param('id', ParseUUIDPipe) id: string, @Body() dto: DeliverInventoryDto, @CurrentUser() user: TCurrentUser) {
|
||||
dto.performedBy = user?.id ?? dto.performedBy;
|
||||
return this.inventoryService.deliver(id, dto);
|
||||
}
|
||||
|
||||
@Patch(':id/dispatch')
|
||||
@BookingStaff(FREIGHT_PERMS.warehouseInventory.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);
|
||||
dispatch(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Body('performedBy') performedBy: string | undefined,
|
||||
@CurrentUser() user: TCurrentUser,
|
||||
) {
|
||||
return this.inventoryService.dispatch(id, user?.id ?? performedBy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Body, Controller, Get, Param, ParseUUIDPipe, Patch, Post, Query, Res } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import type { Response } from 'express';
|
||||
import { CurrentUser } from '@edr/api-common';
|
||||
import type { TCurrentUser } from '@tria-plc/api-common/modules/auth/types/current-user.type';
|
||||
|
||||
import { BookingStaff } from '../../common/booking-guards';
|
||||
import { FREIGHT_PERMS } from '../../seed/freight-permissions.registry';
|
||||
@@ -17,7 +19,8 @@ export class WarehouseInvoiceController {
|
||||
@Post('warehouse-inventory/:id/generate-fee-invoice')
|
||||
@BookingStaff(FREIGHT_PERMS.warehouseFeeInvoices.generate)
|
||||
@ApiOperation({ summary: 'Generate a warehouse fee invoice from Batch 5 fee calculation' })
|
||||
generate(@Param('id', ParseUUIDPipe) id: string, @Body() dto: GenerateInvoiceDto) {
|
||||
generate(@Param('id', ParseUUIDPipe) id: string, @Body() dto: GenerateInvoiceDto, @CurrentUser() user: TCurrentUser) {
|
||||
dto.performedBy = user?.id ?? dto.performedBy;
|
||||
return this.invoiceService.generateForInventory(id, dto);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user