1701 lines
47 KiB
Plaintext
1701 lines
47 KiB
Plaintext
// 新的 Prisma schema - Product-SKU 结构
|
||
// 针对电商企业的产品管理需求设计
|
||
|
||
generator client {
|
||
provider = "prisma-client-js"
|
||
}
|
||
|
||
datasource db {
|
||
provider = "postgresql"
|
||
}
|
||
|
||
// ============================================
|
||
// 产品分类
|
||
// ============================================
|
||
|
||
// 产品分类(动态管理)
|
||
model ProductCategory {
|
||
id String @id @default(cuid())
|
||
name String @unique // 分类名称:沙发、餐桌、书柜...
|
||
description String? @db.Text // 分类描述
|
||
icon String? // 图标(可选)
|
||
sortOrder Int @default(0) // 排序
|
||
isActive Boolean @default(true) // 是否启用
|
||
|
||
// 关联产品
|
||
products Product[]
|
||
planningTemplates PlanningTemplate[] @relation("PlanningTemplateCategory")
|
||
planningWorkspaces PlanningWorkspace[] @relation("PlanningWorkspaceCategory")
|
||
assetFamilies AssetFamily[] @relation("AssetFamilyCategory")
|
||
seriesTemplates SeriesTemplate[] @relation("SeriesTemplateCategory")
|
||
draftGroups DraftGroup[] @relation("DraftGroupCategory")
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([sortOrder])
|
||
@@index([isActive])
|
||
}
|
||
|
||
// ============================================
|
||
// 产品管理(Product-SKU结构)
|
||
// ============================================
|
||
|
||
// 产品(型号/SPU)
|
||
model Product {
|
||
id String @id @default(cuid())
|
||
modelNumber String @unique // 型号,如 "A001"
|
||
name String // 产品名称,如 "现代简约沙发"
|
||
description String? @db.Text // 产品描述
|
||
|
||
// 产品分类(关联 ProductCategory)
|
||
categoryId String?
|
||
category ProductCategory? @relation(fields: [categoryId], references: [id])
|
||
|
||
// 3D模型相关(型号级别的基础模型)
|
||
modelUrl String? // 基础3D模型URL (glTF)
|
||
thumbnailUrl String? // 缩略图
|
||
|
||
// 归属信息(型号级别 - 多人协作)
|
||
createdBy String // 创建者
|
||
creator User @relation("ProductCreator", fields: [createdBy], references: [id])
|
||
|
||
// 协作者关系(多人可编辑)
|
||
collaboratorUsers ProductCollaborator[]
|
||
|
||
// 部门归属
|
||
departmentId String?
|
||
department Department? @relation(fields: [departmentId], references: [id])
|
||
|
||
// 可见性
|
||
visibility String @default("TEAM") // PRIVATE, TEAM, DEPARTMENT, PUBLIC
|
||
|
||
// SKU列表
|
||
skus ProductSKU[]
|
||
|
||
// 装配步骤(型号级别,所有SKU共享)
|
||
assemblySteps AssemblyStep[]
|
||
|
||
// 装配版本历史
|
||
assemblyVersions AssemblyVersion[]
|
||
|
||
// 板件(型号级别)
|
||
panels Panel[]
|
||
|
||
// 分享链接
|
||
shareLinks ShareLink[]
|
||
|
||
// 包装方案
|
||
packagingPlans PackagingPlan[]
|
||
|
||
// 问题反馈
|
||
feedbacks Feedback[]
|
||
|
||
// 五金件
|
||
productHardwares ProductHardware[]
|
||
|
||
// 元数据
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
deletedAt DateTime? // 软删除
|
||
|
||
@@index([modelNumber])
|
||
@@index([categoryId])
|
||
@@index([createdBy])
|
||
@@index([departmentId])
|
||
}
|
||
|
||
// 产品SKU(库存单位)
|
||
model ProductSKU {
|
||
id String @id @default(cuid())
|
||
productId String // 关联的产品ID
|
||
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||
|
||
// SKU标识
|
||
skuCode String @unique // SKU编码,如 "A001-RED-3S"
|
||
|
||
// 规格属性(灵活的JSON结构)
|
||
// 示例: { "color": "红色", "size": "三人座", "material": "布艺" }
|
||
attributes Json
|
||
|
||
// 价格和库存
|
||
price Decimal @db.Decimal(10, 2) // 售价
|
||
costPrice Decimal? @db.Decimal(10, 2) // 成本价
|
||
stock Int @default(0) // 库存数量
|
||
|
||
// 图片(SKU特定的图片)
|
||
images String[] // 图片URL数组
|
||
|
||
// 3D模型(如果SKU有特定的3D模型变体,否则使用Product的基础模型)
|
||
modelUrl String? // SKU特定的3D模型URL
|
||
|
||
// 归属信息(SKU 级别 - 单人负责)
|
||
ownerId String // 负责人ID
|
||
owner User @relation("SKUOwner", fields: [ownerId], references: [id])
|
||
|
||
// 交接记录
|
||
transferHistory Json? // [{ fromUserId, toUserId, transferredAt, reason, operatorId }]
|
||
|
||
// 状态
|
||
isActive Boolean @default(true) // 是否上架
|
||
|
||
// SKU 级装配步骤(与产品级步骤隔离)
|
||
assemblySteps AssemblyStep[]
|
||
// SKU 级板件 / 孔位 / 五金结构数据
|
||
panels Panel[]
|
||
holes Hole[]
|
||
productHardwares ProductHardware[]
|
||
sourcePreSkuId String? @unique
|
||
sourcePreSku PreSku? @relation("PreSkuFormalizedSku", fields: [sourcePreSkuId], references: [id], onDelete: SetNull)
|
||
warehouseLocationId String?
|
||
warehouseLocation WarehouseLocation? @relation("ProductSKUWarehouseLocation", fields: [warehouseLocationId], references: [id], onDelete: SetNull)
|
||
packagedItems PackagedItem[] @relation("PackagedItemSku")
|
||
|
||
// 元数据
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
deletedAt DateTime? // 软删除
|
||
|
||
@@index([productId])
|
||
@@index([skuCode])
|
||
@@index([isActive])
|
||
@@index([ownerId])
|
||
@@index([warehouseLocationId])
|
||
}
|
||
|
||
// 产品协作者关系表
|
||
model ProductCollaborator {
|
||
productId String
|
||
userId String
|
||
role String @default("editor")
|
||
addedAt DateTime @default(now())
|
||
addedBy String?
|
||
|
||
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||
user User @relation("ProductCollaboratorUser", fields: [userId], references: [id], onDelete: Cascade)
|
||
addedByUser User? @relation("ProductCollaboratorAddedBy", fields: [addedBy], references: [id], onDelete: SetNull)
|
||
|
||
@@id([productId, userId])
|
||
@@index([productId])
|
||
@@index([userId])
|
||
@@index([addedBy])
|
||
}
|
||
|
||
model IntakeNonIntegratedProduct {
|
||
id String @id
|
||
spu String
|
||
spuBase String @unique
|
||
brand String
|
||
designer String
|
||
operator String
|
||
purchaser String
|
||
payload Json
|
||
visibility String @default("DEPARTMENT") // PRIVATE, TEAM, DEPARTMENT, PUBLIC
|
||
departmentId String?
|
||
teamId String?
|
||
createdBy String
|
||
updatedBy String
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
deletedAt DateTime?
|
||
|
||
@@index([spu])
|
||
@@index([createdBy])
|
||
@@index([departmentId])
|
||
@@index([teamId])
|
||
@@index([visibility])
|
||
@@index([deletedAt, createdAt])
|
||
}
|
||
|
||
// ============================================
|
||
// 装配相关
|
||
// ============================================
|
||
|
||
// 装配步骤(默认产品级,可选绑定到 SKU)
|
||
model AssemblyStep {
|
||
id String @id @default(cuid())
|
||
productId String
|
||
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||
skuId String?
|
||
sku ProductSKU? @relation(fields: [skuId], references: [id], onDelete: Cascade)
|
||
|
||
order Int // 步骤顺序
|
||
title String // 步骤标题
|
||
description String? @db.Text // 步骤说明
|
||
|
||
// ===== 步骤类型 =====
|
||
// panel=安装板件, hardware=安装五金件, custom=自定义
|
||
stepType String @default("panel")
|
||
|
||
// 关联的板件(stepType=panel 时)
|
||
panelId String?
|
||
|
||
// 关联的五金件 ID 列表(stepType=hardware 时)
|
||
hardwareIds String[]
|
||
|
||
// 动画数据(JSON)
|
||
partIds String[] // 参与的部件 ID(兼容旧数据)
|
||
transform Json // 目标变换 {position, rotation, scale}
|
||
duration Int @default(1500) // 动画时长(ms)
|
||
easing String @default("power2.inOut") // 缓动函数
|
||
|
||
// ===== 摄像机配置 =====
|
||
// { position: [x, y, z], target: [x, y, z] }
|
||
cameraPosition Json?
|
||
// smooth=平滑过渡, cut=直接切换, orbit=环绕
|
||
cameraTransition String @default("smooth")
|
||
|
||
// ===== 信息卡片配置 =====
|
||
showInfoCard Boolean @default(false)
|
||
// { title: string, items: [{ name, quantity, tips, thumbnailUrl }] }
|
||
infoCardData Json?
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([productId, order])
|
||
@@index([productId, skuId, order])
|
||
@@index([skuId])
|
||
@@index([stepType])
|
||
}
|
||
|
||
// 装配版本历史(最多保存7个版本)
|
||
model AssemblyVersion {
|
||
id String @id @default(cuid())
|
||
productId String
|
||
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||
|
||
version Int // 版本号,递增
|
||
snapshot Json // 完整的 steps 快照
|
||
description String? // 版本描述(如 "调整了门板安装顺序")
|
||
|
||
createdBy String? // 创建者 ID
|
||
createdAt DateTime @default(now())
|
||
|
||
@@unique([productId, version])
|
||
@@index([productId])
|
||
}
|
||
|
||
// 板件(属于Product)
|
||
model Panel {
|
||
id String @id @default(cuid())
|
||
productId String
|
||
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||
skuId String?
|
||
sku ProductSKU? @relation(fields: [skuId], references: [id], onDelete: Cascade)
|
||
|
||
code String // 编号
|
||
name String // 名称
|
||
gltfNodeName String // glTF 节点名
|
||
partType String? // 部件类型:顶板/底板/左侧板/右侧板/背板/门板/抽屉/层板
|
||
|
||
// 尺寸
|
||
length Float
|
||
width Float
|
||
thickness Float
|
||
|
||
// 安装相关
|
||
installOrder Int? @default(0) // 安装序号
|
||
explosionDirection String? // 爆炸方向:auto/up/down/left/right/front/back
|
||
isAnchor Boolean @default(false) // 是否为基准件(不移动)
|
||
assemblySemantic Json? // 当前生效的装配语义(SKU scoped)
|
||
|
||
// 材质关联
|
||
panelMaterial PanelMaterial?
|
||
|
||
// 安装在此板件上的五金件
|
||
productHardwares ProductHardware[]
|
||
|
||
// 检测到的孔位
|
||
holes Hole[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([productId])
|
||
@@index([skuId])
|
||
@@index([productId, skuId])
|
||
@@index([installOrder])
|
||
}
|
||
|
||
// ============================================
|
||
// 分享功能
|
||
// ============================================
|
||
|
||
// 分享链接
|
||
model ShareLink {
|
||
id String @id @default(cuid())
|
||
productId String
|
||
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||
|
||
token String @unique // 分享令牌
|
||
expiresAt DateTime? // 过期时间(可选)
|
||
viewCount Int @default(0) // 查看次数
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([productId])
|
||
@@index([token])
|
||
@@index([expiresAt]) // DB-006: 支持过期时间过滤查询
|
||
}
|
||
|
||
// 包装方案
|
||
model PackagingPlan {
|
||
id String @id @default(cuid())
|
||
productId String
|
||
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||
|
||
name String // 方案名称,如 "标准包装"、"精装版"
|
||
description String? @db.Text // 方案描述
|
||
|
||
boxCount Int @default(1) // 箱数
|
||
|
||
// 箱子详情(JSON数组)
|
||
// [{ boxNumber: 1, length: 100, width: 50, height: 30, weight: 5.5, panelIds: ["P001", "P002"], notes: "..." }]
|
||
boxes Json @default("[]")
|
||
|
||
// 汇总信息
|
||
totalWeight Float? // 总重量(kg)
|
||
totalVolume Float? // 总体积(m³)
|
||
|
||
notes String? @db.Text // 备注
|
||
isDefault Boolean @default(false) // 是否为默认方案
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([productId])
|
||
}
|
||
|
||
// ============================================
|
||
// 产品规划域
|
||
// ============================================
|
||
|
||
model PlanningTemplate {
|
||
id String @id @default(cuid())
|
||
code String @unique
|
||
name String
|
||
description String? @db.Text
|
||
|
||
categoryId String?
|
||
category ProductCategory? @relation("PlanningTemplateCategory", fields: [categoryId], references: [id])
|
||
|
||
createdBy String?
|
||
creator User? @relation("PlanningTemplateCreator", fields: [createdBy], references: [id])
|
||
|
||
namePattern String? @db.Text
|
||
slots Json
|
||
rules Json?
|
||
|
||
isPreset Boolean @default(false)
|
||
isActive Boolean @default(true)
|
||
|
||
workspaces PlanningWorkspace[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([categoryId])
|
||
@@index([createdBy])
|
||
@@index([isPreset])
|
||
@@index([isActive])
|
||
}
|
||
|
||
model PlanningWorkspace {
|
||
id String @id @default(cuid())
|
||
name String
|
||
description String? @db.Text
|
||
|
||
templateId String
|
||
template PlanningTemplate @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
||
|
||
categoryId String?
|
||
category ProductCategory? @relation("PlanningWorkspaceCategory", fields: [categoryId], references: [id])
|
||
|
||
createdBy String
|
||
creator User @relation("PlanningWorkspaceCreator", fields: [createdBy], references: [id])
|
||
|
||
departmentId String?
|
||
department Department? @relation("PlanningWorkspaceDepartment", fields: [departmentId], references: [id])
|
||
|
||
visibility String @default("TEAM")
|
||
slotSelections Json @default("{}")
|
||
metadata Json?
|
||
|
||
drafts PlanningDraft[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([templateId])
|
||
@@index([categoryId])
|
||
@@index([createdBy])
|
||
@@index([departmentId])
|
||
@@index([visibility])
|
||
}
|
||
|
||
model PlanningDraft {
|
||
id String @id @default(cuid())
|
||
workspaceId String
|
||
workspace PlanningWorkspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||
|
||
name String
|
||
selectedCandidateKey String
|
||
selectedAssets Json
|
||
previewNodes Json
|
||
summary Json
|
||
warnings Json?
|
||
notes String? @db.Text
|
||
sourceSnapshot Json?
|
||
|
||
createdBy String
|
||
creator User @relation("PlanningDraftCreator", fields: [createdBy], references: [id])
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([workspaceId])
|
||
@@index([createdBy])
|
||
}
|
||
|
||
model Substrate {
|
||
id String @id @default(cuid())
|
||
code String @unique
|
||
name String
|
||
description String? @db.Text
|
||
categoryId String
|
||
category SubstrateCategory @relation(fields: [categoryId], references: [id])
|
||
environmentGradeId String
|
||
environmentGrade SubstrateEnvironmentGrade @relation(fields: [environmentGradeId], references: [id])
|
||
thicknessSpecId String
|
||
thicknessSpec SubstrateThicknessSpec @relation(fields: [thicknessSpecId], references: [id])
|
||
surfaceOptionId String?
|
||
surfaceOption SubstrateSurfaceOption? @relation(fields: [surfaceOptionId], references: [id])
|
||
systemNameOptionId String?
|
||
systemNameOption SubstrateSystemNameOption? @relation(fields: [systemNameOptionId], references: [id])
|
||
suggestedPrice Decimal? @db.Decimal(10, 2)
|
||
weightKg Float?
|
||
metadata Json?
|
||
isActive Boolean @default(true)
|
||
|
||
createdBy String?
|
||
creator User? @relation("SubstrateCreator", fields: [createdBy], references: [id], onDelete: SetNull)
|
||
|
||
assetVariants AssetVariant[]
|
||
finishes SubstrateFinish[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([isActive])
|
||
@@index([createdBy])
|
||
@@index([categoryId])
|
||
@@index([environmentGradeId])
|
||
@@index([thicknessSpecId])
|
||
@@index([surfaceOptionId])
|
||
@@index([systemNameOptionId])
|
||
// 4字段规格由服务层收口:相同规格+相同花色拒绝重复,相同规格+不同花色并入已有基材
|
||
}
|
||
|
||
model SubstrateFinish {
|
||
id String @id @default(cuid())
|
||
substrateId String
|
||
substrate Substrate @relation(fields: [substrateId], references: [id], onDelete: Cascade)
|
||
finishId String
|
||
finish Material @relation(fields: [finishId], references: [id], onDelete: Cascade)
|
||
|
||
createdAt DateTime @default(now())
|
||
|
||
@@unique([substrateId, finishId])
|
||
@@index([substrateId])
|
||
@@index([finishId])
|
||
}
|
||
|
||
model SubstrateCategory {
|
||
id String @id @default(cuid())
|
||
name String @unique
|
||
sortOrder Int @default(0)
|
||
isActive Boolean @default(true)
|
||
|
||
substrates Substrate[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([sortOrder])
|
||
@@index([isActive])
|
||
}
|
||
|
||
model SubstrateEnvironmentGrade {
|
||
id String @id @default(cuid())
|
||
name String @unique
|
||
sortOrder Int @default(0)
|
||
isActive Boolean @default(true)
|
||
|
||
substrates Substrate[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([sortOrder])
|
||
@@index([isActive])
|
||
}
|
||
|
||
model SubstrateThicknessSpec {
|
||
id String @id @default(cuid())
|
||
name String @unique
|
||
thicknessMm Float
|
||
sheetLengthMm Float @default(2440)
|
||
sheetWidthMm Float @default(1220)
|
||
sortOrder Int @default(0)
|
||
isActive Boolean @default(true)
|
||
|
||
substrates Substrate[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([sortOrder])
|
||
@@index([isActive])
|
||
}
|
||
|
||
model SubstrateSurfaceOption {
|
||
id String @id @default(cuid())
|
||
name String @unique
|
||
sortOrder Int @default(0)
|
||
isActive Boolean @default(true)
|
||
|
||
substrates Substrate[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([sortOrder])
|
||
@@index([isActive])
|
||
}
|
||
|
||
model SubstrateSystemNameOption {
|
||
id String @id @default(cuid())
|
||
name String @unique
|
||
sortOrder Int @default(0)
|
||
isActive Boolean @default(true)
|
||
|
||
substrates Substrate[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([sortOrder])
|
||
@@index([isActive])
|
||
}
|
||
|
||
model AssetFamily {
|
||
id String @id @default(cuid())
|
||
type AssetType
|
||
code String @unique
|
||
name String
|
||
description String? @db.Text
|
||
productCategoryId String?
|
||
productCategory ProductCategory? @relation("AssetFamilyCategory", fields: [productCategoryId], references: [id])
|
||
applicableSeriesCodes String[] @default([])
|
||
connectionRoles String[] @default([])
|
||
metadata Json?
|
||
status AssetStatus @default(DRAFT)
|
||
|
||
createdBy String?
|
||
creator User? @relation("AssetFamilyCreator", fields: [createdBy], references: [id], onDelete: SetNull)
|
||
submittedAt DateTime?
|
||
submittedById String?
|
||
submitter User? @relation("AssetFamilySubmitter", fields: [submittedById], references: [id], onDelete: SetNull)
|
||
reviewedAt DateTime?
|
||
reviewedById String?
|
||
reviewer User? @relation("AssetFamilyReviewer", fields: [reviewedById], references: [id], onDelete: SetNull)
|
||
rejectedAt DateTime?
|
||
rejectedById String?
|
||
rejecter User? @relation("AssetFamilyRejecter", fields: [rejectedById], references: [id], onDelete: SetNull)
|
||
rejectionReason String? @db.Text
|
||
lastModifiedById String?
|
||
lastModifier User? @relation("AssetFamilyLastModifier", fields: [lastModifiedById], references: [id], onDelete: SetNull)
|
||
|
||
variants AssetVariant[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([type])
|
||
@@index([status])
|
||
@@index([productCategoryId])
|
||
@@index([createdBy])
|
||
@@index([submittedById])
|
||
@@index([reviewedById])
|
||
@@index([rejectedById])
|
||
@@index([lastModifiedById])
|
||
}
|
||
|
||
model AssetVariant {
|
||
id String @id @default(cuid())
|
||
assetFamilyId String
|
||
assetFamily AssetFamily @relation(fields: [assetFamilyId], references: [id], onDelete: Cascade)
|
||
code String @unique
|
||
name String
|
||
identityKey String
|
||
assetType AssetType
|
||
status AssetStatus @default(DRAFT)
|
||
sizeLabel String?
|
||
substrateId String?
|
||
substrate Substrate? @relation(fields: [substrateId], references: [id], onDelete: SetNull)
|
||
finishId String?
|
||
finish Material? @relation("AssetVariantFinish", fields: [finishId], references: [id], onDelete: SetNull)
|
||
dimensions Json
|
||
modelUrl String
|
||
thumbnailUrl String?
|
||
mountPoints Json
|
||
assemblyBasis Json?
|
||
compatibilityRules Json?
|
||
namingFields Json?
|
||
businessData Json?
|
||
weightKg Float?
|
||
costPrice Decimal? @db.Decimal(10, 2)
|
||
lossFactor Float?
|
||
estimatedCostPrice Decimal? @db.Decimal(10, 2)
|
||
|
||
inheritedFromVariantId String?
|
||
inheritedFromVariant AssetVariant? @relation("AssetVariantInheritance", fields: [inheritedFromVariantId], references: [id], onDelete: SetNull)
|
||
derivedVariants AssetVariant[] @relation("AssetVariantInheritance")
|
||
locationBindings AssetVariantLocationBinding[]
|
||
|
||
createdBy String?
|
||
creator User? @relation("AssetVariantCreator", fields: [createdBy], references: [id], onDelete: SetNull)
|
||
submittedAt DateTime?
|
||
submittedById String?
|
||
submitter User? @relation("AssetVariantSubmitter", fields: [submittedById], references: [id], onDelete: SetNull)
|
||
reviewedAt DateTime?
|
||
reviewedById String?
|
||
reviewer User? @relation("AssetVariantReviewer", fields: [reviewedById], references: [id], onDelete: SetNull)
|
||
rejectedAt DateTime?
|
||
rejectedById String?
|
||
rejecter User? @relation("AssetVariantRejecter", fields: [rejectedById], references: [id], onDelete: SetNull)
|
||
rejectionReason String? @db.Text
|
||
lastModifiedById String?
|
||
lastModifier User? @relation("AssetVariantLastModifier", fields: [lastModifiedById], references: [id], onDelete: SetNull)
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([assetFamilyId, identityKey])
|
||
@@index([assetFamilyId])
|
||
@@index([status])
|
||
@@index([substrateId])
|
||
@@index([finishId])
|
||
@@index([createdBy])
|
||
@@index([submittedById])
|
||
@@index([reviewedById])
|
||
@@index([rejectedById])
|
||
@@index([lastModifiedById])
|
||
@@index([inheritedFromVariantId])
|
||
}
|
||
|
||
model SeriesTemplate {
|
||
id String @id @default(cuid())
|
||
code String @unique
|
||
name String
|
||
seriesName String
|
||
description String? @db.Text
|
||
|
||
categoryId String?
|
||
category ProductCategory? @relation("SeriesTemplateCategory", fields: [categoryId], references: [id])
|
||
|
||
slotSchema Json
|
||
namingRule Json?
|
||
recommendationRule Json?
|
||
previewRule Json?
|
||
intakeRule Json?
|
||
|
||
isPreset Boolean @default(false)
|
||
isActive Boolean @default(true)
|
||
|
||
createdBy String?
|
||
creator User? @relation("SeriesTemplateCreator", fields: [createdBy], references: [id], onDelete: SetNull)
|
||
|
||
draftGroups DraftGroup[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([categoryId])
|
||
@@index([createdBy])
|
||
@@index([isPreset])
|
||
@@index([isActive])
|
||
}
|
||
|
||
model DraftGroup {
|
||
id String @id @default(cuid())
|
||
code String @unique
|
||
name String
|
||
description String? @db.Text
|
||
|
||
categoryId String?
|
||
category ProductCategory? @relation("DraftGroupCategory", fields: [categoryId], references: [id])
|
||
|
||
seriesTemplateId String?
|
||
seriesTemplate SeriesTemplate? @relation(fields: [seriesTemplateId], references: [id], onDelete: SetNull)
|
||
|
||
status DraftGroupStatus @default(DRAFT)
|
||
summary Json
|
||
notes String? @db.Text
|
||
|
||
createdBy String
|
||
creator User @relation("DraftGroupCreator", fields: [createdBy], references: [id])
|
||
|
||
preSkus PreSku[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([categoryId])
|
||
@@index([seriesTemplateId])
|
||
@@index([createdBy])
|
||
@@index([status])
|
||
}
|
||
|
||
model PreSku {
|
||
id String @id @default(cuid())
|
||
draftGroupId String
|
||
draftGroup DraftGroup @relation(fields: [draftGroupId], references: [id], onDelete: Cascade)
|
||
|
||
sequence Int
|
||
defaultName String
|
||
customName String?
|
||
status PreSkuStatus @default(DRAFT)
|
||
sourceCandidateKey String
|
||
familySelection Json
|
||
variantSelection Json
|
||
recommendedHardware Json?
|
||
previewNodes Json
|
||
displaySummary Json
|
||
bomPreview Json
|
||
weightPreview Float?
|
||
componentCodes String[]
|
||
explosionPreview Json?
|
||
installStepPreview Json
|
||
packagingSnapshot Json?
|
||
notes String? @db.Text
|
||
|
||
packagingPlan PreSkuPackagingPlan?
|
||
formalizedSku ProductSKU? @relation("PreSkuFormalizedSku")
|
||
locationBindings PreSkuLocationBinding[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([draftGroupId, sequence])
|
||
@@index([draftGroupId])
|
||
@@index([status])
|
||
}
|
||
|
||
model PreSkuPackagingPlan {
|
||
id String @id @default(cuid())
|
||
preSkuId String @unique
|
||
preSku PreSku @relation(fields: [preSkuId], references: [id], onDelete: Cascade)
|
||
|
||
sourceType PackagingSourceType @default(PRE_SKU)
|
||
sourceRefId String?
|
||
status String @default("DRAFT")
|
||
boxes Json @default("[]")
|
||
costSummary Json?
|
||
processDraft Json?
|
||
notes String? @db.Text
|
||
|
||
packagedItems PackagedItem[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([sourceType])
|
||
}
|
||
|
||
model WarehouseLocation {
|
||
id String @id @default(cuid())
|
||
code String @unique
|
||
name String
|
||
zone String?
|
||
aisle String?
|
||
shelf String?
|
||
bin String?
|
||
notes String? @db.Text
|
||
isActive Boolean @default(true)
|
||
|
||
skus ProductSKU[] @relation("ProductSKUWarehouseLocation")
|
||
packagedItems PackagedItem[] @relation("PackagedItemWarehouseLocation")
|
||
assetVariantBindings AssetVariantLocationBinding[]
|
||
brandMaterialBindings BrandMaterialBinding[]
|
||
preSkuBindings PreSkuLocationBinding[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([isActive])
|
||
}
|
||
|
||
model AssetVariantLocationBinding {
|
||
id String @id @default(cuid())
|
||
assetVariantId String
|
||
warehouseLocationId String
|
||
|
||
assetVariant AssetVariant @relation(fields: [assetVariantId], references: [id], onDelete: Cascade)
|
||
warehouseLocation WarehouseLocation @relation(fields: [warehouseLocationId], references: [id], onDelete: Cascade)
|
||
|
||
createdAt DateTime @default(now())
|
||
|
||
@@unique([assetVariantId, warehouseLocationId])
|
||
@@index([assetVariantId])
|
||
@@index([warehouseLocationId])
|
||
}
|
||
|
||
model BrandMaterialBinding {
|
||
id String @id @default(cuid())
|
||
brand String
|
||
materialType String
|
||
warehouseLocationId String?
|
||
warehouseLocation WarehouseLocation? @relation(fields: [warehouseLocationId], references: [id], onDelete: SetNull)
|
||
fileUrl String?
|
||
fileName String?
|
||
fileSize Int?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([brand, materialType])
|
||
@@index([brand])
|
||
@@index([warehouseLocationId])
|
||
}
|
||
|
||
model PreSkuLocationBinding {
|
||
id String @id @default(cuid())
|
||
preSkuId String
|
||
warehouseLocationId String
|
||
|
||
preSku PreSku @relation(fields: [preSkuId], references: [id], onDelete: Cascade)
|
||
warehouseLocation WarehouseLocation @relation(fields: [warehouseLocationId], references: [id], onDelete: Cascade)
|
||
|
||
createdAt DateTime @default(now())
|
||
|
||
@@unique([preSkuId, warehouseLocationId])
|
||
@@index([preSkuId])
|
||
@@index([warehouseLocationId])
|
||
}
|
||
|
||
model PackagedItem {
|
||
id String @id @default(cuid())
|
||
code String @unique
|
||
name String
|
||
preSkuPackagingPlanId String?
|
||
preSkuPackagingPlan PreSkuPackagingPlan? @relation(fields: [preSkuPackagingPlanId], references: [id], onDelete: SetNull)
|
||
skuId String?
|
||
sku ProductSKU? @relation("PackagedItemSku", fields: [skuId], references: [id], onDelete: SetNull)
|
||
warehouseLocationId String?
|
||
warehouseLocation WarehouseLocation? @relation("PackagedItemWarehouseLocation", fields: [warehouseLocationId], references: [id], onDelete: SetNull)
|
||
quantityPerPack Int @default(1)
|
||
stockQuantity Int @default(0)
|
||
length Float?
|
||
width Float?
|
||
height Float?
|
||
weight Float?
|
||
payload Json?
|
||
status String @default("DRAFT")
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([preSkuPackagingPlanId])
|
||
@@index([skuId])
|
||
@@index([warehouseLocationId])
|
||
}
|
||
|
||
// ============================================
|
||
// 用户管理
|
||
// ============================================
|
||
|
||
// 角色枚举
|
||
enum Role {
|
||
SUPER_ADMIN // 超级管理员
|
||
ADMIN // 管理员
|
||
SUPERVISOR // 主管(按部门/小组管理锚点区分范围)
|
||
DESIGNER // 设计师
|
||
USER // 普通用户
|
||
}
|
||
|
||
// 用户状态枚举
|
||
enum UserStatus {
|
||
ACTIVE // 在职
|
||
PENDING // 待激活
|
||
INACTIVE // 离职
|
||
SUSPENDED // 暂停
|
||
}
|
||
|
||
enum EmployeeSourceStatus {
|
||
EMPLOYED
|
||
DIMISSIONED
|
||
}
|
||
|
||
enum AssetType {
|
||
BOARD
|
||
COMPONENT
|
||
HARDWARE
|
||
PACKAGING
|
||
}
|
||
|
||
enum AssetStatus {
|
||
DRAFT
|
||
PENDING_REVIEW
|
||
REJECTED
|
||
AVAILABLE
|
||
DISABLED
|
||
}
|
||
|
||
enum DraftGroupStatus {
|
||
DRAFT
|
||
PACKAGING
|
||
READY_FOR_INTAKE
|
||
FORMALIZED
|
||
ARCHIVED
|
||
}
|
||
|
||
enum PreSkuStatus {
|
||
DRAFT
|
||
PREVIEW_CONFIRMED
|
||
FORMALIZED
|
||
ARCHIVED
|
||
}
|
||
|
||
enum PackagingSourceType {
|
||
PRODUCT
|
||
PRE_SKU
|
||
DRAFT_GROUP
|
||
}
|
||
|
||
// 用户
|
||
model User {
|
||
id String @id @default(cuid())
|
||
|
||
// 登录凭证
|
||
email String @unique // 邮箱(用于登录)
|
||
name String @unique // 姓名/花名(用于登录和显示)
|
||
password String
|
||
loginSource String @default("LOCAL")
|
||
dingtalkUserId String? @unique
|
||
dingtalkUnionId String? @unique
|
||
dingtalkBoundAt DateTime?
|
||
employeeDirectoryId String? @unique
|
||
employeeDirectory EmployeeDirectory? @relation(fields: [employeeDirectoryId], references: [id], onDelete: SetNull)
|
||
|
||
// 首次登录标识
|
||
isFirstLogin Boolean @default(true) // 超级管理员首次登录需强制修改信息
|
||
|
||
// 其他信息
|
||
avatar String?
|
||
phone String?
|
||
|
||
// 角色与权限
|
||
role Role @default(USER)
|
||
|
||
// 组织架构
|
||
departmentId String?
|
||
department Department? @relation("DepartmentMembers", fields: [departmentId], references: [id])
|
||
|
||
teamId String?
|
||
team Team? @relation("TeamMembers", fields: [teamId], references: [id])
|
||
|
||
// 管理关系
|
||
managedDepartment Department? @relation("DepartmentManager")
|
||
managedTeam Team? @relation("TeamLeader")
|
||
|
||
// 状态
|
||
status UserStatus @default(PENDING)
|
||
|
||
// 关联
|
||
createdProducts Product[] @relation("ProductCreator")
|
||
ownedSKUs ProductSKU[] @relation("SKUOwner")
|
||
createdPlanningTemplates PlanningTemplate[] @relation("PlanningTemplateCreator")
|
||
createdPlanningWorkspaces PlanningWorkspace[] @relation("PlanningWorkspaceCreator")
|
||
createdPlanningDrafts PlanningDraft[] @relation("PlanningDraftCreator")
|
||
createdSubstrates Substrate[] @relation("SubstrateCreator")
|
||
createdAssetFamilies AssetFamily[] @relation("AssetFamilyCreator")
|
||
submittedAssetFamilies AssetFamily[] @relation("AssetFamilySubmitter")
|
||
reviewedAssetFamilies AssetFamily[] @relation("AssetFamilyReviewer")
|
||
rejectedAssetFamilies AssetFamily[] @relation("AssetFamilyRejecter")
|
||
modifiedAssetFamilies AssetFamily[] @relation("AssetFamilyLastModifier")
|
||
createdAssetVariants AssetVariant[] @relation("AssetVariantCreator")
|
||
submittedAssetVariants AssetVariant[] @relation("AssetVariantSubmitter")
|
||
reviewedAssetVariants AssetVariant[] @relation("AssetVariantReviewer")
|
||
rejectedAssetVariants AssetVariant[] @relation("AssetVariantRejecter")
|
||
modifiedAssetVariants AssetVariant[] @relation("AssetVariantLastModifier")
|
||
createdSeriesTemplates SeriesTemplate[] @relation("SeriesTemplateCreator")
|
||
createdDraftGroups DraftGroup[] @relation("DraftGroupCreator")
|
||
collaboratedProducts ProductCollaborator[] @relation("ProductCollaboratorUser")
|
||
addedProductCollaborators ProductCollaborator[] @relation("ProductCollaboratorAddedBy")
|
||
feedbacks Feedback[] @relation("FeedbackSubmitter")
|
||
assignedFeedbacks Feedback[] @relation("FeedbackAssignee")
|
||
|
||
// 操作日志
|
||
activityLogs ActivityLog[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
lastLoginAt DateTime?
|
||
|
||
@@index([email])
|
||
@@index([name])
|
||
@@index([departmentId])
|
||
@@index([teamId])
|
||
@@index([status])
|
||
@@index([loginSource])
|
||
@@index([employeeDirectoryId])
|
||
}
|
||
|
||
// 部门
|
||
model Department {
|
||
id String @id @default(cuid())
|
||
|
||
name String @unique
|
||
fullPath String // 完整路径,如 "设计部-3D组-建模小组"
|
||
externalId String? @unique
|
||
syncedAt DateTime?
|
||
rawSnapshot Json?
|
||
|
||
// 层级关系
|
||
parentId String?
|
||
parent Department? @relation("DepartmentTree", fields: [parentId], references: [id])
|
||
children Department[] @relation("DepartmentTree")
|
||
|
||
// 部门负责人
|
||
managerId String? @unique
|
||
manager User? @relation("DepartmentManager", fields: [managerId], references: [id])
|
||
|
||
// 成员
|
||
members User[] @relation("DepartmentMembers")
|
||
|
||
// 小组
|
||
teams Team[]
|
||
|
||
// 产品
|
||
products Product[]
|
||
planningWorkspaces PlanningWorkspace[] @relation("PlanningWorkspaceDepartment")
|
||
|
||
// 排序
|
||
orderIndex Int @default(0)
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([parentId])
|
||
@@index([externalId])
|
||
}
|
||
|
||
// 小组
|
||
model Team {
|
||
id String @id @default(cuid())
|
||
|
||
name String
|
||
description String?
|
||
|
||
// 所属部门
|
||
departmentId String
|
||
department Department @relation(fields: [departmentId], references: [id], onDelete: Cascade)
|
||
|
||
// 组长
|
||
leaderId String? @unique
|
||
leader User? @relation("TeamLeader", fields: [leaderId], references: [id])
|
||
|
||
// 成员
|
||
members User[] @relation("TeamMembers")
|
||
|
||
// 排序
|
||
orderIndex Int @default(0)
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([departmentId, name])
|
||
@@index([departmentId])
|
||
}
|
||
|
||
model EmployeeDirectory {
|
||
id String @id @default(cuid())
|
||
|
||
dingtalkUserId String? @unique
|
||
dingtalkUnionId String? @unique
|
||
jobNumber String?
|
||
name String
|
||
mobile String?
|
||
email String?
|
||
title String?
|
||
sourceStatus EmployeeSourceStatus @default(EMPLOYED)
|
||
primaryDepartmentExternalId String?
|
||
departmentExternalIds Json?
|
||
syncedAt DateTime @default(now())
|
||
rawSnapshot Json?
|
||
|
||
user User?
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([name])
|
||
@@index([jobNumber])
|
||
@@index([mobile])
|
||
@@index([email])
|
||
@@index([sourceStatus])
|
||
@@index([primaryDepartmentExternalId])
|
||
}
|
||
|
||
// ============================================
|
||
// 问题反馈
|
||
// ============================================
|
||
|
||
// 反馈状态枚举
|
||
enum FeedbackStatus {
|
||
PENDING // 待处理
|
||
IN_PROGRESS // 处理中
|
||
RESOLVED // 已解决
|
||
CLOSED // 已关闭
|
||
}
|
||
|
||
// 反馈优先级枚举
|
||
enum FeedbackPriority {
|
||
LOW // 低
|
||
MEDIUM // 中
|
||
HIGH // 高
|
||
URGENT // 紧急
|
||
}
|
||
|
||
// 问题反馈
|
||
model Feedback {
|
||
id String @id @default(cuid())
|
||
|
||
// 关联产品
|
||
productId String
|
||
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||
|
||
skuId String? // 可选:具体的 SKU
|
||
|
||
// 反馈内容
|
||
title String
|
||
description String @db.Text
|
||
images String[] // 问题截图
|
||
|
||
// 问题分类
|
||
category String // 模型错误、尺寸错误、材质错误、装配步骤错误等
|
||
priority FeedbackPriority @default(MEDIUM)
|
||
|
||
// 提交者
|
||
submitterId String
|
||
submitter User @relation("FeedbackSubmitter", fields: [submitterId], references: [id])
|
||
|
||
// 处理者
|
||
assigneeId String?
|
||
assignee User? @relation("FeedbackAssignee", fields: [assigneeId], references: [id])
|
||
|
||
// 状态
|
||
status FeedbackStatus @default(PENDING)
|
||
|
||
// 处理记录
|
||
comments Json? // [{ userId, content, createdAt }]
|
||
|
||
// 解决方案
|
||
resolution String? @db.Text
|
||
resolvedAt DateTime?
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([productId])
|
||
@@index([submitterId])
|
||
@@index([assigneeId])
|
||
@@index([status])
|
||
}
|
||
|
||
// ============================================
|
||
// 操作日志
|
||
// ============================================
|
||
|
||
// 操作类型枚举
|
||
enum ActivityType {
|
||
USER_REGISTER
|
||
USER_LOGIN
|
||
USER_LOGIN_FAILED
|
||
USER_LOGOUT
|
||
USER_FIRST_LOGIN_UPDATE
|
||
USER_CREATE
|
||
USER_UPDATE
|
||
USER_DELETE
|
||
PRODUCT_CREATE
|
||
PRODUCT_UPDATE
|
||
PRODUCT_DELETE
|
||
SKU_CREATE
|
||
SKU_UPDATE
|
||
SKU_DELETE
|
||
SKU_TRANSFER
|
||
FILE_UPLOAD
|
||
FILE_DOWNLOAD
|
||
SHARE_LINK_CREATE
|
||
SHARE_LINK_ACCESS
|
||
FEEDBACK_CREATE
|
||
FEEDBACK_UPDATE
|
||
DATA_EXPORT
|
||
}
|
||
|
||
// 操作日志
|
||
model ActivityLog {
|
||
id String @id @default(cuid())
|
||
|
||
// 操作者
|
||
userId String
|
||
user User @relation(fields: [userId], references: [id])
|
||
|
||
// 操作类型
|
||
type ActivityType
|
||
|
||
// 操作对象
|
||
targetType String? // Product, ProductSKU, User, Feedback
|
||
targetId String?
|
||
|
||
// 操作详情
|
||
action String // 具体操作描述
|
||
changes Json? // 变更内容
|
||
metadata Json? // 额外信息(IP、设备等)
|
||
|
||
// 时间
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([userId, createdAt])
|
||
@@index([targetType, targetId])
|
||
@@index([type, createdAt])
|
||
@@index([createdAt]) // DB-005: 支持时间范围过滤查询
|
||
}
|
||
|
||
// ============================================
|
||
// 材质系统
|
||
// ============================================
|
||
|
||
// 材质分类
|
||
model MaterialCategory {
|
||
id String @id @default(cuid())
|
||
name String @unique // 分类名称:实木/金属/玻璃/亚克力/皮革/布艺/石材
|
||
icon String? // 分类图标
|
||
sortOrder Int @default(0) // 排序
|
||
isActive Boolean @default(true) // 是否启用
|
||
|
||
// 关联材质
|
||
materials Material[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([sortOrder])
|
||
@@index([isActive])
|
||
}
|
||
|
||
// 材质库(由专业人员管理)
|
||
model Material {
|
||
id String @id @default(cuid())
|
||
|
||
// 基本信息
|
||
name String // 材质名称,如 "橡木纹-自然色"
|
||
code String @unique // 花色编码
|
||
categoryId String // 分类 ID
|
||
category MaterialCategory @relation(fields: [categoryId], references: [id])
|
||
|
||
// 预览图(可选;没有真实缩略图时由服务层统一生成预览)
|
||
thumbnailUrl String?
|
||
|
||
// 纹理贴图
|
||
textureUrl String? // 纹理贴图 URL
|
||
normalMapUrl String? // 法线贴图(可选)
|
||
|
||
// PBR 参数
|
||
color String @default("#808080") // 基础颜色 #RRGGBB
|
||
metalness Float @default(0) // 金属度 0-1
|
||
roughness Float @default(0.5) // 粗糙度 0-1
|
||
opacity Float @default(1) // 透明度 0-1
|
||
|
||
// 纹理参数
|
||
textureRepeat Float @default(1) // 纹理重复次数
|
||
textureRotation Float @default(0) // 纹理旋转角度(弧度)
|
||
textureSizeMm Float @default(600) // 纹理实物宽度(毫米)
|
||
|
||
// 管理标识
|
||
isPreset Boolean @default(false) // 是否系统预设(预设不可删除)
|
||
isActive Boolean @default(true) // 是否启用
|
||
|
||
// 创建者
|
||
createdBy String? // 创建者 ID
|
||
|
||
// 关联板件材质
|
||
panelMaterials PanelMaterial[]
|
||
assetVariants AssetVariant[] @relation("AssetVariantFinish")
|
||
substrateFinishes SubstrateFinish[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([categoryId])
|
||
@@index([isPreset])
|
||
@@index([isActive])
|
||
}
|
||
|
||
// 板件-材质关联(普通用户操作)
|
||
model PanelMaterial {
|
||
id String @id @default(cuid())
|
||
|
||
// 关联板件
|
||
panelId String
|
||
panel Panel @relation(fields: [panelId], references: [id], onDelete: Cascade)
|
||
|
||
// 关联材质
|
||
materialId String
|
||
material Material @relation(fields: [materialId], references: [id])
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([panelId]) // 每个板件只能有一个材质
|
||
@@index([panelId])
|
||
@@index([materialId])
|
||
}
|
||
|
||
// ============================================
|
||
// 主题配置(管理员可视化配置)
|
||
// ============================================
|
||
|
||
// 主题配置
|
||
model ThemeConfig {
|
||
id String @id @default(cuid())
|
||
|
||
// 基本信息
|
||
name String // 主题名称,如 "春节主题"、"默认主题"
|
||
description String? @db.Text // 主题描述
|
||
|
||
// 激活状态
|
||
isActive Boolean @default(false) // 是否激活
|
||
isDefault Boolean @default(false) // 是否为默认主题(系统 fallback)
|
||
|
||
// 定时发布
|
||
startDate DateTime? // 定时生效开始时间
|
||
endDate DateTime? // 定时生效结束时间
|
||
|
||
// 首页背景配置
|
||
// { type: 'image' | 'gradient' | 'video', value: string, overlay?: string }
|
||
homeBackground Json?
|
||
|
||
// 登录页右侧面板配置
|
||
// { type: 'image' | 'video' | 'animation', value: string, overlay?: string, animationConfig?: object }
|
||
loginPanel Json?
|
||
|
||
// 创建者
|
||
createdBy String
|
||
|
||
// 元数据
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([isActive])
|
||
@@index([startDate, endDate])
|
||
}
|
||
|
||
// ============================================
|
||
// 五金件库(标准五金件复用系统)
|
||
// ============================================
|
||
|
||
// 五金件分类
|
||
model HardwareCategory {
|
||
id String @id @default(cuid())
|
||
name String @unique // 分类名称:合页、螺丝、导轨、拉手...
|
||
description String? @db.Text // 分类描述
|
||
icon String? // 图标(emoji 或 URL)
|
||
sortOrder Int @default(0) // 排序顺序
|
||
isActive Boolean @default(true) // 是否启用
|
||
|
||
// 关联五金件
|
||
hardwares Hardware[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([sortOrder])
|
||
@@index([isActive])
|
||
}
|
||
|
||
// 五金件(标准件库)
|
||
model Hardware {
|
||
id String @id @default(cuid())
|
||
|
||
// 基本信息
|
||
name String // 五金件名称,如 "直角合页 50mm"
|
||
code String @unique // 编码,如 "HW-HINGE-001"
|
||
|
||
// 分类
|
||
categoryId String
|
||
category HardwareCategory @relation(fields: [categoryId], references: [id])
|
||
|
||
// 规格参数(JSON)
|
||
// { size: "50x50mm", material: "不锈钢", weight: "25g", ... }
|
||
specifications Json?
|
||
|
||
// 3D 模型
|
||
modelUrl String // GLB 模型文件路径
|
||
thumbnailUrl String? // 缩略图
|
||
|
||
// 尺寸信息(用于碰撞检测和定位)
|
||
dimensions Json? // { width, height, depth, unit: "mm" }
|
||
|
||
// 几何特征(用于自动匹配,从 CAD 导入时自动计算)
|
||
// 尺寸已排序:width >= height >= depth
|
||
geoWidth Float? // 边界框宽度 (mm) - 最大尺寸
|
||
geoHeight Float? // 边界框高度 (mm) - 中间尺寸
|
||
geoDepth Float? // 边界框深度 (mm) - 最小尺寸
|
||
geoVolume Float? // 体积 (mm³)
|
||
|
||
// 安装方向配置(用于动画生成)
|
||
// 五金件模型必须以标准轴向上传,此字段记录"头"的默认朝向
|
||
defaultHeadDirection String? // "+X" | "-X" | "+Y" | "-Y" | "+Z" | "-Z"
|
||
|
||
// 安装提示(显示在信息卡片上)
|
||
installTip String? @db.Text // "从上往下拧入" | "需要预钻孔"
|
||
|
||
// ===== 组合件相关 =====
|
||
// 是否为组合件主件(如把手、铰链等需要配套螺丝的五金件)
|
||
isComposite Boolean @default(false)
|
||
|
||
// 配件定义(仅当 isComposite=true 时有效)
|
||
// 示例: [{ hardwareCode: "M4x25", quantity: 2, relativePositionTolerance: 50 }]
|
||
// hardwareCode: 配件的五金件编码
|
||
// quantity: 需要的数量
|
||
// relativePositionTolerance: 允许的位置偏差(mm),用于自动关联
|
||
accessories Json?
|
||
|
||
// 安装点配置(用于自动对齐)
|
||
// { points: [{ name: "center", position: [0, 0, 0], rotation: [0, 0, 0] }] }
|
||
mountPoints Json?
|
||
|
||
// 材质预设(引用材质 ID)
|
||
defaultMaterialId String?
|
||
|
||
// 管理标识
|
||
isPreset Boolean @default(false) // 是否系统预设
|
||
isActive Boolean @default(true) // 是否启用
|
||
|
||
// 创建者
|
||
createdBy String?
|
||
|
||
// 关联产品五金件
|
||
productHardwares ProductHardware[]
|
||
|
||
// 关联的孔位(匹配后)
|
||
holes Hole[]
|
||
|
||
// 映射关系(用于自动匹配记忆)
|
||
hardwareMappings HardwareMapping[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([categoryId])
|
||
@@index([code])
|
||
@@index([isPreset])
|
||
@@index([isActive])
|
||
@@index([geoWidth, geoHeight, geoDepth]) // 几何特征索引,加速匹配查询
|
||
}
|
||
|
||
// 产品-五金件关联(记录产品使用的五金件及其位置)
|
||
model ProductHardware {
|
||
id String @id @default(cuid())
|
||
|
||
// 关联产品
|
||
productId String
|
||
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||
skuId String?
|
||
sku ProductSKU? @relation(fields: [skuId], references: [id], onDelete: Cascade)
|
||
|
||
// 关联五金件(可为空,未匹配时为 null)
|
||
hardwareId String?
|
||
hardware Hardware? @relation(fields: [hardwareId], references: [id])
|
||
|
||
// 位置信息
|
||
// { position: [x, y, z], rotation: [rx, ry, rz], scale: [sx, sy, sz] }
|
||
transform Json
|
||
|
||
// 关联的板件(可选,指定安装在哪个板件上)
|
||
panelId String?
|
||
panel Panel? @relation(fields: [panelId], references: [id])
|
||
|
||
// 连接的第二个板件(五金件通常连接两个板材)
|
||
connectedPanelId String?
|
||
|
||
// ===== 孔位关联 =====
|
||
// 关联的孔位 ID(从 CAD 导入时自动关联)
|
||
associatedHoleId String?
|
||
// 与孔位的距离(mm),用于验证关联质量
|
||
holeDistance Float?
|
||
|
||
// ===== 组合件关联 =====
|
||
// 如果此五金件是某个组合件的配件,记录主件的 ID
|
||
parentHardwareId String?
|
||
// 是否为配件(属于某个组合件)
|
||
isAccessory Boolean @default(false)
|
||
|
||
// 安装序号(用于动画)
|
||
installOrder Int?
|
||
|
||
// ===== 匹配状态相关字段 =====
|
||
// 匹配状态:matched=已匹配(绿), pending=待确认(黄), unmatched=未匹配(红)
|
||
matchStatus String @default("unmatched")
|
||
// 匹配置信度 0-1
|
||
matchConfidence Float?
|
||
// 匹配方法:blockName=图块名, mapping=映射记忆, size=尺寸匹配, manual=手动选择
|
||
matchMethod String?
|
||
|
||
// CAD 导入时的原始信息(用于匹配和调试)
|
||
sourceBlockName String? // CAD 中的图块名
|
||
sourceSignature Json? // 几何特征签名 { width, height, depth }
|
||
|
||
// 备注
|
||
notes String? @db.Text
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([productId])
|
||
@@index([skuId])
|
||
@@index([hardwareId])
|
||
@@index([panelId])
|
||
@@index([matchStatus])
|
||
}
|
||
|
||
// 五金件映射关系(用于自动匹配记忆)
|
||
model HardwareMapping {
|
||
id String @id @default(cuid())
|
||
|
||
// 匹配条件(二选一)
|
||
blockName String? @unique // 图块名匹配(精确)
|
||
signatureHash String? @unique // 尺寸特征哈希(width_height_depth)
|
||
|
||
// 映射目标
|
||
hardwareId String
|
||
hardware Hardware @relation(fields: [hardwareId], references: [id])
|
||
|
||
// 元数据
|
||
createdBy String @default("manual") // "auto" | "manual"
|
||
usageCount Int @default(0) // 使用次数,越高优先级越高
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([hardwareId])
|
||
}
|
||
|
||
// 通用组件分类
|
||
model CommonComponentCategory {
|
||
id String @id @default(cuid())
|
||
name String @unique
|
||
description String? @db.Text
|
||
icon String?
|
||
sortOrder Int @default(0)
|
||
isActive Boolean @default(true)
|
||
|
||
components CommonComponent[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([sortOrder])
|
||
@@index([isActive])
|
||
}
|
||
|
||
// 通用组件(抽屉、门组件、柜脚组件等)
|
||
model CommonComponent {
|
||
id String @id @default(cuid())
|
||
|
||
name String
|
||
code String @unique
|
||
|
||
categoryId String
|
||
category CommonComponentCategory @relation(fields: [categoryId], references: [id])
|
||
|
||
description String? @db.Text
|
||
|
||
modelUrl String
|
||
thumbnailUrl String?
|
||
|
||
dimensions Json?
|
||
specifications Json?
|
||
mountPoints Json?
|
||
|
||
isPreset Boolean @default(false)
|
||
isActive Boolean @default(true)
|
||
|
||
createdBy String?
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([categoryId])
|
||
@@index([code])
|
||
@@index([isPreset])
|
||
@@index([isActive])
|
||
}
|
||
|
||
// ============================================
|
||
// 孔位检测系统
|
||
// ============================================
|
||
|
||
// 检测到的孔位
|
||
model Hole {
|
||
id String @id @default(cuid())
|
||
|
||
// 关联板件
|
||
panelId String
|
||
panel Panel @relation(fields: [panelId], references: [id], onDelete: Cascade)
|
||
skuId String?
|
||
sku ProductSKU? @relation(fields: [skuId], references: [id], onDelete: Cascade)
|
||
|
||
// 孔位信息(相对于板件坐标系)
|
||
positionX Float // X 坐标 (mm)
|
||
positionY Float // Y 坐标 (mm)
|
||
positionZ Float // Z 坐标 (mm)
|
||
diameter Float // 直径 (mm)
|
||
depth Float // 深度 (mm)
|
||
|
||
// 孔轴方向(法线)
|
||
normalX Float @default(0)
|
||
normalY Float @default(0)
|
||
normalZ Float @default(1)
|
||
|
||
// 孔类型(自动推断或手动设置)
|
||
// hinge, connector, shelf_pin, screw, dowel, cable, unknown
|
||
holeType String?
|
||
|
||
// 检测置信度 0-1
|
||
confidence Float @default(1)
|
||
|
||
// 是否手动修正
|
||
isManual Boolean @default(false)
|
||
|
||
// 关联五金件(匹配后)
|
||
hardwareId String?
|
||
hardware Hardware? @relation(fields: [hardwareId], references: [id])
|
||
|
||
// 关联的孔组 ID(同类孔分组)
|
||
groupId String?
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([panelId])
|
||
@@index([skuId])
|
||
@@index([panelId, skuId])
|
||
@@index([holeType])
|
||
@@index([hardwareId])
|
||
@@index([groupId])
|
||
}
|