stage 1
This commit is contained in:
@@ -0,0 +1,308 @@
|
||||
// MRP QR Code system — database schema
|
||||
// Provider: SQLite (enums stored as string constants; JSON stored as strings)
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Users & sessions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// role: "admin" | "operator"
|
||||
/// admins authenticate with email + password
|
||||
/// operators authenticate with name + 4-digit PIN
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
role String
|
||||
name String
|
||||
email String? @unique
|
||||
passwordHash String?
|
||||
pinHash String?
|
||||
active Boolean @default(true)
|
||||
failedAttempts Int @default(0)
|
||||
lockedUntil DateTime?
|
||||
lastLoginAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
sessions Session[]
|
||||
timeLogs TimeLog[]
|
||||
qcRecords QCRecord[]
|
||||
auditLogs AuditLog[] @relation("ActorLogs")
|
||||
claimedOps Operation[] @relation("ClaimedBy")
|
||||
|
||||
@@index([role, active])
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
tokenHash String @unique
|
||||
deviceLabel String?
|
||||
userAgent String?
|
||||
ipAddress String?
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
lastSeenAt DateTime @default(now())
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId])
|
||||
@@index([expiresAt])
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shop floor: machines + operation templates
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
model Machine {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
kind String // free-form: NCT_PUNCH | PRESS_BRAKE | RIVET | WELD | OTHER
|
||||
location String?
|
||||
notes String?
|
||||
active Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
templates OperationTemplate[]
|
||||
operations Operation[]
|
||||
}
|
||||
|
||||
/// Reusable recipe an admin can pick from when authoring an operation on a part.
|
||||
model OperationTemplate {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
machineId String?
|
||||
defaultSettings String? // JSON-encoded key/value
|
||||
defaultInstructions String?
|
||||
qcRequired Boolean @default(false)
|
||||
active Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
machine Machine? @relation(fields: [machineId], references: [id], onDelete: SetNull)
|
||||
operations Operation[]
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Project hierarchy: Project → Assembly → Part → Operation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
model Project {
|
||||
id String @id @default(cuid())
|
||||
code String @unique
|
||||
name String
|
||||
customerCode String?
|
||||
dueDate DateTime?
|
||||
status String @default("planning") // planning | in_progress | completed | cancelled
|
||||
notes String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
assemblies Assembly[]
|
||||
fasteners Fastener[]
|
||||
purchaseOrders PurchaseOrder[]
|
||||
}
|
||||
|
||||
model Assembly {
|
||||
id String @id @default(cuid())
|
||||
projectId String
|
||||
code String
|
||||
name String
|
||||
qty Int @default(1)
|
||||
notes String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
parts Part[]
|
||||
|
||||
@@unique([projectId, code])
|
||||
}
|
||||
|
||||
model Part {
|
||||
id String @id @default(cuid())
|
||||
assemblyId String
|
||||
code String
|
||||
name String
|
||||
material String?
|
||||
qty Int @default(1)
|
||||
notes String?
|
||||
stepFileId String?
|
||||
drawingFileId String?
|
||||
cutFileId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
assembly Assembly @relation(fields: [assemblyId], references: [id], onDelete: Cascade)
|
||||
stepFile FileAsset? @relation("PartStep", fields: [stepFileId], references: [id], onDelete: SetNull)
|
||||
drawingFile FileAsset? @relation("PartDrawing", fields: [drawingFileId], references: [id], onDelete: SetNull)
|
||||
cutFile FileAsset? @relation("PartCut", fields: [cutFileId], references: [id], onDelete: SetNull)
|
||||
operations Operation[]
|
||||
|
||||
@@unique([assemblyId, code])
|
||||
}
|
||||
|
||||
/// A single shop-floor step on a specific part. Each has its own QR traveler card.
|
||||
/// Only one operator may hold the claim at a time (claimedByUserId set).
|
||||
model Operation {
|
||||
id String @id @default(cuid())
|
||||
partId String
|
||||
sequence Int
|
||||
templateId String?
|
||||
name String
|
||||
machineId String?
|
||||
settings String? // JSON
|
||||
materialNotes String?
|
||||
instructions String?
|
||||
qcRequired Boolean @default(false)
|
||||
status String @default("pending") // pending | in_progress | completed
|
||||
qrToken String @unique
|
||||
claimedByUserId String?
|
||||
claimedAt DateTime?
|
||||
completedAt DateTime?
|
||||
plannedMinutes Int?
|
||||
plannedUnits Int?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
part Part @relation(fields: [partId], references: [id], onDelete: Cascade)
|
||||
template OperationTemplate? @relation(fields: [templateId], references: [id], onDelete: SetNull)
|
||||
machine Machine? @relation(fields: [machineId], references: [id], onDelete: SetNull)
|
||||
claimedBy User? @relation("ClaimedBy", fields: [claimedByUserId], references: [id], onDelete: SetNull)
|
||||
timeLogs TimeLog[]
|
||||
qcRecords QCRecord[]
|
||||
|
||||
@@unique([partId, sequence])
|
||||
@@index([status])
|
||||
@@index([claimedByUserId])
|
||||
}
|
||||
|
||||
model TimeLog {
|
||||
id String @id @default(cuid())
|
||||
operationId String
|
||||
operatorId String
|
||||
startedAt DateTime
|
||||
endedAt DateTime?
|
||||
unitsProcessed Int?
|
||||
note String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
operation Operation @relation(fields: [operationId], references: [id], onDelete: Cascade)
|
||||
operator User @relation(fields: [operatorId], references: [id])
|
||||
|
||||
@@index([operationId])
|
||||
@@index([operatorId])
|
||||
}
|
||||
|
||||
model QCRecord {
|
||||
id String @id @default(cuid())
|
||||
operationId String
|
||||
operatorId String
|
||||
kind String // "inline" | "dedicated"
|
||||
measurements String? // JSON
|
||||
passed Boolean
|
||||
notes String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
operation Operation @relation(fields: [operationId], references: [id], onDelete: Cascade)
|
||||
operator User @relation(fields: [operatorId], references: [id])
|
||||
|
||||
@@index([operationId])
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Purchasing: fasteners + POs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
model Fastener {
|
||||
id String @id @default(cuid())
|
||||
projectId String
|
||||
partNumber String
|
||||
description String
|
||||
qty Int
|
||||
supplier String?
|
||||
unitCost Float?
|
||||
notes String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
poLines POLine[]
|
||||
|
||||
@@index([projectId])
|
||||
}
|
||||
|
||||
model PurchaseOrder {
|
||||
id String @id @default(cuid())
|
||||
projectId String
|
||||
vendor String
|
||||
status String @default("draft") // draft | sent | partial | received | cancelled
|
||||
sentAt DateTime?
|
||||
receivedAt DateTime?
|
||||
notes String?
|
||||
pdfFileId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
pdfFile FileAsset? @relation("PoPdf", fields: [pdfFileId], references: [id], onDelete: SetNull)
|
||||
lines POLine[]
|
||||
}
|
||||
|
||||
model POLine {
|
||||
id String @id @default(cuid())
|
||||
poId String
|
||||
fastenerId String
|
||||
qty Int
|
||||
unitCost Float?
|
||||
receivedQty Int @default(0)
|
||||
|
||||
po PurchaseOrder @relation(fields: [poId], references: [id], onDelete: Cascade)
|
||||
fastener Fastener @relation(fields: [fastenerId], references: [id])
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Files & audit
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
model FileAsset {
|
||||
id String @id @default(cuid())
|
||||
kind String // step | pdf | dxf | svg | png | jpg | other
|
||||
originalName String
|
||||
path String // relative to UPLOAD_DIR
|
||||
sizeBytes Int
|
||||
mimeType String?
|
||||
sha256 String @unique
|
||||
uploadedBy String?
|
||||
uploadedAt DateTime @default(now())
|
||||
|
||||
partStep Part[] @relation("PartStep")
|
||||
partDrawing Part[] @relation("PartDrawing")
|
||||
partCut Part[] @relation("PartCut")
|
||||
poPdfs PurchaseOrder[] @relation("PoPdf")
|
||||
}
|
||||
|
||||
model AuditLog {
|
||||
id String @id @default(cuid())
|
||||
actorId String?
|
||||
action String // e.g. "create", "update", "delete", "login", "claim_op", "close_op"
|
||||
entity String
|
||||
entityId String?
|
||||
before String? // JSON
|
||||
after String? // JSON
|
||||
ipAddress String?
|
||||
at DateTime @default(now())
|
||||
|
||||
actor User? @relation("ActorLogs", fields: [actorId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([entity, entityId])
|
||||
@@index([at])
|
||||
}
|
||||
Reference in New Issue
Block a user