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:
@@ -116,6 +116,14 @@ export default function Parts() {
|
||||
id: 'location',
|
||||
header: 'Location',
|
||||
cell: ({ row }) => {
|
||||
const host = row.original.host;
|
||||
if (host) {
|
||||
return (
|
||||
<span className="text-xs font-mono text-muted-foreground">
|
||||
{host.assetId} / {host.name}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
const path = row.original.bin?.fullPath;
|
||||
return path ? (
|
||||
<span className="text-xs font-mono text-muted-foreground">{path}</span>
|
||||
|
||||
Reference in New Issue
Block a user