mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-06 12:25:02 +00:00
feat(warehouses): backoffice UI for container stack positions
Surfaces the physical stack/slot model in the staff app. - ZoneLayoutModal: stacks drawn level by level with occupancy colours, configured-vs-built-vs-occupied counts, and stack create/delete plus block/reserve/free on empty levels - SlotPicker in the store and move modals, offering only the next fillable level of each stack so the form cannot suggest a position the API will refuse - move modal warns when a container is buried, lists the blockers, and disables the action instead of firing a 409 - fix: move() now asserts accessibility server-side, matching release — both are exits from a stack
This commit is contained in:
@@ -154,6 +154,14 @@ import type {
|
||||
WarehouseYard,
|
||||
WarehouseZone,
|
||||
ZoneContentItem,
|
||||
ZoneLayout,
|
||||
ZoneSlotSummary,
|
||||
WarehouseZoneStack,
|
||||
WarehouseZoneSlot,
|
||||
SaveStackPayload,
|
||||
AvailableSlot,
|
||||
ContainerAccessibility,
|
||||
FindSlotPayload,
|
||||
} from "@/types/warehouse";
|
||||
import { endpoint } from "@/utils/endpoint";
|
||||
import {
|
||||
@@ -1259,6 +1267,85 @@ export const api = {
|
||||
({ zoneId }) => ["warehouse-zones", zoneId, "contents"],
|
||||
),
|
||||
|
||||
zoneLayout: endpoint<{ zoneId: string }, ZoneLayout>(
|
||||
"warehouse-zones",
|
||||
"layout",
|
||||
({ zoneId }) => warehouseService.zoneLayout(zoneId).then((r) => r.data),
|
||||
({ zoneId }) => ["warehouse-zones", zoneId, "layout"],
|
||||
),
|
||||
|
||||
zoneSlotSummary: endpoint<{ zoneId: string }, ZoneSlotSummary>(
|
||||
"warehouse-zones",
|
||||
"slot-summary",
|
||||
({ zoneId }) => warehouseService.zoneSlotSummary(zoneId).then((r) => r.data),
|
||||
({ zoneId }) => ["warehouse-zones", zoneId, "slot-summary"],
|
||||
),
|
||||
|
||||
// ── Ground stacks and slots ────────────────────────────────────────────
|
||||
listStacks: endpoint<{ zoneId: string }, WarehouseZoneStack[]>(
|
||||
"warehouse-zone-stacks",
|
||||
"list",
|
||||
({ zoneId }) => warehouseService.listStacks(zoneId).then((r) => r.data),
|
||||
({ zoneId }) => ["warehouse-zone-stacks", zoneId],
|
||||
),
|
||||
|
||||
createStack: endpoint<
|
||||
{ zoneId: string; payload: SaveStackPayload },
|
||||
WarehouseZoneStack
|
||||
>(
|
||||
"warehouse-zone-stacks",
|
||||
"create",
|
||||
({ zoneId, payload }) =>
|
||||
warehouseService.createStack(zoneId, payload).then((r) => r.data),
|
||||
undefined,
|
||||
({ zoneId }) => [
|
||||
["warehouse-zone-stacks", zoneId],
|
||||
["warehouse-zones", zoneId, "layout"],
|
||||
["warehouse-zones", zoneId, "slot-summary"],
|
||||
],
|
||||
),
|
||||
|
||||
updateStack: endpoint<
|
||||
{ id: string; zoneId: string; payload: Partial<SaveStackPayload> },
|
||||
WarehouseZoneStack
|
||||
>(
|
||||
"warehouse-zone-stacks",
|
||||
"update",
|
||||
({ id, payload }) => warehouseService.updateStack(id, payload).then((r) => r.data),
|
||||
undefined,
|
||||
({ zoneId }) => [
|
||||
["warehouse-zone-stacks", zoneId],
|
||||
["warehouse-zones", zoneId, "layout"],
|
||||
["warehouse-zones", zoneId, "slot-summary"],
|
||||
],
|
||||
),
|
||||
|
||||
deleteStack: endpoint<{ id: string; zoneId: string }, unknown>(
|
||||
"warehouse-zone-stacks",
|
||||
"delete",
|
||||
({ id }) => warehouseService.removeStack(id).then((r) => r.data),
|
||||
undefined,
|
||||
({ zoneId }) => [
|
||||
["warehouse-zone-stacks", zoneId],
|
||||
["warehouse-zones", zoneId, "layout"],
|
||||
["warehouse-zones", zoneId, "slot-summary"],
|
||||
],
|
||||
),
|
||||
|
||||
updateSlot: endpoint<
|
||||
{ slotId: string; zoneId: string; payload: { status?: string; isActive?: boolean } },
|
||||
WarehouseZoneSlot
|
||||
>(
|
||||
"warehouse-zone-stacks",
|
||||
"update-slot",
|
||||
({ slotId, payload }) => warehouseService.updateSlot(slotId, payload).then((r) => r.data),
|
||||
undefined,
|
||||
({ zoneId }) => [
|
||||
["warehouse-zones", zoneId, "layout"],
|
||||
["warehouse-zones", zoneId, "slot-summary"],
|
||||
],
|
||||
),
|
||||
|
||||
deleteZone: endpoint<{ id: string }, unknown>(
|
||||
"warehouses",
|
||||
"deleteZone",
|
||||
@@ -1490,7 +1577,7 @@ export const api = {
|
||||
"store",
|
||||
({ id, payload }) => warehouseService.store(id, payload).then((r) => r.data),
|
||||
undefined,
|
||||
() => INVENTORY_INVALIDATIONS,
|
||||
() => [...INVENTORY_INVALIDATIONS, ["warehouse-zones"]],
|
||||
),
|
||||
|
||||
reserve: endpoint<ReserveInventoryPayload, WarehouseInventoryItem>(
|
||||
@@ -1529,6 +1616,36 @@ export const api = {
|
||||
() => INVENTORY_INVALIDATIONS,
|
||||
),
|
||||
|
||||
findAvailableSlot: endpoint<FindSlotPayload, AvailableSlot | null>(
|
||||
"warehouse-inventory",
|
||||
"find-slot",
|
||||
(payload) => warehouseService.findAvailableSlot(payload).then((r) => r.data),
|
||||
(payload) => ["warehouse-inventory", "find-slot", payload],
|
||||
),
|
||||
|
||||
containerAccessibility: endpoint<{ id: string }, ContainerAccessibility>(
|
||||
"warehouse-inventory",
|
||||
"accessibility",
|
||||
({ id }) => warehouseService.containerAccessibility(id).then((r) => r.data),
|
||||
({ id }) => ["warehouse-inventory", id, "accessibility"],
|
||||
),
|
||||
|
||||
assignSlot: endpoint<{ id: string; slotId?: string }, WarehouseInventoryItem>(
|
||||
"warehouse-inventory",
|
||||
"assign-slot",
|
||||
({ id, slotId }) => warehouseService.assignSlot(id, slotId).then((r) => r.data),
|
||||
undefined,
|
||||
() => [...INVENTORY_INVALIDATIONS, ["warehouse-zones"]],
|
||||
),
|
||||
|
||||
releaseSlot: endpoint<{ id: string }, WarehouseInventoryItem>(
|
||||
"warehouse-inventory",
|
||||
"release-slot",
|
||||
({ id }) => warehouseService.releaseSlot(id).then((r) => r.data),
|
||||
undefined,
|
||||
() => [...INVENTORY_INVALIDATIONS, ["warehouse-zones"]],
|
||||
),
|
||||
|
||||
move: endpoint<
|
||||
{ id: string; payload: MoveInventoryPayload },
|
||||
WarehouseInventoryItem
|
||||
@@ -1538,7 +1655,7 @@ export const api = {
|
||||
({ id, payload }) =>
|
||||
warehouseService.move(id, payload).then((r) => r.data),
|
||||
undefined,
|
||||
() => INVENTORY_INVALIDATIONS,
|
||||
() => [...INVENTORY_INVALIDATIONS, ["warehouse-zones"]],
|
||||
),
|
||||
|
||||
markReadyForPickup: endpoint<string, WarehouseInventoryItem>(
|
||||
|
||||
@@ -5,6 +5,14 @@ import { api as apiClient } from '../auth/http';
|
||||
import { URL_CONSTANTS } from '@/constants/URLS';
|
||||
import type {
|
||||
ZoneOccupancy,
|
||||
ZoneLayout,
|
||||
ZoneSlotSummary,
|
||||
WarehouseZoneStack,
|
||||
WarehouseZoneSlot,
|
||||
SaveStackPayload,
|
||||
AvailableSlot,
|
||||
ContainerAccessibility,
|
||||
FindSlotPayload,
|
||||
TruckOnSite,
|
||||
WarehouseOpsStats,
|
||||
WarehouseThroughputPoint,
|
||||
@@ -289,6 +297,37 @@ export const warehouseService = {
|
||||
removeZone: (id: string) => apiClient.delete(URL_CONSTANTS.WAREHOUSE_ZONES.BY_ID(id)),
|
||||
zoneContents: (zoneId: string) =>
|
||||
apiClient.get<ZoneContentItem[]>(`${URL_CONSTANTS.WAREHOUSE_ZONES.BY_ID(zoneId)}/contents`),
|
||||
zoneLayout: (zoneId: string) =>
|
||||
apiClient.get<ZoneLayout>(URL_CONSTANTS.WAREHOUSE_ZONES.LAYOUT(zoneId)),
|
||||
zoneSlotSummary: (zoneId: string) =>
|
||||
apiClient.get<ZoneSlotSummary>(URL_CONSTANTS.WAREHOUSE_ZONES.SLOT_SUMMARY(zoneId)),
|
||||
|
||||
// ── Ground stacks and slots ────────────────────────────────────────────
|
||||
listStacks: (zoneId: string) =>
|
||||
apiClient.get<WarehouseZoneStack[]>(URL_CONSTANTS.WAREHOUSE_ZONE_STACKS.BY_ZONE(zoneId)),
|
||||
createStack: (zoneId: string, payload: SaveStackPayload) =>
|
||||
apiClient.post<WarehouseZoneStack>(URL_CONSTANTS.WAREHOUSE_ZONE_STACKS.BASE, {
|
||||
...payload,
|
||||
zoneId,
|
||||
}),
|
||||
updateStack: (id: string, payload: Partial<SaveStackPayload>) =>
|
||||
apiClient.patch<WarehouseZoneStack>(URL_CONSTANTS.WAREHOUSE_ZONE_STACKS.BY_ID(id), payload),
|
||||
removeStack: (id: string) =>
|
||||
apiClient.delete(URL_CONSTANTS.WAREHOUSE_ZONE_STACKS.BY_ID(id)),
|
||||
updateSlot: (slotId: string, payload: { status?: string; isActive?: boolean }) =>
|
||||
apiClient.patch<WarehouseZoneSlot>(URL_CONSTANTS.WAREHOUSE_ZONE_STACKS.SLOT(slotId), payload),
|
||||
|
||||
// ── Physical placement ─────────────────────────────────────────────────
|
||||
findAvailableSlot: (payload: FindSlotPayload) =>
|
||||
apiClient.post<AvailableSlot | null>(URL_CONSTANTS.WAREHOUSE_INVENTORY.FIND_SLOT, payload),
|
||||
assignSlot: (id: string, slotId?: string) =>
|
||||
apiClient.post<WarehouseInventoryItem>(URL_CONSTANTS.WAREHOUSE_INVENTORY.ASSIGN_SLOT(id), {
|
||||
slotId,
|
||||
}),
|
||||
releaseSlot: (id: string) =>
|
||||
apiClient.post<WarehouseInventoryItem>(URL_CONSTANTS.WAREHOUSE_INVENTORY.RELEASE_SLOT(id), {}),
|
||||
containerAccessibility: (id: string) =>
|
||||
apiClient.get<ContainerAccessibility>(URL_CONSTANTS.WAREHOUSE_INVENTORY.ACCESSIBILITY(id)),
|
||||
|
||||
// ── Inventory ──────────────────────────────────────────────────────────
|
||||
listInventory: (filter?: InventoryFilter) =>
|
||||
|
||||
Reference in New Issue
Block a user