feat: laundry-list polish pass
CI / Lint · Typecheck · Test · Build (push) Successful in 44s
CI / Playwright (smoke) (push) Has been skipped
CI / Build & push images (push) Successful in 59s

Seven bundled improvements:
- PartModel combobox on Add Part + Log Repair (known MPN auto-fills;
  unknown reveals manufacturer picker for catalog upsert).
- Host lifecycle: state (DEPLOYED/DEGRADED/TESTING) and stack
  (PRODUCTION/VETTING) fields, driven by external clients via the API.
- Locations page redesigned as a 2-pane tree + bin grid with breadcrumb.
- PENDING_REPAIR custody state: tech takes a SPARE into custody for a
  future swap; resolves to DEPLOYED via Repair or back to SPARE via a
  bin-required drop-off.
- Move Category from Part to PartModel; seed common categories
  (GPU/RAM/SSD/HDD/NIC/CPU/PSU/MOBO). Parts table gets a Category
  column and filter sourced from the model.
- Fix Deployed Value 100x bug on the Dashboard (price is stored as
  dollars, not cents).
- PartModels table shows "No" instead of "--" when destroyOnFail=false.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-17 13:36:11 -04:00
parent 3d77f2846d
commit 60255f20bb
39 changed files with 1731 additions and 630 deletions
+17 -4
View File
@@ -8,20 +8,33 @@ export const LogRepairRequest = z
hostId: z.string().uuid().optional(),
assetId: z.string().trim().min(1).max(128).optional(),
brokenSerial: z.string().trim().min(1).max(128),
brokenMpn: z.string().trim().min(1).max(128),
brokenManufacturerId: z.string().uuid(),
// When the broken serial isn't in Vector yet we ingest it. Provide either a known PartModel
// (brokenPartModelId) or the manufacturer + mpn pair to auto-create it.
brokenPartModelId: z.string().uuid().optional(),
brokenMpn: z.string().trim().min(1).max(128).optional(),
brokenManufacturerId: z.string().uuid().optional(),
replacementSerial: z.string().trim().min(1).max(128),
fmId: z.string().uuid().optional(),
})
.superRefine((v, ctx) => {
const has = [v.hostId, v.assetId].filter((x) => x !== undefined && x !== '').length;
if (has !== 1) {
const hostHas = [v.hostId, v.assetId].filter((x) => x !== undefined && x !== '').length;
if (hostHas !== 1) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Provide exactly one of hostId or assetId',
path: ['hostId'],
});
}
const hasModel =
v.brokenPartModelId !== undefined ||
(v.brokenMpn !== undefined && v.brokenManufacturerId !== undefined);
if (!hasModel) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Provide brokenPartModelId or both brokenMpn and brokenManufacturerId',
path: ['brokenPartModelId'],
});
}
});
export type LogRepairRequest = z.infer<typeof LogRepairRequest>;