feat: rework EOL, repairs, and hosts for real workflow
Four domain-model changes driven by exercising the deployed 2.0 build: - EOL moves from manufacturer to MPN via new PartModel catalog table, so alerts fire on the thing that actually ages. - Repairs re-home to Host (required hostId + problem text) with an optional RepairJobPart join for affected parts; drop Part.replacementPartId. - New /repairs/:id detail page with editable problem, part list, and a RepairComment thread (REPAIR_COMMENTED events fan out to each problem part's timeline). - Host.assetId (required, unique) surfaces prominently on the repair page so techs can confirm they're touching the right box. Single destructive migration reshapes existing dev data. All 7 packages typecheck clean; 30 API tests pass (9 new covering host membership, upsertByMpn idempotency + race, assetId 409, comment userId stamping). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -16,16 +16,17 @@ datasource db {
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
username String @unique
|
||||
email String @unique
|
||||
id String @id @default(uuid())
|
||||
username String @unique
|
||||
email String @unique
|
||||
passwordHash String
|
||||
role String @default("TECHNICIAN")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
role String @default("TECHNICIAN")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
partEvents PartEvent[]
|
||||
refreshTokens RefreshToken[]
|
||||
repairAssignments RepairJob[] @relation("RepairAssignee")
|
||||
repairAssignments RepairJob[] @relation("RepairAssignee")
|
||||
repairComments RepairComment[]
|
||||
savedViews SavedView[]
|
||||
csvImportJobs CsvImportJob[]
|
||||
}
|
||||
@@ -45,12 +46,28 @@ model RefreshToken {
|
||||
}
|
||||
|
||||
model Manufacturer {
|
||||
id String @id @default(uuid())
|
||||
name String @unique
|
||||
eolDate DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
parts Part[]
|
||||
id String @id @default(uuid())
|
||||
name String @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
parts Part[]
|
||||
partModels PartModel[]
|
||||
}
|
||||
|
||||
model PartModel {
|
||||
id String @id @default(uuid())
|
||||
manufacturerId String
|
||||
manufacturer Manufacturer @relation(fields: [manufacturerId], references: [id], onDelete: Restrict)
|
||||
mpn String
|
||||
eolDate DateTime?
|
||||
notes String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
parts Part[]
|
||||
|
||||
@@unique([manufacturerId, mpn])
|
||||
@@index([manufacturerId])
|
||||
@@index([eolDate])
|
||||
}
|
||||
|
||||
model Site {
|
||||
@@ -97,33 +114,33 @@ model Category {
|
||||
}
|
||||
|
||||
model Part {
|
||||
id String @id @default(uuid())
|
||||
serialNumber String @unique
|
||||
mpn String
|
||||
manufacturerId String
|
||||
manufacturer Manufacturer @relation(fields: [manufacturerId], references: [id], onDelete: Restrict)
|
||||
price Float?
|
||||
state String @default("SPARE")
|
||||
binId String?
|
||||
bin Bin? @relation(fields: [binId], references: [id], onDelete: SetNull)
|
||||
categoryId String?
|
||||
category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
|
||||
replacementPartId String?
|
||||
replacement Part? @relation("PartReplacement", fields: [replacementPartId], references: [id], onDelete: SetNull)
|
||||
replacedBy Part[] @relation("PartReplacement")
|
||||
notes String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
events PartEvent[]
|
||||
tags PartTag[]
|
||||
repairs RepairJob[]
|
||||
id String @id @default(uuid())
|
||||
serialNumber String @unique
|
||||
partModelId String
|
||||
partModel PartModel @relation(fields: [partModelId], references: [id], onDelete: Restrict)
|
||||
manufacturerId String
|
||||
manufacturer Manufacturer @relation(fields: [manufacturerId], references: [id], onDelete: Restrict)
|
||||
price Float?
|
||||
state String @default("SPARE")
|
||||
binId String?
|
||||
bin Bin? @relation(fields: [binId], references: [id], onDelete: SetNull)
|
||||
categoryId String?
|
||||
category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
|
||||
hostId String?
|
||||
host Host? @relation(fields: [hostId], references: [id], onDelete: SetNull)
|
||||
notes String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
events PartEvent[]
|
||||
tags PartTag[]
|
||||
problemInRepairs RepairJobPart[]
|
||||
|
||||
@@index([state])
|
||||
@@index([binId])
|
||||
@@index([manufacturerId])
|
||||
@@index([mpn])
|
||||
@@index([partModelId])
|
||||
@@index([categoryId])
|
||||
@@index([replacementPartId])
|
||||
@@index([hostId])
|
||||
}
|
||||
|
||||
model PartEvent {
|
||||
@@ -164,36 +181,61 @@ model PartTag {
|
||||
|
||||
model Host {
|
||||
id String @id @default(uuid())
|
||||
assetId String @unique
|
||||
name String @unique
|
||||
location String?
|
||||
notes String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
parts Part[]
|
||||
repairs RepairJob[]
|
||||
}
|
||||
|
||||
model RepairJob {
|
||||
id String @id @default(uuid())
|
||||
partId String
|
||||
part Part @relation(fields: [partId], references: [id], onDelete: Cascade)
|
||||
hostId String?
|
||||
host Host? @relation(fields: [hostId], references: [id], onDelete: SetNull)
|
||||
assigneeId String?
|
||||
assignee User? @relation("RepairAssignee", fields: [assigneeId], references: [id], onDelete: SetNull)
|
||||
status String @default("PENDING")
|
||||
openedAt DateTime @default(now())
|
||||
closedAt DateTime?
|
||||
notes String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
id String @id @default(uuid())
|
||||
hostId String
|
||||
host Host @relation(fields: [hostId], references: [id], onDelete: Restrict)
|
||||
assigneeId String?
|
||||
assignee User? @relation("RepairAssignee", fields: [assigneeId], references: [id], onDelete: SetNull)
|
||||
status String @default("PENDING")
|
||||
problem String
|
||||
openedAt DateTime @default(now())
|
||||
closedAt DateTime?
|
||||
notes String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
problemParts RepairJobPart[]
|
||||
comments RepairComment[]
|
||||
|
||||
@@index([partId])
|
||||
@@index([status])
|
||||
@@index([hostId])
|
||||
@@index([assigneeId])
|
||||
@@index([status, openedAt(sort: Desc)])
|
||||
}
|
||||
|
||||
model RepairJobPart {
|
||||
repairJobId String
|
||||
partId String
|
||||
repairJob RepairJob @relation(fields: [repairJobId], references: [id], onDelete: Cascade)
|
||||
part Part @relation(fields: [partId], references: [id], onDelete: Restrict)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@id([repairJobId, partId])
|
||||
@@index([partId])
|
||||
}
|
||||
|
||||
model RepairComment {
|
||||
id String @id @default(uuid())
|
||||
repairJobId String
|
||||
repairJob RepairJob @relation(fields: [repairJobId], references: [id], onDelete: Cascade)
|
||||
userId String?
|
||||
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
||||
content String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([repairJobId, createdAt])
|
||||
}
|
||||
|
||||
model WebhookSubscription {
|
||||
id String @id @default(uuid())
|
||||
url String
|
||||
|
||||
Reference in New Issue
Block a user