feat(warehouse): Batch 10 — import post-inspection actions (pickup / last-mile / store / dispatch)

- READY_FOR_PICKUP transitions now → DELIVERED (customer pickup) OR DISPATCHED (dispatch out) OR
  STORED (operator-chosen storage) — pickup and dispatch kept separate. No auto-storage.
- GET /warehouse-inventory/import/pickup-ready-queue: READY_FOR_PICKUP import items (shared query
  with the unloaded queue, route-derived import filter)
- WarehouseInventoryTable: PICKUP_READY rows now show explicit Store + Dispatch buttons alongside
  the customer-pickup (release/deliver) next action; reuses the existing advance() dispatcher
- Import → Dispatch Queue tab renders pickup-ready items via InventoryWorkbench (all existing
  actions + modals intact) with Last Mile shown only when door delivery was requested
- Verified: pickup-ready item → Store → STORED; → Dispatch → DISPATCHED

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hagernesh
2026-06-21 14:38:23 +00:00
parent 040ba6d495
commit 41d21edae5
8 changed files with 100 additions and 11 deletions

View File

@@ -37,7 +37,10 @@ export const WAREHOUSE_INVENTORY_TRANSITIONS: Record<WarehouseInventoryStatus, W
READY_FOR_LOADING: ['LOADED'],
LOADED: ['DISPATCHED'],
DISPATCHED: [],
READY_FOR_PICKUP: ['DELIVERED'],
// Batch 10: an inspected import item can leave by customer pickup (DELIVERED) or be dispatched
// out by EDR (DISPATCHED) — kept separate — or be put into storage (STORED) if no one collects
// it / customs or inspection hold / operator chooses to store.
READY_FOR_PICKUP: ['DELIVERED', 'STORED', 'DISPATCHED'],
DELIVERED: [],
};

View File

@@ -142,6 +142,12 @@ export class WarehouseInventoryController {
return this.inventoryService.importUnloadedQueue();
}
@Get('import/pickup-ready-queue')
@ApiOperation({ summary: 'IMPORT inventory that is PICKUP_READY (READY_FOR_PICKUP) awaiting pickup/dispatch' })
importPickupReadyQueue() {
return this.inventoryService.importPickupReadyQueue();
}
@Get('loadable-wagons')
@ApiOperation({ summary: 'List wagons usable for loading (read-only from scheduling)' })
loadableWagons() {

View File

@@ -706,12 +706,8 @@ export class WarehouseInventoryService {
return this.exportInventoryByStatus('LOADED');
}
/**
* Batch 9 — IMPORT inventory sitting in the Unloaded Queue (UNLOADED / destination-inspection
* states), with the columns the inspection screen needs. Direction is route-derived so only
* import items appear. Read-only.
*/
async importUnloadedQueue(): Promise<ImportUnloadedRow[]> {
/** Shared query for the import queues — IMPORT inventory at the given statuses, inspection columns. */
private async importQueueByStatuses(statuses: string[]): Promise<ImportUnloadedRow[]> {
const rows: Array<
ImportUnloadedRow & { originCountry: string | null; destinationCountry: string | null }
> = await this.dataSource.query(
@@ -743,8 +739,9 @@ export class WarehouseInventoryService {
LEFT JOIN freight.train_schedule_bookings tsb ON tsb.booking_id = b.id AND tsb.deleted_at IS NULL
LEFT JOIN freight.train_schedules ts ON ts.id = tsb.train_schedule_id
WHERE inv.deleted_at IS NULL
AND inv.status IN ('UNLOADED', 'DESTINATION_INSPECTION', 'UNDER_INSPECTION')
AND inv.status = ANY($1)
ORDER BY inv.created_at DESC`,
[statuses],
);
return rows
@@ -755,6 +752,22 @@ export class WarehouseInventoryService {
.map(({ originCountry: _oc, destinationCountry: _dc, ...rest }) => rest);
}
/**
* Batch 9 — IMPORT inventory sitting in the Unloaded Queue (UNLOADED / destination-inspection
* states), with the columns the inspection screen needs. Read-only.
*/
importUnloadedQueue(): Promise<ImportUnloadedRow[]> {
return this.importQueueByStatuses(['UNLOADED', 'DESTINATION_INSPECTION', 'UNDER_INSPECTION']);
}
/**
* Batch 10 — IMPORT inventory that passed inspection and is PICKUP_READY (READY_FOR_PICKUP),
* awaiting customer pickup / last mile / store / dispatch. Read-only.
*/
importPickupReadyQueue(): Promise<ImportUnloadedRow[]> {
return this.importQueueByStatuses(['READY_FOR_PICKUP']);
}
/**
* Bulk-dispatch loaded EXPORT inventory. Reuses the single-item dispatch transition
* (status LOADED → DISPATCHED, capacity freed, movement/activity logged). Items not