stage 1
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"role" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"email" TEXT,
|
||||
"passwordHash" TEXT,
|
||||
"pinHash" TEXT,
|
||||
"active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"failedAttempts" INTEGER NOT NULL DEFAULT 0,
|
||||
"lockedUntil" DATETIME,
|
||||
"lastLoginAt" DATETIME,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Session" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"userId" TEXT NOT NULL,
|
||||
"tokenHash" TEXT NOT NULL,
|
||||
"deviceLabel" TEXT,
|
||||
"userAgent" TEXT,
|
||||
"ipAddress" TEXT,
|
||||
"expiresAt" DATETIME NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"lastSeenAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Machine" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"kind" TEXT NOT NULL,
|
||||
"location" TEXT,
|
||||
"notes" TEXT,
|
||||
"active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "OperationTemplate" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"machineId" TEXT,
|
||||
"defaultSettings" TEXT,
|
||||
"defaultInstructions" TEXT,
|
||||
"qcRequired" BOOLEAN NOT NULL DEFAULT false,
|
||||
"active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "OperationTemplate_machineId_fkey" FOREIGN KEY ("machineId") REFERENCES "Machine" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Project" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"code" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"customerCode" TEXT,
|
||||
"dueDate" DATETIME,
|
||||
"status" TEXT NOT NULL DEFAULT 'planning',
|
||||
"notes" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Assembly" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"qty" INTEGER NOT NULL DEFAULT 1,
|
||||
"notes" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Assembly_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Part" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"assemblyId" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"material" TEXT,
|
||||
"qty" INTEGER NOT NULL DEFAULT 1,
|
||||
"notes" TEXT,
|
||||
"stepFileId" TEXT,
|
||||
"drawingFileId" TEXT,
|
||||
"cutFileId" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Part_assemblyId_fkey" FOREIGN KEY ("assemblyId") REFERENCES "Assembly" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "Part_stepFileId_fkey" FOREIGN KEY ("stepFileId") REFERENCES "FileAsset" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
CONSTRAINT "Part_drawingFileId_fkey" FOREIGN KEY ("drawingFileId") REFERENCES "FileAsset" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
CONSTRAINT "Part_cutFileId_fkey" FOREIGN KEY ("cutFileId") REFERENCES "FileAsset" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Operation" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"partId" TEXT NOT NULL,
|
||||
"sequence" INTEGER NOT NULL,
|
||||
"templateId" TEXT,
|
||||
"name" TEXT NOT NULL,
|
||||
"machineId" TEXT,
|
||||
"settings" TEXT,
|
||||
"materialNotes" TEXT,
|
||||
"instructions" TEXT,
|
||||
"qcRequired" BOOLEAN NOT NULL DEFAULT false,
|
||||
"status" TEXT NOT NULL DEFAULT 'pending',
|
||||
"qrToken" TEXT NOT NULL,
|
||||
"claimedByUserId" TEXT,
|
||||
"claimedAt" DATETIME,
|
||||
"completedAt" DATETIME,
|
||||
"plannedMinutes" INTEGER,
|
||||
"plannedUnits" INTEGER,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Operation_partId_fkey" FOREIGN KEY ("partId") REFERENCES "Part" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "Operation_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "OperationTemplate" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
CONSTRAINT "Operation_machineId_fkey" FOREIGN KEY ("machineId") REFERENCES "Machine" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
CONSTRAINT "Operation_claimedByUserId_fkey" FOREIGN KEY ("claimedByUserId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "TimeLog" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"operationId" TEXT NOT NULL,
|
||||
"operatorId" TEXT NOT NULL,
|
||||
"startedAt" DATETIME NOT NULL,
|
||||
"endedAt" DATETIME,
|
||||
"unitsProcessed" INTEGER,
|
||||
"note" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "TimeLog_operationId_fkey" FOREIGN KEY ("operationId") REFERENCES "Operation" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "TimeLog_operatorId_fkey" FOREIGN KEY ("operatorId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "QCRecord" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"operationId" TEXT NOT NULL,
|
||||
"operatorId" TEXT NOT NULL,
|
||||
"kind" TEXT NOT NULL,
|
||||
"measurements" TEXT,
|
||||
"passed" BOOLEAN NOT NULL,
|
||||
"notes" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "QCRecord_operationId_fkey" FOREIGN KEY ("operationId") REFERENCES "Operation" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "QCRecord_operatorId_fkey" FOREIGN KEY ("operatorId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Fastener" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"partNumber" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"qty" INTEGER NOT NULL,
|
||||
"supplier" TEXT,
|
||||
"unitCost" REAL,
|
||||
"notes" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Fastener_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PurchaseOrder" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"vendor" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'draft',
|
||||
"sentAt" DATETIME,
|
||||
"receivedAt" DATETIME,
|
||||
"notes" TEXT,
|
||||
"pdfFileId" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "PurchaseOrder_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "PurchaseOrder_pdfFileId_fkey" FOREIGN KEY ("pdfFileId") REFERENCES "FileAsset" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "POLine" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"poId" TEXT NOT NULL,
|
||||
"fastenerId" TEXT NOT NULL,
|
||||
"qty" INTEGER NOT NULL,
|
||||
"unitCost" REAL,
|
||||
"receivedQty" INTEGER NOT NULL DEFAULT 0,
|
||||
CONSTRAINT "POLine_poId_fkey" FOREIGN KEY ("poId") REFERENCES "PurchaseOrder" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "POLine_fastenerId_fkey" FOREIGN KEY ("fastenerId") REFERENCES "Fastener" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "FileAsset" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"kind" TEXT NOT NULL,
|
||||
"originalName" TEXT NOT NULL,
|
||||
"path" TEXT NOT NULL,
|
||||
"sizeBytes" INTEGER NOT NULL,
|
||||
"mimeType" TEXT,
|
||||
"sha256" TEXT NOT NULL,
|
||||
"uploadedBy" TEXT,
|
||||
"uploadedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AuditLog" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"actorId" TEXT,
|
||||
"action" TEXT NOT NULL,
|
||||
"entity" TEXT NOT NULL,
|
||||
"entityId" TEXT,
|
||||
"before" TEXT,
|
||||
"after" TEXT,
|
||||
"ipAddress" TEXT,
|
||||
"at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "AuditLog_actorId_fkey" FOREIGN KEY ("actorId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "User_role_active_idx" ON "User"("role", "active");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Session_tokenHash_key" ON "Session"("tokenHash");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Session_userId_idx" ON "Session"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Session_expiresAt_idx" ON "Session"("expiresAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Machine_name_key" ON "Machine"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "OperationTemplate_name_key" ON "OperationTemplate"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Project_code_key" ON "Project"("code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Assembly_projectId_code_key" ON "Assembly"("projectId", "code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Part_assemblyId_code_key" ON "Part"("assemblyId", "code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Operation_qrToken_key" ON "Operation"("qrToken");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Operation_status_idx" ON "Operation"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Operation_claimedByUserId_idx" ON "Operation"("claimedByUserId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Operation_partId_sequence_key" ON "Operation"("partId", "sequence");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "TimeLog_operationId_idx" ON "TimeLog"("operationId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "TimeLog_operatorId_idx" ON "TimeLog"("operatorId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "QCRecord_operationId_idx" ON "QCRecord"("operationId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Fastener_projectId_idx" ON "Fastener"("projectId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "FileAsset_sha256_key" ON "FileAsset"("sha256");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AuditLog_entity_entityId_idx" ON "AuditLog"("entity", "entityId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AuditLog_at_idx" ON "AuditLog"("at");
|
||||
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "sqlite"
|
||||
@@ -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