mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 23:28:11 +00:00
Adds the operational-performance layer to the warehouse cockpit:
- Dwell time & aging: dwellStats() (avg + 0-3/4-7/8-14/15+ buckets over
in-warehouse items) → DwellAgingCard histogram.
- Cycle time & on-time: cycleStats() (arrived→ready→loaded→dispatched stage
averages + dock-to-dispatch) and onTimeDispatchStats() (share of items that
left before their storage free-days expired, resolved via the fee engine's
own rule matching) → CycleTimeCard.
- Live tiles: opsStats() now returns receivedYesterday; KpiStrip renders an
optional ▲/▼ delta, and the ops strip shows received-today vs yesterday.
New endpoints: GET /warehouse-inventory/{dwell-stats,cycle-stats} and
/warehouse-fees/on-time-dispatch (all guarded). New "Performance" section on
WarehouseDashboardPage. On-time reads N/A when there is no sample / no active
storage rule.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
147 lines
6.0 KiB
TypeScript
147 lines
6.0 KiB
TypeScript
import { Body, Controller, Delete, Get, HttpCode, 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 {
|
|
AllocationPreviewDto,
|
|
CreateAllocationRuleDto,
|
|
UpdateAllocationRuleDto,
|
|
} from './dto/allocation-rule.dto';
|
|
import { CreateFeeRuleDto, UpdateFeeRuleDto } from './dto/fee-rule.dto';
|
|
import { AcknowledgeAccrualDto } from './dto/acknowledge-accrual.dto';
|
|
import { WarehouseAllocationService } from './warehouse-allocation.service';
|
|
import { WarehouseFeeService } from './warehouse-fee.service';
|
|
|
|
@ApiTags('warehouse-rules')
|
|
@ApiBearerAuth()
|
|
@Controller()
|
|
export class WarehouseRulesController {
|
|
constructor(
|
|
private readonly allocationService: WarehouseAllocationService,
|
|
private readonly feeService: WarehouseFeeService,
|
|
) {}
|
|
|
|
// ── Allocation rules ───────────────────────────────────────────────────────
|
|
@Get('warehouse-allocation-rules')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseAllocationRules.view)
|
|
@ApiOperation({ summary: 'List warehouse allocation rules' })
|
|
listAllocationRules() {
|
|
return this.allocationService.listRules();
|
|
}
|
|
|
|
@Post('warehouse-allocation-rules')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseAllocationRules.create)
|
|
@ApiOperation({ summary: 'Create a warehouse allocation rule' })
|
|
createAllocationRule(@Body() dto: CreateAllocationRuleDto) {
|
|
return this.allocationService.createRule(dto);
|
|
}
|
|
|
|
@Patch('warehouse-allocation-rules/:id')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseAllocationRules.update)
|
|
@ApiOperation({ summary: 'Update a warehouse allocation rule' })
|
|
updateAllocationRule(@Param('id', ParseUUIDPipe) id: string, @Body() dto: UpdateAllocationRuleDto) {
|
|
return this.allocationService.updateRule(id, dto);
|
|
}
|
|
|
|
@Delete('warehouse-allocation-rules/:id')
|
|
@HttpCode(204)
|
|
@BookingStaff(FREIGHT_PERMS.warehouseAllocationRules.delete)
|
|
@ApiOperation({ summary: 'Delete a warehouse allocation rule' })
|
|
deleteAllocationRule(@Param('id', ParseUUIDPipe) id: string) {
|
|
return this.allocationService.deleteRule(id);
|
|
}
|
|
|
|
@Post('warehouse-allocation/preview')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseAllocationRules.view)
|
|
@ApiOperation({ summary: 'Preview the yard/warehouse/zone a booking would be allocated to' })
|
|
previewAllocation(@Body() dto: AllocationPreviewDto) {
|
|
return this.allocationService.resolveLocation(dto);
|
|
}
|
|
|
|
// ── Fee rules ────────────────────────────────────────────────────────────────
|
|
@Get('warehouse-fee-rules')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseFeeRules.view)
|
|
@ApiOperation({ summary: 'List storage / demurrage fee rules' })
|
|
listFeeRules() {
|
|
return this.feeService.listRules();
|
|
}
|
|
|
|
@Post('warehouse-fee-rules')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseFeeRules.create)
|
|
@ApiOperation({ summary: 'Create a storage / demurrage fee rule' })
|
|
createFeeRule(@Body() dto: CreateFeeRuleDto) {
|
|
return this.feeService.createRule(dto);
|
|
}
|
|
|
|
@Patch('warehouse-fee-rules/:id')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseFeeRules.update)
|
|
@ApiOperation({ summary: 'Update a fee rule' })
|
|
updateFeeRule(@Param('id', ParseUUIDPipe) id: string, @Body() dto: UpdateFeeRuleDto) {
|
|
return this.feeService.updateRule(id, dto);
|
|
}
|
|
|
|
@Delete('warehouse-fee-rules/:id')
|
|
@HttpCode(204)
|
|
@BookingStaff(FREIGHT_PERMS.warehouseFeeRules.delete)
|
|
@ApiOperation({ summary: 'Delete a fee rule' })
|
|
deleteFeeRule(@Param('id', ParseUUIDPipe) id: string) {
|
|
return this.feeService.deleteRule(id);
|
|
}
|
|
|
|
@Get('warehouse-fees/accrual-dashboard')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseFeeRules.view)
|
|
@ApiOperation({ summary: 'Live per-item fee accrual (storage/demurrage) with alerts' })
|
|
accrualDashboard(@Query('billingCurrency') billingCurrency?: string) {
|
|
return this.feeService.accrualDashboard(billingCurrency);
|
|
}
|
|
|
|
@Get('warehouse-fees/on-time-dispatch')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseFeeRules.view)
|
|
@ApiOperation({ summary: 'On-time dispatch rate — items that left before storage free-days expired' })
|
|
onTimeDispatch() {
|
|
return this.feeService.onTimeDispatchStats();
|
|
}
|
|
|
|
@Post('warehouse-fees/accrual/:inventoryId/acknowledge')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseFeeRules.update)
|
|
@ApiOperation({ summary: 'Acknowledge / snooze an item fee-accrual alert' })
|
|
acknowledgeAccrual(
|
|
@Param('inventoryId', ParseUUIDPipe) inventoryId: string,
|
|
@Body() dto: AcknowledgeAccrualDto,
|
|
) {
|
|
return this.feeService.acknowledgeAccrual(inventoryId, {
|
|
snoozeDays: dto.snoozeDays,
|
|
note: dto.note,
|
|
});
|
|
}
|
|
|
|
@Delete('warehouse-fees/accrual/:inventoryId/acknowledge')
|
|
@HttpCode(204)
|
|
@BookingStaff(FREIGHT_PERMS.warehouseFeeRules.update)
|
|
@ApiOperation({ summary: 'Remove an accrual acknowledgement (re-surface for alerts)' })
|
|
unacknowledgeAccrual(@Param('inventoryId', ParseUUIDPipe) inventoryId: string) {
|
|
return this.feeService.unacknowledgeAccrual(inventoryId);
|
|
}
|
|
|
|
@Get('warehouse-inventory/:id/fee-preview')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseFeeRules.view)
|
|
@ApiOperation({ summary: 'Preview demurrage + storage fees for an inventory item' })
|
|
feePreview(
|
|
@Param('id', ParseUUIDPipe) id: string,
|
|
@Query('billingCurrency') billingCurrency?: string,
|
|
) {
|
|
return this.feeService.previewForInventory(id, billingCurrency);
|
|
}
|
|
|
|
@Get('last-mile/:id/truck-detention-preview')
|
|
@BookingStaff(FREIGHT_PERMS.warehouseFeeRules.view)
|
|
@ApiOperation({ summary: 'Preview truck detention for a last-mile leg (per truck per day after grace)' })
|
|
truckDetentionPreview(
|
|
@Param('id', ParseUUIDPipe) id: string,
|
|
@Query('billingCurrency') billingCurrency?: string,
|
|
) {
|
|
return this.feeService.previewTruckDetention(id, billingCurrency);
|
|
}
|
|
}
|