feat(warehouses): track physical container stack and slot positions

Extends the warehouse hierarchy below zone with ground stacks and vertical
slots, so a container's exact position is recorded rather than only its zone.

- freight.warehouse_zone_stacks / warehouse_zone_slots, plus nullable
  stack_id / slot_id on warehouse_inventory (existing rows stay valid)
- slot occupancy is derived from inventory status, guarded by a partial
  unique index, so no exit path has to remember to free a slot
- placement service: hierarchy validation, bottom-up stacking rules,
  accessibility/blocking-container reads, capacity vs slot summaries
- stack CRUD with auto-generated slots; reuses warehouse-zone permissions
- slot support folded into the existing move()/store() paths
- fix: validateLocation now rejects a mismatched warehouse/yard/zone triple
- seed:warehouse-layout builds the layout from a JSON config
This commit is contained in:
Hagernesh
2026-08-28 16:07:50 +00:00
parent 9de4e9e238
commit a6ea1a48ac
28 changed files with 2313 additions and 13 deletions

View File

@@ -24,6 +24,7 @@ import {
WarehouseStatusBadge,
WarehouseTypeBadge,
ZoneContentsModal,
type ZoneRef,
ZoneOccupancyHeatmap,
formatCapacity,
humanizeEnum,
@@ -61,7 +62,7 @@ export default function WarehouseDetailPage() {
const [zoneModalOpen, setZoneModalOpen] = useState(false);
const [editingZone, setEditingZone] = useState<WarehouseZone | null>(null);
const [selectedYardId, setSelectedYardId] = useState<string | null>(null);
const [contentsZone, setContentsZone] = useState<WarehouseZone | null>(null);
const [contentsZone, setContentsZone] = useState<ZoneRef | null>(null);
const zonesQuery = useQuery(
api.warehouses.listZones.queryOptions({
@@ -373,7 +374,10 @@ export default function WarehouseDetailPage() {
</Button>
</Group>
<ZoneOccupancyHeatmap yardId={selectedYardId ?? undefined} />
<ZoneOccupancyHeatmap
yardId={selectedYardId ?? undefined}
onZoneClick={(zone) => setContentsZone(zone)}
/>
{!selectedYardId ? (
<Text c="dimmed" ta="center" py="lg">