mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
Gates the previously open support-agent, procurement, compliance, facilities, list-users and trade-access controllers, separates customer from staff routes across bookings, contracts, companies, billing, warehouses, files and train scheduling, and moves billing, overview, reports and the settings controllers onto their own keys instead of the blanket admin key. Drops the demo-permissions module and the untested notification test route.
613 lines
26 KiB
TypeScript
613 lines
26 KiB
TypeScript
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 { actorLabel } from './current-actor.util';
|
|
import { BookingStaff, MixedAudience } from '../../common/booking-guards';
|
|
import { FREIGHT_PERMS } from '../../seed/freight-permissions.registry';
|
|
import { BulkReceiveDto } from './dto/bulk-receive.dto';
|
|
import { BulkInspectDto } from './dto/bulk-inspect.dto';
|
|
import { DeliverInventoryDto } from './dto/deliver-inventory.dto';
|
|
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 { StoreInventoryDto } from './dto/store-inventory.dto';
|
|
import { ReceiveWarehouseInventoryDto } from './dto/receive-inventory.dto';
|
|
import { ApproveDeliveryDto } from './dto/approve-delivery.dto';
|
|
import { SetDoubleHandlingDto } from './dto/double-handling.dto';
|
|
import { ReleaseOrderDto } from './dto/release-order.dto';
|
|
import { ReserveInventoryDto } from './dto/reserve-inventory.dto';
|
|
import { UnloadBookingDto } from './dto/unload-booking.dto';
|
|
import { SchedulingReadFacade } from './scheduling-read.facade';
|
|
import { WarehouseInventoryService } from './warehouse-inventory.service';
|
|
import { HandoverService } from './handover.service';
|
|
|
|
@ApiTags('warehouse-inventory')
|
|
@ApiBearerAuth()
|
|
@Controller('warehouse-inventory')
|
|
export class WarehouseInventoryController {
|
|
constructor(
|
|
private readonly inventoryService: WarehouseInventoryService,
|
|
private readonly scheduling: SchedulingReadFacade,
|
|
private readonly handoverService: HandoverService,
|
|
) {}
|
|
|
|
@Get()
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'List warehouse inventory' })
|
|
findAll(@Query() filter: FilterWarehouseInventoryDto) {
|
|
return this.inventoryService.findAll(filter);
|
|
}
|
|
|
|
@Get('ready-for-loading')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'List inventory ready for loading' })
|
|
findReadyForLoading(@Query() filter: FilterWarehouseInventoryDto) {
|
|
return this.inventoryService.findReadyForLoading(filter);
|
|
}
|
|
|
|
@Get('inquiry')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Locate any item inside the warehouse' })
|
|
inquiry(@Query() filter: InquiryWarehouseInventoryDto) {
|
|
return this.inventoryService.inquiry(filter);
|
|
}
|
|
|
|
@Get('arrival-queue')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Arrived bookings awaiting unload / inspection' })
|
|
arrivalQueue() {
|
|
return this.inventoryService.arrivalQueue();
|
|
}
|
|
|
|
@Get('ops-stats')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'At-a-glance warehouse ops counters for the KPI strip' })
|
|
opsStats() {
|
|
return this.inventoryService.opsStats();
|
|
}
|
|
|
|
@Get('trucks-on-site')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({
|
|
summary: 'Trucks currently in the yard (customer self-haul + EDR last-mile)',
|
|
})
|
|
trucksOnSite() {
|
|
return this.inventoryService.trucksOnSite();
|
|
}
|
|
|
|
@Get('zone-occupancy')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Live occupancy per zone (rated capacity vs held) — heatmap data' })
|
|
zoneOccupancy(@Query('yardId') yardId?: string) {
|
|
return this.inventoryService.zoneOccupancy(yardId);
|
|
}
|
|
|
|
@Get('throughput')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Received-vs-dispatched throughput time series (week/month/year)' })
|
|
throughput(@Query('granularity') granularity?: string) {
|
|
const g = granularity === 'week' || granularity === 'year' ? granularity : 'month';
|
|
return this.inventoryService.throughput(g);
|
|
}
|
|
|
|
@Get('dwell-stats')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Dwell time of in-warehouse items: average + aging buckets' })
|
|
dwellStats() {
|
|
return this.inventoryService.dwellStats();
|
|
}
|
|
|
|
@Get('cycle-stats')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Average stage cycle times over recently dispatched items' })
|
|
cycleStats() {
|
|
return this.inventoryService.cycleStats();
|
|
}
|
|
|
|
@Get('gate-stats')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Gate/dock throughput: cleared today, turnaround, hourly clearances' })
|
|
gateStats() {
|
|
return this.inventoryService.gateStats();
|
|
}
|
|
|
|
@Post('auto-unload-arrived')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.unload)
|
|
@ApiOperation({ summary: 'Bulk auto-unload all arrived bookings into the warehouse' })
|
|
autoUnloadArrived() {
|
|
return this.inventoryService.autoUnloadArrived();
|
|
}
|
|
|
|
@Post('auto-load-ready')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.load)
|
|
@ApiOperation({ summary: 'Auto-load READY_FOR_LOADING inventory with PAID bookings' })
|
|
autoLoadReady() {
|
|
return this.inventoryService.autoLoadReady();
|
|
}
|
|
|
|
@Get('eligible-bookings')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'PAID bookings not yet received, classified IMPORT/EXPORT by route; omit direction for all' })
|
|
eligibleBookings(@Query('direction') direction?: string) {
|
|
const dir = direction === 'IMPORT' || direction === 'EXPORT' ? direction : undefined;
|
|
return this.inventoryService.eligibleBookings(dir);
|
|
}
|
|
|
|
@Post('receive-bulk')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.receive)
|
|
@ApiOperation({ summary: 'Bulk-receive selected eligible PAID bookings into a location' })
|
|
receiveBulk(@Body() dto: BulkReceiveDto, @CurrentUser() user: TCurrentUser) {
|
|
dto.performedBy = actorLabel(user) ?? dto.performedBy;
|
|
return this.inventoryService.bulkReceive(dto);
|
|
}
|
|
|
|
|
|
@Get('ready-to-load-export')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'EXPORT inventory that passed inspection and is READY_FOR_LOADING' })
|
|
readyToLoadExport() {
|
|
return this.inventoryService.readyToLoadExport();
|
|
}
|
|
|
|
@Get('received-export')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'EXPORT inventory that has been received and is awaiting inspection' })
|
|
receivedExport() {
|
|
return this.inventoryService.receivedExport();
|
|
}
|
|
|
|
@Get('loaded-export')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'EXPORT inventory that is LOADED and queued for dispatch' })
|
|
loadedExport() {
|
|
return this.inventoryService.loadedExport();
|
|
}
|
|
|
|
@Get('loadable-trains')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'EXPORT trains (pre-dispatch) with inventory waiting to be loaded' })
|
|
loadableTrains() {
|
|
return this.inventoryService.loadableTrains();
|
|
}
|
|
|
|
@Get('train/:scheduleId/loadable-items')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Container/cargo inventory assigned to a train, with allocated wagons' })
|
|
trainLoadableItems(@Param('scheduleId', ParseUUIDPipe) scheduleId: string) {
|
|
return this.inventoryService.trainLoadableItems(scheduleId);
|
|
}
|
|
|
|
@Post('train/:scheduleId/load')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.load)
|
|
@ApiOperation({ summary: 'Load selected inventory items onto their allocated wagons for a train' })
|
|
loadItemsOntoTrain(
|
|
@Param('scheduleId', ParseUUIDPipe) scheduleId: string,
|
|
@Body() dto: { inventoryIds: string[]; performedBy?: string },
|
|
@CurrentUser() user: TCurrentUser,
|
|
) {
|
|
return this.inventoryService.loadItemsOntoTrain(scheduleId, dto.inventoryIds ?? [], actorLabel(user) ?? 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 }, @CurrentUser() user: TCurrentUser) {
|
|
return this.inventoryService.bulkDispatchExport(dto.inventoryIds ?? [], actorLabel(user) ?? dto.performedBy);
|
|
}
|
|
|
|
@Post('bulk-mark-inspected')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.inspect)
|
|
@ApiOperation({ summary: 'Bulk mark received inventory inspection PASSED (EXPORT → READY_FOR_LOADING)' })
|
|
bulkMarkInspected(@Body() dto: BulkInspectDto) {
|
|
return this.inventoryService.bulkMarkInspected(dto);
|
|
}
|
|
|
|
@Post('bookings/:bookingId/unload')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.unload)
|
|
@ApiOperation({ summary: 'Unload a single arrived booking into a location' })
|
|
unloadBooking(
|
|
@Param('bookingId', ParseUUIDPipe) bookingId: string,
|
|
@Body() dto: UnloadBookingDto,
|
|
) {
|
|
return this.inventoryService.unloadBooking(bookingId, dto);
|
|
}
|
|
|
|
@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 | undefined,
|
|
@CurrentUser() user: TCurrentUser,
|
|
) {
|
|
return this.inventoryService.gateClearance(id, actorLabel(user) ?? performedBy);
|
|
}
|
|
|
|
@Get('import/arrive-queue')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Arrived IMPORT train schedules (route-derived), read-only from scheduling' })
|
|
importArriveQueue() {
|
|
return this.scheduling.importArriveQueue();
|
|
}
|
|
|
|
@Get('import/trains/:scheduleId/items')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Assigned bookings/items for an arrived import train (read-only)' })
|
|
importTrainDetail(@Param('scheduleId', ParseUUIDPipe) scheduleId: string) {
|
|
return this.scheduling.importTrainDetail(scheduleId);
|
|
}
|
|
|
|
@Post('import/auto-unload-arrived-bookings')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.unload)
|
|
@ApiOperation({ summary: 'Unload all eligible assigned bookings of an ARRIVED import train (→ UNLOADED)' })
|
|
autoUnloadArrivedBookings(@Body() dto: {
|
|
scheduleId: string;
|
|
warehouseId?: string;
|
|
performedBy?: string;
|
|
assignments?: { bookingId: string; warehouseId: string; yardId: string; zoneId: string }[];
|
|
}, @CurrentUser() user: TCurrentUser) {
|
|
return this.inventoryService.autoUnloadArrivedBookings(
|
|
dto.scheduleId,
|
|
actorLabel(user) ?? dto.performedBy,
|
|
dto.warehouseId,
|
|
dto.assignments,
|
|
);
|
|
}
|
|
|
|
@Get('import/unloaded-queue')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'IMPORT inventory in the Unloaded Queue (UNLOADED / destination inspection)' })
|
|
importUnloadedQueue() {
|
|
return this.inventoryService.importUnloadedQueue();
|
|
}
|
|
|
|
@Get('export/djibouti-arrival-queue')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Arrived EXPORT train schedules at Djibouti-side ports, ready for unloading' })
|
|
exportDjiboutiArrivalQueue(
|
|
@Query('scheduleId') scheduleId?: string,
|
|
@Query('destination') destination?: string,
|
|
@Query('status') status?: string,
|
|
@Query('dateFrom') dateFrom?: string,
|
|
@Query('dateTo') dateTo?: string,
|
|
) {
|
|
return this.scheduling.exportDjiboutiArrivalQueue({
|
|
scheduleId,
|
|
destination,
|
|
status,
|
|
dateFrom,
|
|
dateTo,
|
|
});
|
|
}
|
|
|
|
@Get('export/djibouti-trains/:scheduleId/items')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Assigned export bookings/items for an arrived Djibouti-side train' })
|
|
exportDjiboutiTrainDetail(@Param('scheduleId', ParseUUIDPipe) scheduleId: string) {
|
|
return this.scheduling.exportDjiboutiTrainDetail(scheduleId);
|
|
}
|
|
|
|
@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 }, @CurrentUser() user: TCurrentUser) {
|
|
return this.inventoryService.autoUnloadExportAtDjibouti(dto.scheduleId, actorLabel(user) ?? dto.performedBy);
|
|
}
|
|
|
|
@Get('import/pickup-ready-queue')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'IMPORT inventory that is PICKUP_READY (READY_FOR_PICKUP) awaiting pickup/dispatch' })
|
|
importPickupReadyQueue() {
|
|
return this.inventoryService.importPickupReadyQueue();
|
|
}
|
|
|
|
@Get('loadable-wagons')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'List wagons usable for loading (read-only from scheduling)' })
|
|
loadableWagons() {
|
|
return this.scheduling.listLoadableWagons();
|
|
}
|
|
|
|
@Get('booking/:bookingId/schedule')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Read-only schedule + wagon + departure status for a booking' })
|
|
bookingSchedule(@Param('bookingId', ParseUUIDPipe) bookingId: string) {
|
|
return this.scheduling.getBookingSchedule(bookingId);
|
|
}
|
|
|
|
@Post('receive')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.receive)
|
|
@ApiOperation({ summary: 'Receive inventory at a warehouse location' })
|
|
receive(@Body() dto: ReceiveWarehouseInventoryDto, @CurrentUser() user: TCurrentUser) {
|
|
dto.performedBy = actorLabel(user) ?? 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, @CurrentUser() user: TCurrentUser) {
|
|
dto.performedBy = actorLabel(user) ?? dto.performedBy;
|
|
return this.inventoryService.reserve(dto);
|
|
}
|
|
|
|
@Get(':id/movements')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Inventory movement history' })
|
|
movements(@Param('id', ParseUUIDPipe) id: string) {
|
|
return this.inventoryService.findMovements(id);
|
|
}
|
|
|
|
@Get(':id/activity')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Inventory activity log' })
|
|
activity(@Param('id', ParseUUIDPipe) id: string) {
|
|
return this.inventoryService.findActivity(id);
|
|
}
|
|
|
|
@Get(':id/loadings')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Loading records for an inventory item' })
|
|
loadings(@Param('id', ParseUUIDPipe) id: string) {
|
|
return this.inventoryService.findLoadingsByInventory(id);
|
|
}
|
|
|
|
@Post(':id/move')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.move)
|
|
@ApiOperation({ summary: 'Move inventory to another warehouse/yard/zone' })
|
|
move(@Param('id', ParseUUIDPipe) id: string, @Body() dto: MoveInventoryDto) {
|
|
return this.inventoryService.move(id, dto);
|
|
}
|
|
|
|
@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, @CurrentUser() user: TCurrentUser) {
|
|
return this.inventoryService.store(id, actorLabel(user) ?? 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 | undefined,
|
|
@CurrentUser() user: TCurrentUser,
|
|
) {
|
|
return this.inventoryService.readyForLoading(id, actorLabel(user) ?? performedBy);
|
|
}
|
|
|
|
@Post(':id/load')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.load)
|
|
@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/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 | undefined,
|
|
@CurrentUser() user: TCurrentUser,
|
|
) {
|
|
return this.inventoryService.readyForPickup(id, actorLabel(user) ?? performedBy);
|
|
}
|
|
|
|
@Post(':id/release')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.release)
|
|
@ApiOperation({ summary: 'Issue a DO / release order for ready-for-pickup inventory' })
|
|
release(@Param('id', ParseUUIDPipe) id: string, @Body() dto: ReleaseOrderDto) {
|
|
return this.inventoryService.release(id, dto);
|
|
}
|
|
|
|
@Get(':id/release-document')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'View warehouse release / exit paper PDF' })
|
|
async releaseDocument(@Param('id', ParseUUIDPipe) id: string, @Res() res: Response) {
|
|
const { filename, buffer } = await this.inventoryService.releaseDocument(id);
|
|
res.setHeader('Content-Type', 'application/pdf');
|
|
res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
|
|
res.setHeader('Content-Length', buffer.length);
|
|
return res.send(buffer);
|
|
}
|
|
|
|
@Get('customer-truck-exit-paper/:assignmentId')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Per-truck exit paper PDF (containers loaded on one customer truck)' })
|
|
async truckExitPaper(
|
|
@Param('assignmentId', ParseUUIDPipe) assignmentId: string,
|
|
@Res() res: Response,
|
|
) {
|
|
const { filename, buffer } = await this.inventoryService.truckExitPaper(assignmentId);
|
|
res.setHeader('Content-Type', 'application/pdf');
|
|
res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
|
|
res.setHeader('Content-Length', buffer.length);
|
|
return res.send(buffer);
|
|
}
|
|
|
|
@Get('edr-truck-exit-paper/:assignmentId')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Per-truck exit paper PDF for an EDR last-mile truck' })
|
|
async edrTruckExitPaper(
|
|
@Param('assignmentId', ParseUUIDPipe) assignmentId: string,
|
|
@Res() res: Response,
|
|
) {
|
|
const { filename, buffer } = await this.inventoryService.edrTruckExitPaper(assignmentId);
|
|
res.setHeader('Content-Type', 'application/pdf');
|
|
res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
|
|
res.setHeader('Content-Length', buffer.length);
|
|
return res.send(buffer);
|
|
}
|
|
|
|
@Get(':id/grn-document')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'View goods received note PDF' })
|
|
async grnDocument(@Param('id', ParseUUIDPipe) id: string, @Res() res: Response) {
|
|
const { filename, buffer } = await this.inventoryService.grnDocument(id);
|
|
res.setHeader('Content-Type', 'application/pdf');
|
|
res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
|
|
res.setHeader('Content-Length', buffer.length);
|
|
return res.send(buffer);
|
|
}
|
|
|
|
@Get(':id/handover-document')
|
|
@MixedAudience(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'View import goods handover document PDF' })
|
|
async handoverDocument(@Param('id', ParseUUIDPipe) id: string, @Res() res: Response) {
|
|
const { filename, buffer } = await this.inventoryService.handoverDocument(id);
|
|
res.setHeader('Content-Type', 'application/pdf');
|
|
res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
|
|
res.setHeader('Content-Length', buffer.length);
|
|
return res.send(buffer);
|
|
}
|
|
|
|
@Post('bookings/:bookingId/approve-delivery')
|
|
@MixedAudience(FREIGHT_PERMS.warehouseInventory.deliver)
|
|
@ApiOperation({ summary: "Approve delivery — customer records their full name (signature optional)" })
|
|
approveDeliveryForBooking(
|
|
@Param('bookingId', ParseUUIDPipe) bookingId: string,
|
|
@Body() dto: ApproveDeliveryDto,
|
|
@Request() req: { user?: { id?: string; sub?: string } },
|
|
@CurrentUser() user: TCurrentUser,
|
|
) {
|
|
return this.inventoryService.approveDeliveryForBooking(
|
|
bookingId,
|
|
user?.id ?? req.user?.id ?? req.user?.sub,
|
|
dto.signerName,
|
|
);
|
|
}
|
|
|
|
@Get('bookings/:bookingId/handovers')
|
|
@MixedAudience(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Handover records for a booking (per-booking or per-truck)' })
|
|
bookingHandovers(@Param('bookingId', ParseUUIDPipe) bookingId: string) {
|
|
return this.handoverService.list(bookingId);
|
|
}
|
|
|
|
@Post('handovers/:handoverId/sign')
|
|
@MixedAudience(FREIGHT_PERMS.warehouseInventory.deliver)
|
|
@ApiOperation({ summary: 'Customer signs one handover (EDR last-mile: one signature per truck)' })
|
|
signHandover(
|
|
@Param('handoverId', ParseUUIDPipe) handoverId: string,
|
|
@Body() dto: ApproveDeliveryDto,
|
|
@Request() req: { user?: { id?: string; sub?: string } },
|
|
@CurrentUser() user: TCurrentUser,
|
|
) {
|
|
return this.inventoryService.signHandover(
|
|
handoverId,
|
|
user?.id ?? req.user?.id ?? req.user?.sub,
|
|
dto.signerName,
|
|
);
|
|
}
|
|
|
|
@Post('bookings/:bookingId/request-handover-signature')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.deliver)
|
|
@ApiOperation({ summary: 'Ask the customer to sign the handover (creates one if none, then notifies)' })
|
|
requestHandoverSignature(@Param('bookingId', ParseUUIDPipe) bookingId: string) {
|
|
return this.handoverService.requestSignature(bookingId);
|
|
}
|
|
|
|
@Get('bookings/:bookingId/grn-document')
|
|
@MixedAudience(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'View GRN PDF for a booking (customer portal)' })
|
|
async bookingGrnDocument(@Param('bookingId', ParseUUIDPipe) bookingId: string, @Res() res: Response) {
|
|
const { filename, buffer } = await this.inventoryService.grnDocumentForBooking(bookingId);
|
|
res.setHeader('Content-Type', 'application/pdf');
|
|
res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
|
|
res.setHeader('Content-Length', buffer.length);
|
|
return res.send(buffer);
|
|
}
|
|
|
|
@Get('bookings/:bookingId/release-document')
|
|
@MixedAudience(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'View gate-clearance / release-order PDF for a booking (customer portal)' })
|
|
async bookingReleaseDocument(@Param('bookingId', ParseUUIDPipe) bookingId: string, @Res() res: Response) {
|
|
const { filename, buffer } = await this.inventoryService.releaseDocumentForBooking(bookingId);
|
|
res.setHeader('Content-Type', 'application/pdf');
|
|
res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
|
|
res.setHeader('Content-Length', buffer.length);
|
|
return res.send(buffer);
|
|
}
|
|
|
|
@Get('bookings/:bookingId/handover-document')
|
|
@MixedAudience(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'View import goods handover document PDF (resolved by booking; ?handoverId= for the per-truck variant)' })
|
|
async bookingHandoverDocument(
|
|
@Param('bookingId', ParseUUIDPipe) bookingId: string,
|
|
@Res() res: Response,
|
|
@Query('handoverId') handoverId?: string,
|
|
) {
|
|
const { filename, buffer } = await this.inventoryService.handoverDocumentForBooking(
|
|
bookingId,
|
|
handoverId || undefined,
|
|
);
|
|
res.setHeader('Content-Type', 'application/pdf');
|
|
res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
|
|
res.setHeader('Content-Length', buffer.length);
|
|
return res.send(buffer);
|
|
}
|
|
|
|
@Patch('bookings/:bookingId/double-handling')
|
|
@BookingStaff([FREIGHT_PERMS.warehouseInventory.unload, FREIGHT_PERMS.warehouseInventory.inspect])
|
|
@ApiOperation({
|
|
summary: 'Record Yes/No double handling after unloading (Yes applies the double-handling fee rule)',
|
|
})
|
|
setDoubleHandling(
|
|
@Param('bookingId', ParseUUIDPipe) bookingId: string,
|
|
@Body() dto: SetDoubleHandlingDto,
|
|
@CurrentUser() user: TCurrentUser,
|
|
) {
|
|
return this.inventoryService.setDoubleHandling(
|
|
bookingId,
|
|
dto.doubleHandling,
|
|
actorLabel(user),
|
|
);
|
|
}
|
|
|
|
@Get('bookings/:bookingId/container-items')
|
|
@MixedAudience(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: 'Per-container/bulk items of a booking with lifecycle stage + refs' })
|
|
containerItems(@Param('bookingId', ParseUUIDPipe) bookingId: string) {
|
|
return this.inventoryService.containerItems(bookingId);
|
|
}
|
|
|
|
@Get('bookings/:bookingId/container-weights')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: "A booking's containers + VGM cargo weight (tonnes) for exit weighing" })
|
|
containerWeights(@Param('bookingId', ParseUUIDPipe) bookingId: string) {
|
|
return this.inventoryService.bookingContainerWeights(bookingId);
|
|
}
|
|
|
|
@Get('bookings/:bookingId/location')
|
|
@MixedAudience(FREIGHT_PERMS.warehouseInventory.view)
|
|
@ApiOperation({ summary: "Warehouse location of a booking's inventory (customer portal)" })
|
|
bookingLocation(@Param('bookingId', ParseUUIDPipe) bookingId: string) {
|
|
return this.inventoryService.bookingLocation(bookingId);
|
|
}
|
|
|
|
@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, @CurrentUser() user: TCurrentUser) {
|
|
dto.performedBy = actorLabel(user) ?? 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 | undefined,
|
|
@CurrentUser() user: TCurrentUser,
|
|
) {
|
|
return this.inventoryService.dispatch(id, actorLabel(user) ?? performedBy);
|
|
}
|
|
}
|