feat(parts): couple state and location (host vs bin)
DEPLOYED parts live on a host; every other state lives in a bin (or unassigned). Previously binId and hostId were independent nullable fields with no validation, so the Edit Part dialog could leave a DEPLOYED part with only a bin and no host — which silently dropped it from the repair problem-part picker. - Service: resolveLocation() helper enforces the invariant on create and update. On a state transition, update auto-clears the stale relation and emits LOCATION_CHANGED for the cleared side. - Zod: CreatePartRequest.superRefine rejects mismatched state/location up front; UpdatePartRequest rejects both-fields-set. - Web: PartFormDialog swaps a single Location field between Host combobox (DEPLOYED) and Bin combobox (others); switching State clears the opposite field. Parts list + detail render host first, then bin path, then Unassigned. - Tests: 9 new cases covering the invariant including the no-op guard so an unrelated PATCH on a DEPLOYED part doesn't touch hostId/binId. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -166,7 +166,11 @@ export default function PartDetail() {
|
||||
<DetailRow
|
||||
label="Location"
|
||||
value={
|
||||
part.bin?.fullPath ? (
|
||||
part.host ? (
|
||||
<span className="font-mono text-xs">
|
||||
{part.host.assetId} / {part.host.name}
|
||||
</span>
|
||||
) : part.bin?.fullPath ? (
|
||||
<span className="font-mono text-xs">{part.bin.fullPath}</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground italic">Unassigned</span>
|
||||
|
||||
Reference in New Issue
Block a user