3706 lines
129 KiB
Plaintext
3706 lines
129 KiB
Plaintext
// 新的 Prisma schema - Product-SKU 结构
|
||
// 针对电商企业的产品管理需求设计
|
||
|
||
generator client {
|
||
provider = "prisma-client-js"
|
||
}
|
||
|
||
datasource db {
|
||
provider = "postgresql"
|
||
}
|
||
|
||
model SampleProgressItem {
|
||
id String @id @default(cuid())
|
||
orderDate DateTime @db.Date
|
||
imageUrl String?
|
||
openingCode String @unique
|
||
productDevelopmentFlowUrl String? @db.Text
|
||
productSize String
|
||
color String
|
||
material String
|
||
sampleQuantity Int?
|
||
brand String
|
||
sampleDueDate DateTime @db.Date
|
||
actualSampleCompletedAt DateTime?
|
||
oaReachedAt DateTime?
|
||
supplier String
|
||
supplierConfirmedAt DateTime?
|
||
sampleNotifiedAt DateTime?
|
||
courierTrackingNumber String?
|
||
finalSampleFactory String?
|
||
status String
|
||
flowStatus String @default("")
|
||
designer String
|
||
purchaseOwner String?
|
||
merchandiserOwner String?
|
||
progressDescription String?
|
||
autoGenerated Boolean @default(false)
|
||
requestedCost Decimal? @db.Decimal(12, 2)
|
||
actualCost Decimal? @db.Decimal(12, 2)
|
||
followUpOwner String?
|
||
createdBy String?
|
||
updatedBy String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
changeLogs SampleProgressChangeLog[]
|
||
|
||
@@index([status])
|
||
@@index([brand])
|
||
@@index([sampleDueDate])
|
||
@@index([actualSampleCompletedAt])
|
||
@@index([oaReachedAt])
|
||
@@index([designer])
|
||
}
|
||
|
||
model SampleProgressChangeLog {
|
||
id String @id @default(cuid())
|
||
itemId String
|
||
item SampleProgressItem @relation(fields: [itemId], references: [id], onDelete: Cascade)
|
||
changedFields Json
|
||
beforeSnapshot Json
|
||
afterSnapshot Json
|
||
changeReason String?
|
||
changedById String?
|
||
changedByName String
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([itemId, createdAt])
|
||
}
|
||
|
||
// ============================================
|
||
// 产品分类
|
||
// ============================================
|
||
|
||
// 产品分类(动态管理)
|
||
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")
|
||
assetSeries AssetSeries[] @relation("AssetSeriesCategory")
|
||
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"
|
||
name String? // SKU名称
|
||
|
||
// 规格属性(灵活的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) // 是否上架
|
||
originalSkuCode String?
|
||
deletedCodeAt DateTime?
|
||
deletedByProductId String?
|
||
productDeletedAt DateTime?
|
||
|
||
// SKU 级装配步骤(与产品级步骤隔离)
|
||
assemblySteps AssemblyStep[]
|
||
// SKU 级板件 / 孔位 / 五金结构数据
|
||
panels Panel[]
|
||
holes Hole[]
|
||
productHardwares ProductHardware[]
|
||
sourcePreSkuId String? @unique
|
||
sourcePreSku PreSku? @relation("PreSkuFormalizedSku", fields: [sourcePreSkuId], references: [id], onDelete: SetNull)
|
||
sourceAssetVariantId String?
|
||
sourceAssetVariant AssetVariant? @relation("ProductSKUSourceAssetVariant", fields: [sourceAssetVariantId], references: [id], onDelete: SetNull)
|
||
sourceAssetVariantIds String[] @default([])
|
||
assetProfile ProductSkuAssetProfile?
|
||
warehouseLocationId String?
|
||
warehouseLocation WarehouseLocation? @relation("ProductSKUWarehouseLocation", fields: [warehouseLocationId], references: [id], onDelete: SetNull)
|
||
packagedItems PackagedItem[] @relation("PackagedItemSku")
|
||
qualityInspections ProductSkuQualityInspection[] @relation("ProductSKUQualityInspections")
|
||
|
||
// 元数据
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
deletedAt DateTime? // 软删除
|
||
|
||
@@index([productId])
|
||
@@index([skuCode])
|
||
@@index([name])
|
||
@@index([isActive])
|
||
@@index([ownerId])
|
||
@@index([sourceAssetVariantId])
|
||
@@index([sourceAssetVariantIds], type: Gin)
|
||
@@index([warehouseLocationId])
|
||
}
|
||
|
||
// 正式 SKU 的资产资料结构化同步副本
|
||
model ProductSkuAssetProfile {
|
||
id String @id @default(cuid())
|
||
skuId String @unique
|
||
sku ProductSKU @relation(fields: [skuId], references: [id], onDelete: Cascade)
|
||
|
||
primarySourceAssetVariantId String?
|
||
sourceAssetVariantIds String[] @default([])
|
||
sourceAssetFamilyId String?
|
||
sourceAssetSeriesId String?
|
||
|
||
model String?
|
||
modelName String?
|
||
specCode String?
|
||
specName String?
|
||
barcode String?
|
||
size Json
|
||
color String?
|
||
material String?
|
||
imageUrl String?
|
||
|
||
sourceStatus ProductSkuAssetSourceStatus @default(ACTIVE)
|
||
skuDeletedAt DateTime?
|
||
syncedAt DateTime
|
||
lockedFields String[] @default([])
|
||
lockedReasons Json
|
||
sourceVariants Json
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([primarySourceAssetVariantId])
|
||
@@index([sourceAssetVariantIds], type: Gin)
|
||
@@index([sourceAssetFamilyId])
|
||
@@index([sourceAssetSeriesId])
|
||
@@index([specCode])
|
||
@@index([barcode])
|
||
@@index([sourceStatus])
|
||
@@index([skuDeletedAt])
|
||
}
|
||
|
||
// 产品协作者关系表
|
||
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
|
||
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?
|
||
approvalWorkflows ApprovalWorkflow[]
|
||
skuItems IntakeNonIntegratedSkuItem[]
|
||
pricingRows IntakeNonIntegratedPricingRow[]
|
||
productLimitRows IntakeNonIntegratedProductLimitRow[]
|
||
fixedVirtualInventoryTasks IntakeJushuitanFixedVirtualInventoryTask[]
|
||
profile IntakeNonIntegratedProductProfile?
|
||
modelAssets IntakeProductModelAsset[]
|
||
|
||
@@index([spu])
|
||
@@index([spuBase])
|
||
@@index([createdBy])
|
||
@@index([departmentId])
|
||
@@index([teamId])
|
||
@@index([visibility])
|
||
@@index([deletedAt, createdAt])
|
||
}
|
||
|
||
model BarcodePrintImportRecord {
|
||
id String @id @default(cuid())
|
||
kind String
|
||
// System-created barcode rows point to their pricing-row source. Imported
|
||
// rows keep this null, so identical Excel rows remain independently valid.
|
||
sourcePricingRowId String? @unique
|
||
skuCode String
|
||
normalizedSku String
|
||
supplierCode String @default("")
|
||
supplierName String @default("")
|
||
normalizedSupplierName String @default("")
|
||
sourceFileName String @default("")
|
||
rowNumber Int @default(0)
|
||
importedAt DateTime @default(now())
|
||
updates Json
|
||
createdBy String
|
||
creator User @relation("BarcodePrintImportRecordCreator", fields: [createdBy], references: [id], onDelete: Cascade)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([kind])
|
||
@@index([normalizedSku])
|
||
@@index([supplierCode])
|
||
@@index([normalizedSupplierName])
|
||
@@index([createdBy])
|
||
@@index([createdAt])
|
||
@@index([kind, createdAt, id])
|
||
@@index([kind, normalizedSupplierName, createdAt, id])
|
||
@@index([kind, supplierCode, createdAt, id])
|
||
@@index([kind, normalizedSku, createdAt, id])
|
||
}
|
||
|
||
model IntakeNonIntegratedProductProfile {
|
||
id String @id @default(cuid())
|
||
productId String @unique
|
||
product IntakeNonIntegratedProduct @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||
productName String @default("")
|
||
productImageUrl String? @db.Text
|
||
productCategory String @default("")
|
||
salesCategory String @default("")
|
||
productYear String @default("")
|
||
productStatus String @default("")
|
||
productDevelopmentKind String @default("")
|
||
goodsType String @default("")
|
||
materialType String @default("")
|
||
productionType String @default("")
|
||
size String @default("")
|
||
color String @default("")
|
||
tagMaterial String @default("")
|
||
priceBand String @default("")
|
||
demandFlowNo String @default("")
|
||
processTemplate String @default("")
|
||
productPlanning String @default("")
|
||
brandPlanning String @default("")
|
||
demandDepartment String @default("")
|
||
spuOwner String @default("")
|
||
operationsOwner String @default("")
|
||
purchaseOwner String @default("")
|
||
visualDirector String @default("")
|
||
visualPlanning String @default("")
|
||
structureDesigner String @default("")
|
||
sourceSpu String @default("")
|
||
sourceSkuId String @default("")
|
||
sourceCategory String @default("")
|
||
sourceItemType String @default("")
|
||
sourceSalesStatus String @default("")
|
||
sourceEnabled String @default("")
|
||
sourceStockDisabled String @default("")
|
||
stockSkuCount Int @default(0)
|
||
stockHasOnSaleSku Boolean @default(false)
|
||
source String @default("PAYLOAD_SYNC")
|
||
syncedAt DateTime @default(now())
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([productName])
|
||
@@index([productCategory])
|
||
@@index([salesCategory])
|
||
@@index([productStatus])
|
||
@@index([productDevelopmentKind])
|
||
@@index([spuOwner])
|
||
@@index([purchaseOwner])
|
||
@@index([sourceSpu])
|
||
@@index([sourceSkuId])
|
||
@@index([sourceCategory])
|
||
@@index([sourceItemType])
|
||
@@index([sourceSalesStatus])
|
||
@@index([sourceEnabled])
|
||
}
|
||
|
||
model IntakeNonIntegratedSkuItem {
|
||
id String @id
|
||
productId String
|
||
product IntakeNonIntegratedProduct @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||
pricingRow IntakeNonIntegratedPricingRow? @relation("IntakeNonIntegratedPricingSkuItem")
|
||
productLimitRow IntakeNonIntegratedProductLimitRow? @relation("IntakeNonIntegratedLimitSkuItem")
|
||
skuItemId String
|
||
sortOrder Int
|
||
skuSequence Int @default(0)
|
||
batchNo Int @default(1)
|
||
demandFlowNo String @default("")
|
||
purchaseOwner String @default("")
|
||
packageGroupId String @default("")
|
||
setCount String @default("1")
|
||
image String? @db.Text
|
||
imageName String @default("")
|
||
model String @default("")
|
||
skuCode String @default("")
|
||
specName String @default("")
|
||
barcode String @default("")
|
||
size String @default("")
|
||
color String @default("")
|
||
material String @default("")
|
||
boardMaterialType String @default("")
|
||
versionNo String @default("V01")
|
||
attributeCode String @default("")
|
||
drawingId String @default("")
|
||
installMethod String @default("")
|
||
executeStandard String @default("")
|
||
nationalStandardCompliant String @default("")
|
||
manufacturer String @default("")
|
||
manufacturerCode String @default("")
|
||
factoryWarehouse String @default("")
|
||
shippingMethod String @default("")
|
||
purchaseOrderMethod String @default("")
|
||
minimumOrderQuantity String @default("")
|
||
taxIncludedUnitPrice String @default("")
|
||
leadTime String @default("")
|
||
sampleWeightKg Decimal? @db.Decimal(18, 3)
|
||
sampleLengthCm Decimal? @db.Decimal(18, 3)
|
||
sampleWidthCm Decimal? @db.Decimal(18, 3)
|
||
sampleHeightCm Decimal? @db.Decimal(18, 3)
|
||
salesStatus String @default("")
|
||
tagMaterial String @default("")
|
||
model3dDataUrl String? @db.Text
|
||
source String @default("")
|
||
sourceTable String @default("")
|
||
sourceSkuId String @default("")
|
||
sourceWeight Decimal? @db.Decimal(18, 3)
|
||
sourceBrand String @default("")
|
||
sourceCreatedAt DateTime?
|
||
sourceUpdatedAt DateTime?
|
||
contentLocked Boolean @default(false)
|
||
dataQualityStatus IntakeNonIntegratedSkuDataQualityStatus @default(INCOMPLETE)
|
||
missingRequiredFields String[] @default([])
|
||
aiProfile IntakeSkuAiProfile?
|
||
aiRenders AssistedAssemblyAiRender[]
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([productId, skuItemId])
|
||
@@index([productId])
|
||
@@index([productId, purchaseOwner])
|
||
@@index([demandFlowNo])
|
||
@@index([skuCode])
|
||
@@index([barcode])
|
||
@@index([model])
|
||
@@index([dataQualityStatus])
|
||
@@index([sourceTable, sourceSkuId])
|
||
}
|
||
|
||
model ProductArchiveImageEmbedding {
|
||
id String @id @default(cuid())
|
||
archiveId String @unique
|
||
archive SupplyChainProductArchive @relation(fields: [archiveId], references: [id], onDelete: Cascade)
|
||
imageUrl String @db.Text
|
||
imageHash String @db.VarChar(64)
|
||
embedding Bytes
|
||
dimensions Int
|
||
modelVersion String @db.VarChar(120)
|
||
status String @default("READY") @db.VarChar(24)
|
||
errorMessage String? @db.Text
|
||
indexedAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([status])
|
||
@@index([imageHash])
|
||
}
|
||
|
||
model SupplyChainProductArchive {
|
||
id String @id @default(cuid())
|
||
barcode String @unique
|
||
spu String @default("")
|
||
sku String @default("")
|
||
image String? @db.Text
|
||
businessCreatedAt DateTime
|
||
categoryL1 String @default("")
|
||
categoryL2 String @default("")
|
||
productName String @default("")
|
||
designer String @default("")
|
||
productStatus String @default("")
|
||
material String @default("")
|
||
color String @default("")
|
||
weight Decimal? @db.Decimal(18, 3)
|
||
length Decimal? @db.Decimal(18, 3)
|
||
width Decimal? @db.Decimal(18, 3)
|
||
height Decimal? @db.Decimal(18, 3)
|
||
volume Decimal? @db.Decimal(24, 3)
|
||
packType String @default("")
|
||
productTag String @default("")
|
||
installDocId String @default("")
|
||
hasDrawing String @default("")
|
||
hasVideo String @default("")
|
||
spec String @default("")
|
||
brand String @default("")
|
||
orderType String @default("")
|
||
inOutWarehouse String @default("")
|
||
storageType String @default("")
|
||
isSeasonal String @default("")
|
||
replaceCount String @default("")
|
||
region String @default("")
|
||
supplierName String @default("")
|
||
opsDirector String @default("")
|
||
opsDeptHead String @default("")
|
||
opsHead String @default("")
|
||
purchaseOwner String @default("")
|
||
supplierType String @default("")
|
||
supplierStatus String @default("")
|
||
minimumOrderQuantity String @default("")
|
||
settlementMethod String @default("")
|
||
boardProvider String @default("")
|
||
penaltyStatus String @default("")
|
||
warehouseType String @default("")
|
||
warehouseHead String @default("")
|
||
firstSourceCode String @default("PRODUCT_DEVELOPMENT")
|
||
firstLeadTime String @default("")
|
||
palletSpec String @default("")
|
||
dailySales String @default("")
|
||
sales3d String @default("")
|
||
sales7d String @default("")
|
||
sales15d String @default("")
|
||
purchasePrice String @default("")
|
||
costPrice String @default("")
|
||
costPriceSecondary String @default("")
|
||
fieldSourceMap Json @default("{}")
|
||
sources SupplyChainProductArchiveSource[]
|
||
imageEmbedding ProductArchiveImageEmbedding?
|
||
imageCache SupplyChainProductArchiveImageCache?
|
||
salesPerformance SupplyChainProductArchiveSalesPerformance?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([spu])
|
||
@@index([sku])
|
||
@@index([businessCreatedAt])
|
||
@@index([categoryL1])
|
||
@@index([categoryL2])
|
||
@@index([productStatus])
|
||
@@index([inOutWarehouse])
|
||
@@index([firstSourceCode, createdAt])
|
||
}
|
||
|
||
// 销售宽表按条码汇总的每日快照。
|
||
model SupplyChainProductArchiveSalesPerformance {
|
||
archiveId String @id
|
||
archive SupplyChainProductArchive @relation(fields: [archiveId], references: [id], onDelete: Cascade)
|
||
barcode String @unique
|
||
periodStart DateTime @db.Date
|
||
periodEnd DateTime @db.Date
|
||
shipAmount Decimal @db.Decimal(24, 4)
|
||
returnAmount Decimal @db.Decimal(24, 4)
|
||
realPerformance Decimal @db.Decimal(24, 4)
|
||
shipQty BigInt
|
||
returnQty BigInt
|
||
grossProfit Decimal @db.Decimal(24, 4)
|
||
grossMargin Decimal? @db.Decimal(20, 8)
|
||
feeRatio Decimal? @db.Decimal(20, 8)
|
||
operatingProfit Decimal? @db.Decimal(24, 4)
|
||
syncedAt DateTime @default(now())
|
||
|
||
@@index([periodEnd])
|
||
@@index([syncedAt])
|
||
}
|
||
|
||
// 图纸与视频导出来源:按条码保存当前有效的图纸/视频状态,支持月度增量覆盖。
|
||
model SupplyChainProductArchiveImageCache {
|
||
id String @id @default(cuid())
|
||
archiveId String @unique
|
||
archive SupplyChainProductArchive @relation(fields: [archiveId], references: [id], onDelete: Cascade)
|
||
barcode String @default("")
|
||
spu String @default("")
|
||
sku String @default("")
|
||
previewUrl String? @db.Text
|
||
source String @default("")
|
||
viewKey String @default("")
|
||
viewLabel String @default("")
|
||
status String @default("NOT_FOUND")
|
||
checkedAt DateTime @default(now())
|
||
error String @default("")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([barcode])
|
||
@@index([spu, sku])
|
||
@@index([status, checkedAt])
|
||
}
|
||
|
||
model SupplyChainProductArchiveDrawingMedia {
|
||
id String @id @default(cuid())
|
||
barcode String @unique
|
||
drawingId String @default("")
|
||
skuModel String @default("")
|
||
factoryModel String @default("")
|
||
completedAt DateTime?
|
||
externalLink String? @db.Text
|
||
lineDrawingStatus String @default("")
|
||
explodedDrawingStatus String @default("")
|
||
installationDrawingStatus String @default("")
|
||
installationVideoStatus String @default("")
|
||
sourceFileName String @default("")
|
||
sourceRowNumber Int?
|
||
sourceUpdatedAt DateTime?
|
||
lastSyncedAt DateTime @default(now())
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([drawingId])
|
||
@@index([completedAt])
|
||
@@index([sourceFileName, sourceRowNumber])
|
||
}
|
||
|
||
model SupplyChainProductArchiveCategoryConfig {
|
||
id String @id @default(cuid())
|
||
categoryL2 String @unique
|
||
categoryL1 String
|
||
status String @default("ACTIVE")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([categoryL1])
|
||
@@index([status])
|
||
}
|
||
|
||
model SupplyChainProductArchiveWarehouseConfig {
|
||
id String @id @default(cuid())
|
||
warehouseName String @unique
|
||
warehouseHead String @default("")
|
||
storageType String
|
||
status String @default("ACTIVE")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([storageType])
|
||
@@index([status])
|
||
}
|
||
|
||
model SupplyChainProductArchiveSource {
|
||
id String @id @default(cuid())
|
||
archiveId String
|
||
archive SupplyChainProductArchive @relation(fields: [archiveId], references: [id], onDelete: Cascade)
|
||
sourceCode String
|
||
entryMode String @default("AUTO")
|
||
sourceRecordId String
|
||
sourceProductId String @default("")
|
||
sourceSkuItemId String @default("")
|
||
sourceSystem String @default("")
|
||
sourceSchema String @default("")
|
||
sourceObject String @default("")
|
||
sourceCreatedAt DateTime?
|
||
lastSyncedAt DateTime @default(now())
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([sourceCode, sourceRecordId])
|
||
@@index([archiveId, sourceCode])
|
||
@@index([sourceProductId])
|
||
@@index([sourceSkuItemId])
|
||
}
|
||
|
||
model IntakeProductModelAsset {
|
||
id String @id @default(cuid())
|
||
productId String
|
||
product IntakeNonIntegratedProduct @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||
modelKey String
|
||
displayName String @default("")
|
||
versions IntakeProductModelVersion[]
|
||
createdBy String
|
||
creator User @relation("IntakeProductModelAssetCreator", fields: [createdBy], references: [id], onDelete: Restrict)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([productId, modelKey])
|
||
@@index([productId, createdAt])
|
||
@@index([createdBy])
|
||
}
|
||
|
||
model IntakeProductModelVersion {
|
||
id String @id @default(cuid())
|
||
assetId String
|
||
asset IntakeProductModelAsset @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||
versionNo Int
|
||
originalFileName String
|
||
mimeType String
|
||
fileSize Int
|
||
checksumSha256 String @default("")
|
||
sourceStorageKey String @db.Text
|
||
sourceUrl String @db.Text
|
||
previewStorageKey String? @db.Text
|
||
previewUrl String? @db.Text
|
||
createdBy String
|
||
creator User @relation("IntakeProductModelVersionCreator", fields: [createdBy], references: [id], onDelete: Restrict)
|
||
skuProfiles IntakeSkuAiProfile[]
|
||
aiRenders AssistedAssemblyAiRender[]
|
||
createdAt DateTime @default(now())
|
||
|
||
@@unique([assetId, versionNo])
|
||
@@index([assetId, createdAt])
|
||
@@index([checksumSha256])
|
||
@@index([createdBy])
|
||
}
|
||
|
||
model IntakeSkuAiProfile {
|
||
id String @id @default(cuid())
|
||
skuItemId String @unique
|
||
skuItem IntakeNonIntegratedSkuItem @relation(fields: [skuItemId], references: [id], onDelete: Cascade)
|
||
currentModelVersionId String?
|
||
currentModelVersion IntakeProductModelVersion? @relation(fields: [currentModelVersionId], references: [id], onDelete: SetNull)
|
||
currentMaterialVersionId String? @unique
|
||
currentMaterialVersion IntakeSkuAiMaterialVersion? @relation("CurrentIntakeSkuAiMaterialVersion", fields: [currentMaterialVersionId], references: [id], onDelete: SetNull)
|
||
materialVersions IntakeSkuAiMaterialVersion[] @relation("IntakeSkuAiMaterialVersions")
|
||
createdBy String
|
||
updatedBy String?
|
||
creator User @relation("IntakeSkuAiProfileCreator", fields: [createdBy], references: [id], onDelete: Restrict)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([currentModelVersionId])
|
||
@@index([createdBy])
|
||
}
|
||
|
||
model IntakeSkuAiMaterialVersion {
|
||
id String @id @default(cuid())
|
||
profileId String
|
||
profile IntakeSkuAiProfile @relation("IntakeSkuAiMaterialVersions", fields: [profileId], references: [id], onDelete: Cascade)
|
||
currentForProfile IntakeSkuAiProfile? @relation("CurrentIntakeSkuAiMaterialVersion")
|
||
versionNo Int
|
||
materialColor String @default("#d8c6ad")
|
||
textureStorageKey String? @db.Text
|
||
textureUrl String? @db.Text
|
||
nodeMaterials Json?
|
||
uvConfig Json?
|
||
createdBy String
|
||
creator User @relation("IntakeSkuAiMaterialVersionCreator", fields: [createdBy], references: [id], onDelete: Restrict)
|
||
aiRenders AssistedAssemblyAiRender[]
|
||
createdAt DateTime @default(now())
|
||
|
||
@@unique([profileId, versionNo])
|
||
@@index([profileId, createdAt])
|
||
@@index([createdBy])
|
||
}
|
||
|
||
model IntakeNonIntegratedPricingRow {
|
||
id String @id
|
||
productId String
|
||
product IntakeNonIntegratedProduct @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||
skuItemId String
|
||
skuItem IntakeNonIntegratedSkuItem @relation("IntakeNonIntegratedPricingSkuItem", fields: [productId, skuItemId], references: [productId, skuItemId], onDelete: Cascade, onUpdate: Cascade, map: "IntakeNonIntegratedPricingRow_product_sku_item_fkey")
|
||
skuCode String @default("")
|
||
barcode String @default("")
|
||
costPrice Decimal? @db.Decimal(10, 2)
|
||
productTag String @default("")
|
||
pricingConfirmed Boolean @default(false)
|
||
pricingConfirmedAt DateTime?
|
||
systemEnteredAt DateTime?
|
||
jstVirtualInventoryStatus String @default("")
|
||
jstVirtualInventoryScheduledAt DateTime?
|
||
jstVirtualInventoryPushedAt DateTime?
|
||
jstVirtualInventoryRetryCount Int @default(0)
|
||
jstVirtualInventoryLastAttemptAt DateTime?
|
||
jstVirtualInventoryError String @default("")
|
||
jstVirtualInventorySoId String @default("")
|
||
source String @default("PAYLOAD_SYNC")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
|
||
@@unique([productId, skuItemId])
|
||
@@index([productId])
|
||
@@index([skuCode])
|
||
@@index([barcode])
|
||
@@index([pricingConfirmed])
|
||
@@index([systemEnteredAt, productId])
|
||
}
|
||
|
||
model IntakeJushuitanFixedVirtualInventoryTask {
|
||
id String @id @default(cuid())
|
||
productId String
|
||
product IntakeNonIntegratedProduct @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||
productSpu String @default("")
|
||
skuItemId String
|
||
skuCode String @default("")
|
||
barcode String @default("")
|
||
status String @default("pending")
|
||
scheduledAt DateTime
|
||
retryCount Int @default(0)
|
||
lastAttemptAt DateTime?
|
||
pushedAt DateTime?
|
||
error String @default("")
|
||
requestId String @default("")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([productId, skuItemId])
|
||
@@index([status, scheduledAt])
|
||
@@index([productId])
|
||
}
|
||
|
||
model IntakeNonIntegratedProductLimitRow {
|
||
id String @id
|
||
productId String
|
||
product IntakeNonIntegratedProduct @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||
skuItemId String
|
||
skuItem IntakeNonIntegratedSkuItem @relation("IntakeNonIntegratedLimitSkuItem", fields: [productId, skuItemId], references: [productId, skuItemId], onDelete: Cascade, onUpdate: Cascade, map: "IntakeNonIntegratedProductLimitRow_product_sku_item_fkey")
|
||
skuCode String @default("")
|
||
barcode String @default("")
|
||
expectedDailyLimitPrice String @default("")
|
||
expectedLimitGrossMarginRate String @default("")
|
||
nonSpuOwnerLimitPrice String @default("")
|
||
limitConfirmed Boolean @default(false)
|
||
limitConfirmedAt DateTime?
|
||
source String @default("PAYLOAD_SYNC")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
|
||
@@unique([productId, skuItemId])
|
||
@@index([productId])
|
||
@@index([skuCode])
|
||
@@index([barcode])
|
||
@@index([limitConfirmed])
|
||
}
|
||
|
||
model MasterDataQualityDecision {
|
||
id String @id @default(cuid())
|
||
targetType String
|
||
targetId String
|
||
targetBusinessKey String @default("")
|
||
status String @default("APPROVED_INCOMPLETE")
|
||
missingFields String[] @default([])
|
||
reason String @db.Text
|
||
decidedBy String
|
||
decidedAt DateTime @default(now())
|
||
resolvedBy String?
|
||
resolvedAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([targetType, targetId])
|
||
@@index([targetBusinessKey])
|
||
@@index([status])
|
||
@@index([decidedAt])
|
||
}
|
||
|
||
model ProductPatentRecord {
|
||
id String @id
|
||
patentGroupKey String @default("")
|
||
productId String @default("")
|
||
linkedProductIds String[] @default([])
|
||
linkedProductNames String[] @default([])
|
||
linkedSpus String[] @default([])
|
||
linkedBrands String[] @default([])
|
||
productDisplayName String @default("")
|
||
productImage String? @db.Text
|
||
productImageName String @default("")
|
||
patentStatus String @default("待确认")
|
||
patentType String @default("外观设计")
|
||
patentName String @default("")
|
||
spu String @default("")
|
||
saRequired String @default("否")
|
||
salesStatus String @default("在售")
|
||
designer String @default("")
|
||
applicationNo String @default("")
|
||
patentNo String @default("")
|
||
applicationDate String @default("")
|
||
authorizationDate String @default("")
|
||
firstFeeDate String @default("")
|
||
annualFees Json @default("{}")
|
||
annualFeeDates Json @default("{}")
|
||
nextFeeDueDate String @default("")
|
||
feeAmount String @default("")
|
||
patentCertificateFileName String @default("")
|
||
patentCertificateFileUrl String? @db.Text
|
||
patentCertificateFileMimeType String @default("")
|
||
patentCertificateStorageKey String @default("")
|
||
annualFeeNote String @default("")
|
||
continueDecision String @default("待判断")
|
||
decisionReason String @default("")
|
||
owner String @default("")
|
||
remark String @default("")
|
||
sortOrder Int @default(0)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([patentGroupKey])
|
||
@@index([productId])
|
||
@@index([spu])
|
||
@@index([patentStatus])
|
||
@@index([patentNo])
|
||
@@index([applicationNo])
|
||
@@index([designer])
|
||
@@index([nextFeeDueDate])
|
||
@@index([sortOrder])
|
||
}
|
||
|
||
model IntakeReferenceLibraryItem {
|
||
id String @id @default(cuid())
|
||
libraryType String
|
||
itemKey String
|
||
code String @default("")
|
||
name String @default("")
|
||
category String @default("")
|
||
letter String @default("")
|
||
payload Json @default("{}")
|
||
sortOrder Int @default(0)
|
||
isActive Boolean @default(true)
|
||
createdBy String?
|
||
updatedBy String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([libraryType, itemKey])
|
||
@@index([libraryType, isActive, sortOrder])
|
||
@@index([libraryType, code])
|
||
@@index([libraryType, name])
|
||
@@index([category])
|
||
}
|
||
|
||
model BomCostingCase {
|
||
id String @id @default(cuid())
|
||
sourceUrl String @db.Text
|
||
sourceHost String
|
||
sourceSnapshotId String?
|
||
sourceSnapshot BomCostingSourceSnapshot? @relation(fields: [sourceSnapshotId], references: [id])
|
||
sourceCacheHit Boolean @default(false)
|
||
title String @default("")
|
||
providerCode String @default("")
|
||
providerTaskId String @default("")
|
||
analysisSchemaVersion String @default("competitor-analysis/v1")
|
||
status BomCostingCaseStatus @default(DRAFT)
|
||
progress Int @default(0)
|
||
statusMessage String @default("")
|
||
targetErrorPercent Decimal @default(10) @db.Decimal(5, 2)
|
||
currency String @default("CNY")
|
||
rawAnalysisSnapshot Json?
|
||
createdById String
|
||
creator User @relation("BomCostingCaseCreator", fields: [createdById], references: [id])
|
||
processingStartedAt DateTime?
|
||
processingFinishedAt DateTime?
|
||
deletedAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
skus BomCostingSku[]
|
||
taskEvents BomCostingTaskEvent[]
|
||
analysisRuns BomCostingAnalysisRun[]
|
||
reviewInputs BomCostingReviewInput[]
|
||
scenarios BomCostingScenario[]
|
||
|
||
@@index([createdById, deletedAt, createdAt])
|
||
@@index([status, deletedAt])
|
||
@@index([sourceSnapshotId])
|
||
@@index([providerTaskId])
|
||
}
|
||
|
||
model BomCostingSourceSnapshot {
|
||
id String @id @default(cuid())
|
||
sourceKey String @unique
|
||
sourceUrl String @db.Text
|
||
sourceHost String
|
||
channel String
|
||
externalItemId String
|
||
title String @default("")
|
||
shopName String @default("")
|
||
detailUrl String? @db.Text
|
||
status BomCostingSourceStatus @default(PENDING)
|
||
skuCount Int @default(0)
|
||
imageCount Int @default(0)
|
||
storedImageCount Int @default(0)
|
||
failedImageCount Int @default(0)
|
||
rawSnapshot Json?
|
||
errorMessage String @default("")
|
||
fetchedAt DateTime?
|
||
completedAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
cases BomCostingCase[]
|
||
assets BomCostingSourceAsset[]
|
||
factAnalyses BomCostingSourceFactAnalysis[]
|
||
|
||
@@index([status, updatedAt])
|
||
@@index([channel, externalItemId])
|
||
}
|
||
|
||
model BomCostingSourceAsset {
|
||
id String @id @default(cuid())
|
||
sourceSnapshotId String
|
||
sourceSnapshot BomCostingSourceSnapshot @relation(fields: [sourceSnapshotId], references: [id], onDelete: Cascade)
|
||
assetKey String
|
||
kind BomCostingSourceAssetKind
|
||
status BomCostingSourceAssetStatus @default(PENDING)
|
||
originalUrl String @db.Text
|
||
storedUrl String @default("") @db.Text
|
||
storageKey String @default("") @db.Text
|
||
contentType String @default("")
|
||
byteSize Int?
|
||
externalSkuIds Json @default("[]")
|
||
sortOrder Int @default(0)
|
||
errorMessage String @default("")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([sourceSnapshotId, assetKey])
|
||
@@index([sourceSnapshotId, status, sortOrder])
|
||
}
|
||
|
||
model BomCostingSourceFactAnalysis {
|
||
id String @id @default(cuid())
|
||
sourceSnapshotId String
|
||
sourceSnapshot BomCostingSourceSnapshot @relation(fields: [sourceSnapshotId], references: [id], onDelete: Cascade)
|
||
schemaVersion String
|
||
revision Int @default(1)
|
||
origin BomCostingFactAnalysisOrigin @default(INITIAL)
|
||
baseAnalysisId String?
|
||
baseAnalysis BomCostingSourceFactAnalysis? @relation("BomCostingFactAnalysisRevisions", fields: [baseAnalysisId], references: [id], onDelete: SetNull)
|
||
derivedAnalyses BomCostingSourceFactAnalysis[] @relation("BomCostingFactAnalysisRevisions")
|
||
selectedAssetIds Json @default("[]")
|
||
assetFingerprint String
|
||
status BomCostingFactAnalysisStatus @default(PENDING)
|
||
model String @default("")
|
||
factSnapshot Json?
|
||
analyzedImageCount Int @default(0)
|
||
totalImageCount Int @default(0)
|
||
batchCount Int @default(0)
|
||
promptTokens Int @default(0)
|
||
completionTokens Int @default(0)
|
||
durationMs Int @default(0)
|
||
errorMessage String @default("")
|
||
startedAt DateTime?
|
||
completedAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
analysisRuns BomCostingAnalysisRun[]
|
||
|
||
@@unique([sourceSnapshotId, schemaVersion, revision])
|
||
@@index([sourceSnapshotId, status])
|
||
@@index([status, updatedAt])
|
||
@@index([baseAnalysisId])
|
||
}
|
||
|
||
model BomCostingAnalysisRun {
|
||
id String @id @default(cuid())
|
||
caseId String
|
||
case BomCostingCase @relation(fields: [caseId], references: [id], onDelete: Cascade)
|
||
revision Int
|
||
mode BomCostingAnalysisRunMode
|
||
status BomCostingAnalysisRunStatus @default(PENDING)
|
||
factAnalysisId String?
|
||
factAnalysis BomCostingSourceFactAnalysis? @relation(fields: [factAnalysisId], references: [id], onDelete: SetNull)
|
||
selectedAssetIds Json @default("[]")
|
||
instruction String @default("") @db.Text
|
||
requestedCategorySkillId String @default("")
|
||
resolvedCategorySkillId String @default("")
|
||
resolvedCategorySkillVersion String @default("")
|
||
resolvedStructureKnowledgeVersion String @default("")
|
||
categorySkillSelection Json?
|
||
resultSnapshot Json?
|
||
model String @default("")
|
||
callId String @default("")
|
||
promptTokens Int @default(0)
|
||
completionTokens Int @default(0)
|
||
durationMs Int @default(0)
|
||
errorMessage String @default("")
|
||
createdById String?
|
||
creator User? @relation("BomCostingAnalysisRunCreator", fields: [createdById], references: [id], onDelete: SetNull)
|
||
startedAt DateTime?
|
||
completedAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([caseId, revision])
|
||
@@index([caseId, status])
|
||
@@index([factAnalysisId])
|
||
@@index([status, updatedAt])
|
||
@@index([createdById])
|
||
}
|
||
|
||
model BomCostingSku {
|
||
id String @id @default(cuid())
|
||
caseId String
|
||
case BomCostingCase @relation(fields: [caseId], references: [id], onDelete: Cascade)
|
||
externalSkuId String
|
||
name String @default("")
|
||
structureGroupKey String @default("")
|
||
costFamilyKey String @default("")
|
||
imageUrl String? @db.Text
|
||
attributes Json?
|
||
lengthMm Decimal? @db.Decimal(12, 3)
|
||
widthMm Decimal? @db.Decimal(12, 3)
|
||
heightMm Decimal? @db.Decimal(12, 3)
|
||
weightKg Decimal? @db.Decimal(12, 3)
|
||
packageLengthMm Decimal? @db.Decimal(12, 3)
|
||
packageWidthMm Decimal? @db.Decimal(12, 3)
|
||
packageHeightMm Decimal? @db.Decimal(12, 3)
|
||
packageWeightKg Decimal? @db.Decimal(12, 3)
|
||
estimatedCost Decimal? @db.Decimal(12, 2)
|
||
costCalculatedAt DateTime?
|
||
sortOrder Int @default(0)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
bomLines BomCostingLine[]
|
||
reviewInputs BomCostingReviewInput[]
|
||
scenarios BomCostingScenario[]
|
||
|
||
@@unique([caseId, externalSkuId])
|
||
@@index([caseId, sortOrder])
|
||
@@index([structureGroupKey])
|
||
@@index([costFamilyKey])
|
||
}
|
||
|
||
model BomCostingLine {
|
||
id String @id @default(cuid())
|
||
skuId String
|
||
sku BomCostingSku @relation(fields: [skuId], references: [id], onDelete: Cascade)
|
||
externalLineId String
|
||
category BomCostingLineCategory
|
||
partName String
|
||
materialCode String @default("")
|
||
materialName String @default("")
|
||
materialSpec String @default("")
|
||
processName String @default("")
|
||
packagingKind String @default("")
|
||
lengthMm Decimal? @db.Decimal(12, 3)
|
||
widthMm Decimal? @db.Decimal(12, 3)
|
||
heightMm Decimal? @db.Decimal(12, 3)
|
||
quantity Decimal @default(1) @db.Decimal(12, 4)
|
||
unit String @default("")
|
||
weightKg Decimal? @db.Decimal(12, 4)
|
||
lossFactor Decimal @default(1) @db.Decimal(8, 4)
|
||
sourceConfidence Decimal? @db.Decimal(5, 4)
|
||
evidence Json?
|
||
matchStatus BomCostingMatchStatus @default(UNMATCHED)
|
||
matchedLibraryType String @default("")
|
||
matchedLibraryRecordId String @default("")
|
||
matchedLibraryName String @default("")
|
||
matchedLibrarySpec String @default("")
|
||
resolvedUnitPrice Decimal? @db.Decimal(12, 4)
|
||
calculatedQuantity Decimal? @db.Decimal(14, 6)
|
||
subtotal Decimal? @db.Decimal(12, 2)
|
||
sortOrder Int @default(0)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
priceSnapshots BomCostingPriceSnapshot[]
|
||
reviewInputs BomCostingReviewInput[]
|
||
scenarioLines BomCostingScenarioLine[]
|
||
|
||
@@unique([skuId, externalLineId])
|
||
@@index([skuId, sortOrder])
|
||
@@index([category, matchStatus])
|
||
@@index([matchedLibraryType, matchedLibraryRecordId])
|
||
}
|
||
|
||
model BomCostingPriceSnapshot {
|
||
id String @id @default(cuid())
|
||
lineId String
|
||
line BomCostingLine @relation(fields: [lineId], references: [id], onDelete: Cascade)
|
||
sourceType String
|
||
sourceRecordId String
|
||
sourceName String
|
||
sourceSpec String @default("")
|
||
unitPrice Decimal @db.Decimal(12, 4)
|
||
unit String
|
||
currency String @default("CNY")
|
||
lossFactor Decimal @default(1) @db.Decimal(8, 4)
|
||
isCurrent Boolean @default(true)
|
||
matchedAt DateTime @default(now())
|
||
|
||
@@index([lineId, isCurrent])
|
||
@@index([sourceType, sourceRecordId])
|
||
}
|
||
|
||
model BomCostingReviewInput {
|
||
id String @id @default(cuid())
|
||
caseId String
|
||
case BomCostingCase @relation(fields: [caseId], references: [id], onDelete: Cascade)
|
||
skuId String?
|
||
sku BomCostingSku? @relation(fields: [skuId], references: [id], onDelete: SetNull)
|
||
lineId String?
|
||
line BomCostingLine? @relation(fields: [lineId], references: [id], onDelete: SetNull)
|
||
skuExternalId String @default("")
|
||
lineExternalId String @default("")
|
||
skuName String @default("")
|
||
partName String @default("")
|
||
createdById String?
|
||
creator User? @relation("BomCostingReviewInputCreator", fields: [createdById], references: [id], onDelete: SetNull)
|
||
source BomCostingReviewInputSource @default(MANUAL)
|
||
lengthMm Decimal? @db.Decimal(12, 3)
|
||
widthMm Decimal? @db.Decimal(12, 3)
|
||
heightMm Decimal? @db.Decimal(12, 3)
|
||
quantity Decimal? @db.Decimal(12, 4)
|
||
materialCode String @default("")
|
||
materialName String @default("")
|
||
materialSpec String @default("")
|
||
unitPrice Decimal? @db.Decimal(12, 4)
|
||
priceUnit String @default("")
|
||
currency String @default("CNY")
|
||
lossFactor Decimal? @db.Decimal(8, 4)
|
||
subtotal Decimal? @db.Decimal(12, 2)
|
||
note String @default("") @db.Text
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([caseId, createdAt])
|
||
@@index([lineId, createdAt])
|
||
@@index([caseId, skuExternalId, lineExternalId, createdAt])
|
||
@@index([createdById, createdAt])
|
||
}
|
||
|
||
model BomCostingTaskEvent {
|
||
id String @id @default(cuid())
|
||
caseId String
|
||
case BomCostingCase @relation(fields: [caseId], references: [id], onDelete: Cascade)
|
||
eventType String
|
||
status String
|
||
progress Int @default(0)
|
||
message String @default("")
|
||
providerTaskId String @default("")
|
||
payload Json?
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([caseId, createdAt])
|
||
@@index([providerTaskId])
|
||
}
|
||
|
||
// Local, auditable mirror of the upstream rpt.pm_bomBaseInfo view. The
|
||
// costing request path only reads these rows; the SQL Server source is used by
|
||
// the scheduled sync job and never by a user request.
|
||
model BomCostingReferenceSyncRun {
|
||
id String @id @default(cuid())
|
||
sourceSystem String @default("JSReportReplica")
|
||
sourceDatabase String @default("")
|
||
sourceSchema String @default("rpt")
|
||
sourceObject String @default("pm_bomBaseInfo")
|
||
status BomCostingReferenceSyncStatus @default(RUNNING)
|
||
batchKey String @default("")
|
||
configFingerprint String @default("") @db.VarChar(64)
|
||
cursorStart String @default("")
|
||
cursorEnd String @default("")
|
||
rowsRead Int @default(0)
|
||
rowsWritten Int @default(0)
|
||
rowsCreated Int @default(0)
|
||
rowsUpdated Int @default(0)
|
||
rowsUnchanged Int @default(0)
|
||
rowsSkipped Int @default(0)
|
||
errorCount Int @default(0)
|
||
errorMessage String @default("") @db.Text
|
||
durationMs Int @default(0)
|
||
startedAt DateTime @default(now())
|
||
completedAt DateTime?
|
||
sourceAsOf DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
referenceRows BomCostingReferenceRow[]
|
||
scenarios BomCostingScenario[]
|
||
|
||
@@index([status, startedAt])
|
||
@@index([status, completedAt])
|
||
@@index([completedAt])
|
||
@@index([batchKey])
|
||
}
|
||
|
||
model BomCostingReferenceRow {
|
||
id String @id @default(cuid())
|
||
sourceRowId String
|
||
sourceSku String @default("")
|
||
bomVersionKey String @default("")
|
||
category String @default("")
|
||
categoryCode String @default("")
|
||
partNumber String @default("")
|
||
partName String @default("")
|
||
side String @default("")
|
||
materialName String @default("")
|
||
materialCode String @default("")
|
||
materialSpec String @default("")
|
||
processName String @default("")
|
||
unit String @default("")
|
||
rawLength String @default("")
|
||
rawWidth String @default("")
|
||
rawLongEdge String @default("")
|
||
rawShortEdge String @default("")
|
||
rawQuantity String @default("")
|
||
rawCoefficient String @default("")
|
||
rawArea String @default("")
|
||
rawEdgeLength String @default("")
|
||
rawUnitPrice String @default("")
|
||
rawAmount String @default("")
|
||
rawNote String @default("") @db.Text
|
||
rawCreatedAt String @default("")
|
||
lengthMm Decimal? @db.Decimal(18, 6)
|
||
widthMm Decimal? @db.Decimal(18, 6)
|
||
longEdgeMm Decimal? @db.Decimal(18, 6)
|
||
shortEdgeMm Decimal? @db.Decimal(18, 6)
|
||
quantity Decimal? @db.Decimal(18, 6)
|
||
coefficient Decimal? @db.Decimal(18, 6)
|
||
areaSqm Decimal? @db.Decimal(18, 6)
|
||
edgeLengthM Decimal? @db.Decimal(18, 6)
|
||
unitPrice Decimal? @db.Decimal(18, 6)
|
||
amount Decimal? @db.Decimal(18, 6)
|
||
sourceCreatedAt DateTime?
|
||
sourceFingerprint String @db.VarChar(64)
|
||
isSummaryRow Boolean @default(false)
|
||
syncRunId String
|
||
syncRun BomCostingReferenceSyncRun @relation(fields: [syncRunId], references: [id], onDelete: Cascade)
|
||
syncedAt DateTime @default(now())
|
||
|
||
// A run is written as an isolated snapshot so failed/new runs cannot mutate
|
||
// the visible batch. After activation, only the newest operational mirror is
|
||
// retained; scenario lines keep immutable historical calculation snapshots.
|
||
@@unique([syncRunId, sourceRowId, sourceSku, bomVersionKey], map: "BomCostingReferenceRow_source_key")
|
||
@@index([syncRunId, sourceSku, categoryCode, materialName], map: "BomCostingReferenceRow_syncRunId_sku_category_material_idx")
|
||
@@index([syncRunId, materialName, materialSpec, unit], map: "BomCostingReferenceRow_syncRunId_material_spec_unit_idx")
|
||
@@index([syncRunId, categoryCode, partNumber], map: "BomCostingReferenceRow_syncRunId_category_part_number_idx")
|
||
@@index([syncRunId, categoryCode, lengthMm, widthMm], map: "BomCostingReferenceRow_syncRunId_category_dimensions_idx")
|
||
@@index([sourceSku, categoryCode, materialName], map: "BomCostingReferenceRow_sku_category_material_idx")
|
||
@@index([materialName, materialSpec, unit], map: "BomCostingReferenceRow_material_spec_unit_idx")
|
||
@@index([syncRunId])
|
||
@@index([syncRunId, isSummaryRow, categoryCode], map: "BomCostingReferenceRow_syncRunId_summary_category_idx")
|
||
@@index([sourceCreatedAt])
|
||
}
|
||
|
||
model BomCostingMaterialOption {
|
||
id String @id @default(cuid())
|
||
code String @unique
|
||
name String
|
||
normalizedName String
|
||
category String @default("BOARD")
|
||
aliases Json @default("[]")
|
||
enabled Boolean @default(true)
|
||
sortOrder Int @default(0)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
substitutionScenarios BomCostingScenario[]
|
||
|
||
@@index([enabled, sortOrder])
|
||
@@index([normalizedName])
|
||
}
|
||
|
||
model BomCostingScenario {
|
||
id String @id @default(cuid())
|
||
caseId String
|
||
case BomCostingCase @relation(fields: [caseId], references: [id], onDelete: Cascade)
|
||
skuId String
|
||
sku BomCostingSku @relation(fields: [skuId], references: [id], onDelete: Cascade)
|
||
kind BomCostingScenarioKind
|
||
scenarioKey String @default("")
|
||
revision Int @default(1)
|
||
isCurrent Boolean @default(true)
|
||
targetMaterialId String?
|
||
targetMaterial BomCostingMaterialOption? @relation(fields: [targetMaterialId], references: [id], onDelete: SetNull)
|
||
targetMaterialName String @default("")
|
||
status BomCostingScenarioStatus @default(PROVISIONAL)
|
||
totalCost Decimal? @db.Decimal(18, 2)
|
||
estimateLow Decimal? @db.Decimal(18, 2)
|
||
estimateHigh Decimal? @db.Decimal(18, 2)
|
||
estimateBasis String @default("")
|
||
sampleCount Int @default(0)
|
||
estimatedLineCount Int @default(0)
|
||
knownCost Decimal? @db.Decimal(18, 2)
|
||
materialCost Decimal? @db.Decimal(18, 2)
|
||
processingCost Decimal? @db.Decimal(18, 2)
|
||
laborCost Decimal? @db.Decimal(18, 2)
|
||
packagingCost Decimal? @db.Decimal(18, 2)
|
||
lossCost Decimal? @db.Decimal(18, 2)
|
||
competitorPrice Decimal? @db.Decimal(18, 2)
|
||
priceDelta Decimal? @db.Decimal(18, 2)
|
||
realtimeMarketQuote Json?
|
||
wholeProductEstimateAudit Json?
|
||
confidence Decimal? @db.Decimal(5, 4)
|
||
warning String @default("") @db.Text
|
||
unresolvedCount Int @default(0)
|
||
dataAsOf DateTime?
|
||
syncRunId String?
|
||
syncRun BomCostingReferenceSyncRun? @relation(fields: [syncRunId], references: [id], onDelete: SetNull)
|
||
rulesetVersion String @default("dynamic-bom-cost/v5")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
lines BomCostingScenarioLine[]
|
||
|
||
@@unique([skuId, kind, scenarioKey, revision], map: "BomCostingScenario_sku_kind_scenarioKey_revision_key")
|
||
@@index([caseId, kind])
|
||
@@index([skuId, isCurrent, updatedAt])
|
||
@@index([status, dataAsOf])
|
||
@@index([syncRunId])
|
||
}
|
||
|
||
model BomCostingScenarioLine {
|
||
id String @id @default(cuid())
|
||
scenarioId String
|
||
scenario BomCostingScenario @relation(fields: [scenarioId], references: [id], onDelete: Cascade)
|
||
sourceLineId String?
|
||
sourceLine BomCostingLine? @relation(fields: [sourceLineId], references: [id], onDelete: SetNull)
|
||
sourceRowId String @default("")
|
||
category String @default("")
|
||
partName String @default("")
|
||
originalMaterial String @default("")
|
||
appliedMaterial String @default("")
|
||
materialSpec String @default("")
|
||
unit String @default("")
|
||
quantity Decimal? @db.Decimal(18, 6)
|
||
coefficient Decimal? @db.Decimal(18, 6)
|
||
pricingQuantity Decimal? @db.Decimal(18, 6)
|
||
unitPrice Decimal? @db.Decimal(18, 6)
|
||
subtotal Decimal? @db.Decimal(18, 2)
|
||
estimateLow Decimal? @db.Decimal(18, 2)
|
||
estimateHigh Decimal? @db.Decimal(18, 2)
|
||
estimateBasis String @default("")
|
||
sampleCount Int @default(0)
|
||
priceSource String @default("")
|
||
sourceSku String @default("")
|
||
referenceSkus Json @default("[]")
|
||
referenceRowIds Json @default("[]")
|
||
sourceFingerprint String @default("") @db.VarChar(64)
|
||
sourceCreatedAt DateTime?
|
||
sourceSyncedAt DateTime?
|
||
calculationRule String @default("")
|
||
confidence Decimal? @db.Decimal(5, 4)
|
||
status BomCostingScenarioStatus @default(PROVISIONAL)
|
||
warning String @default("") @db.Text
|
||
rawSnapshot Json?
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([scenarioId, category])
|
||
@@index([sourceRowId])
|
||
@@index([status])
|
||
}
|
||
|
||
// ============================================
|
||
// 装配相关
|
||
// ============================================
|
||
|
||
// 装配步骤(默认产品级,可选绑定到 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
|
||
// 4字段规格由服务层收口:相同规格+相同花色拒绝重复,相同规格+不同花色并入已有基材
|
||
|
||
@@index([isActive])
|
||
@@index([createdBy])
|
||
@@index([categoryId])
|
||
@@index([environmentGradeId])
|
||
@@index([thicknessSpecId])
|
||
@@index([surfaceOptionId])
|
||
@@index([systemNameOptionId])
|
||
}
|
||
|
||
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])
|
||
assetSeriesId String?
|
||
assetSeries AssetSeries? @relation("AssetFamilySeries", fields: [assetSeriesId], references: [id], onDelete: SetNull)
|
||
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)
|
||
deletedAt DateTime?
|
||
deletedById String?
|
||
deleter User? @relation("AssetFamilyDeleter", fields: [deletedById], references: [id], onDelete: SetNull)
|
||
|
||
variants AssetVariant[]
|
||
compatibleSeries AssetFamilySeriesCompatibility[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([type])
|
||
@@index([status])
|
||
@@index([productCategoryId])
|
||
@@index([assetSeriesId])
|
||
@@index([createdBy])
|
||
@@index([submittedById])
|
||
@@index([reviewedById])
|
||
@@index([rejectedById])
|
||
@@index([lastModifiedById])
|
||
@@index([deletedAt])
|
||
@@index([deletedById])
|
||
}
|
||
|
||
model AssetSeries {
|
||
id String @id @default(cuid())
|
||
code String @unique
|
||
name String
|
||
description String? @db.Text
|
||
|
||
productCategoryId String?
|
||
category ProductCategory? @relation("AssetSeriesCategory", fields: [productCategoryId], references: [id])
|
||
|
||
sortOrder Int @default(0)
|
||
isActive Boolean @default(true)
|
||
metadata Json?
|
||
|
||
createdBy String?
|
||
creator User? @relation("AssetSeriesCreator", fields: [createdBy], references: [id], onDelete: SetNull)
|
||
|
||
compatibleFamilies AssetFamilySeriesCompatibility[]
|
||
assetFamilies AssetFamily[] @relation("AssetFamilySeries")
|
||
draftGroups DraftGroup[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([productCategoryId])
|
||
@@index([createdBy])
|
||
@@index([sortOrder])
|
||
@@index([isActive])
|
||
}
|
||
|
||
model AssetFamilySeriesCompatibility {
|
||
id String @id @default(cuid())
|
||
assetFamilyId String
|
||
assetFamily AssetFamily @relation(fields: [assetFamilyId], references: [id], onDelete: Cascade)
|
||
assetSeriesId String
|
||
assetSeries AssetSeries @relation(fields: [assetSeriesId], references: [id], onDelete: Cascade)
|
||
|
||
createdAt DateTime @default(now())
|
||
|
||
@@unique([assetFamilyId, assetSeriesId])
|
||
@@index([assetFamilyId])
|
||
@@index([assetSeriesId])
|
||
}
|
||
|
||
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?
|
||
barcode String?
|
||
supplierName String?
|
||
supplierCode String?
|
||
versionNo 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[]
|
||
sourceProductSkus ProductSKU[] @relation("ProductSKUSourceAssetVariant")
|
||
skuSyncEvents AssetSkuSyncEvent[]
|
||
|
||
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)
|
||
deletedAt DateTime?
|
||
deletedById String?
|
||
deleter User? @relation("AssetVariantDeleter", fields: [deletedById], references: [id], onDelete: SetNull)
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([assetFamilyId, identityKey])
|
||
@@index([assetFamilyId])
|
||
@@index([status])
|
||
@@index([barcode])
|
||
@@index([substrateId])
|
||
@@index([finishId])
|
||
@@index([createdBy])
|
||
@@index([submittedById])
|
||
@@index([reviewedById])
|
||
@@index([rejectedById])
|
||
@@index([lastModifiedById])
|
||
@@index([inheritedFromVariantId])
|
||
@@index([deletedAt])
|
||
@@index([deletedById])
|
||
}
|
||
|
||
model AssetSkuSyncEvent {
|
||
id String @id @default(cuid())
|
||
assetVariantId String
|
||
assetVariant AssetVariant @relation(fields: [assetVariantId], references: [id], onDelete: Cascade)
|
||
reason String
|
||
status AssetSkuSyncEventStatus @default(PENDING)
|
||
payload Json?
|
||
progress Json?
|
||
affectedCount Int @default(0)
|
||
syncedCount Int @default(0)
|
||
failedCount Int @default(0)
|
||
errorMessage String? @db.Text
|
||
retryCount Int @default(0)
|
||
lockedAt DateTime?
|
||
processedAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([assetVariantId])
|
||
@@index([status])
|
||
@@index([status, createdAt])
|
||
@@index([createdAt])
|
||
}
|
||
|
||
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])
|
||
|
||
assetSeriesId String?
|
||
assetSeries AssetSeries? @relation(fields: [assetSeriesId], 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([assetSeriesId])
|
||
@@index([createdBy])
|
||
@@index([status])
|
||
}
|
||
|
||
model AssistedAssemblyAiRender {
|
||
id String @id @default(cuid())
|
||
|
||
createdBy String
|
||
creator User @relation("AssistedAssemblyAiRenderCreator", fields: [createdBy], references: [id], onDelete: Cascade)
|
||
|
||
workbenchSpuId String?
|
||
workbenchSpu AiRenderWorkbenchSpu? @relation(fields: [workbenchSpuId], references: [id], onDelete: SetNull)
|
||
workbenchSkuId String?
|
||
workbenchSku AiRenderWorkbenchSku? @relation(fields: [workbenchSkuId], references: [id], onDelete: SetNull)
|
||
|
||
intakeSkuItemId String?
|
||
intakeSkuItem IntakeNonIntegratedSkuItem? @relation(fields: [intakeSkuItemId], references: [id], onDelete: SetNull)
|
||
modelVersionId String?
|
||
modelVersion IntakeProductModelVersion? @relation(fields: [modelVersionId], references: [id], onDelete: SetNull)
|
||
materialVersionId String?
|
||
materialVersion IntakeSkuAiMaterialVersion? @relation(fields: [materialVersionId], references: [id], onDelete: SetNull)
|
||
outputMode IntakeAiRenderOutputMode?
|
||
batchId String?
|
||
viewName String?
|
||
viewIndex Int?
|
||
storageClass IntakeAiRenderStorageClass @default(TEMPORARY)
|
||
|
||
prompt String @db.Text
|
||
negativePrompt String? @db.Text
|
||
params Json
|
||
|
||
referenceImageUrl String?
|
||
resultImages Json @default("[]")
|
||
|
||
workspaceSnapshot Json
|
||
assetSnapshot Json
|
||
|
||
externalRequestId String?
|
||
attempts Json?
|
||
status AssistedAssemblyAiRenderStatus @default(PENDING)
|
||
errorMessage String? @db.Text
|
||
durationMs Int?
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([createdBy, createdAt])
|
||
@@index([workbenchSpuId, createdAt])
|
||
@@index([workbenchSkuId, createdAt])
|
||
@@index([intakeSkuItemId, createdAt])
|
||
@@index([intakeSkuItemId, outputMode, createdAt])
|
||
@@index([modelVersionId])
|
||
@@index([materialVersionId])
|
||
@@index([storageClass, createdAt])
|
||
@@index([status, createdAt])
|
||
}
|
||
|
||
model AiRenderWorkbenchSpu {
|
||
id String @id @default(cuid())
|
||
|
||
name String
|
||
description String @default("") @db.Text
|
||
sortOrder Int @default(0)
|
||
|
||
createdBy String
|
||
creator User @relation("AiRenderWorkbenchSpuCreator", fields: [createdBy], references: [id], onDelete: Cascade)
|
||
updatedBy String?
|
||
|
||
skus AiRenderWorkbenchSku[]
|
||
renders AssistedAssemblyAiRender[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([createdBy])
|
||
@@index([sortOrder, createdAt])
|
||
}
|
||
|
||
model AiRenderWorkbenchSku {
|
||
id String @id @default(cuid())
|
||
|
||
spuId String
|
||
spu AiRenderWorkbenchSpu @relation(fields: [spuId], references: [id], onDelete: Cascade)
|
||
|
||
name String
|
||
modelUrl String @default("") @db.Text
|
||
thumbnailUrl String? @db.Text
|
||
note String? @db.Text
|
||
materialColor String @default("#d8c6ad")
|
||
materialTextureUrl String? @db.Text
|
||
nodeMaterials Json?
|
||
sortOrder Int @default(0)
|
||
|
||
createdBy String
|
||
creator User @relation("AiRenderWorkbenchSkuCreator", fields: [createdBy], references: [id], onDelete: Cascade)
|
||
updatedBy String?
|
||
|
||
renders AssistedAssemblyAiRender[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([spuId, sortOrder, createdAt])
|
||
@@index([createdBy])
|
||
}
|
||
|
||
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
|
||
|
||
basicInfo PreSkuBasicInfo?
|
||
packagingPlan PreSkuPackagingPlan?
|
||
formalizedSku ProductSKU? @relation("PreSkuFormalizedSku")
|
||
locationBindings PreSkuLocationBinding[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([draftGroupId, sequence])
|
||
@@index([draftGroupId])
|
||
@@index([status])
|
||
}
|
||
|
||
model PreSkuBasicInfo {
|
||
id String @id @default(cuid())
|
||
preSkuId String @unique
|
||
preSku PreSku @relation(fields: [preSkuId], references: [id], onDelete: Cascade)
|
||
|
||
model String?
|
||
formalSkuCode String?
|
||
barcode String?
|
||
size String?
|
||
color String?
|
||
material String?
|
||
specName String?
|
||
versionNo String?
|
||
setCount String?
|
||
attributeCode String?
|
||
drawingId String?
|
||
installMethod String?
|
||
|
||
manualUpdatedBy String?
|
||
manualUpdatedByName String?
|
||
manualUpdatedAt DateTime?
|
||
source String @default("DRAFT_GROUP")
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([model])
|
||
@@index([formalSkuCode])
|
||
@@index([barcode])
|
||
@@index([specName, versionNo])
|
||
}
|
||
|
||
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])
|
||
}
|
||
|
||
model ProductSkuQualityInspection {
|
||
id String @id @default(cuid())
|
||
|
||
skuId String
|
||
sku ProductSKU @relation("ProductSKUQualityInspections", fields: [skuId], references: [id], onDelete: Cascade)
|
||
|
||
recordKey String @unique
|
||
sourceType String @default("WAREHOUSE")
|
||
sourceProductId String?
|
||
sourceProductSpu String?
|
||
sourceSkuItemId String?
|
||
|
||
batchNo String
|
||
inspectedAt DateTime?
|
||
inspectorName String?
|
||
conclusion String
|
||
detail String? @db.Text
|
||
inspectedQuantity Int?
|
||
qualifiedQuantity Int?
|
||
qualifiedRate Float?
|
||
metrics Json?
|
||
attachments Json?
|
||
status String @default("DRAFT")
|
||
|
||
createdBy String?
|
||
updatedBy String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([skuId])
|
||
@@index([sourceType])
|
||
@@index([batchNo])
|
||
@@index([inspectedAt])
|
||
@@index([createdAt])
|
||
}
|
||
|
||
// ============================================
|
||
// 用户管理
|
||
// ============================================
|
||
|
||
// 角色枚举
|
||
enum Role {
|
||
SUPER_ADMIN // 超级管理员
|
||
ADMIN // 管理员
|
||
SUPERVISOR // 主管(按部门/小组管理锚点区分范围)
|
||
USER // 普通用户
|
||
}
|
||
|
||
// 用户状态枚举
|
||
enum UserStatus {
|
||
ACTIVE // 在职
|
||
PENDING // 待激活
|
||
INACTIVE // 离职
|
||
SUSPENDED // 暂停
|
||
}
|
||
|
||
enum EmployeeSourceStatus {
|
||
EMPLOYED
|
||
DIMISSIONED
|
||
}
|
||
|
||
enum BusinessAccountType {
|
||
GENERAL
|
||
CUSTOMER_SERVICE
|
||
DESIGN
|
||
PLANNING
|
||
STRATEGY
|
||
PURCHASING
|
||
OPERATIONS
|
||
WAREHOUSE
|
||
MANAGEMENT
|
||
SUPPLIER
|
||
EXTERNAL_INTAKE
|
||
}
|
||
|
||
enum BomCostingCaseStatus {
|
||
DRAFT
|
||
QUEUED
|
||
ANALYZING
|
||
READY
|
||
FAILED
|
||
}
|
||
|
||
enum BomCostingSourceStatus {
|
||
PENDING
|
||
FETCHING
|
||
ARCHIVING
|
||
READY
|
||
FAILED
|
||
}
|
||
|
||
enum BomCostingSourceAssetKind {
|
||
MAIN
|
||
DETAIL
|
||
SKU
|
||
}
|
||
|
||
enum BomCostingSourceAssetStatus {
|
||
PENDING
|
||
STORED
|
||
FAILED
|
||
}
|
||
|
||
enum BomCostingFactAnalysisStatus {
|
||
PENDING
|
||
PROCESSING
|
||
READY
|
||
FAILED
|
||
}
|
||
|
||
enum BomCostingFactAnalysisOrigin {
|
||
INITIAL
|
||
SELECTED_REANALYSIS
|
||
FULL_REANALYSIS
|
||
}
|
||
|
||
enum BomCostingAnalysisRunMode {
|
||
INITIAL
|
||
REUSE_FACTS
|
||
REANALYZE_SELECTED
|
||
REANALYZE_ALL
|
||
}
|
||
|
||
enum BomCostingAnalysisRunStatus {
|
||
PENDING
|
||
PROCESSING
|
||
READY
|
||
FAILED
|
||
}
|
||
|
||
enum BomCostingLineCategory {
|
||
BOARD
|
||
STEEL
|
||
ACCESSORY
|
||
PACKAGING
|
||
PROCESSING
|
||
LABOR
|
||
}
|
||
|
||
enum BomCostingMatchStatus {
|
||
UNMATCHED
|
||
MATCHED
|
||
NEEDS_CONFIRMATION
|
||
}
|
||
|
||
enum BomCostingReviewInputSource {
|
||
MANUAL
|
||
LIBRARY_MATCH
|
||
}
|
||
|
||
enum BomCostingReferenceSyncStatus {
|
||
RUNNING
|
||
SUCCEEDED
|
||
FAILED
|
||
}
|
||
|
||
enum BomCostingScenarioKind {
|
||
SAME_MATERIAL
|
||
MATERIAL_SUBSTITUTION
|
||
}
|
||
|
||
enum BomCostingScenarioStatus {
|
||
FINAL
|
||
PROVISIONAL
|
||
INSUFFICIENT_DATA
|
||
STALE
|
||
}
|
||
|
||
enum ApprovalWorkflowStatus {
|
||
DRAFT
|
||
PENDING
|
||
IN_PROGRESS
|
||
COMPLETED
|
||
REJECTED
|
||
ARCHIVED
|
||
}
|
||
|
||
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 AssistedAssemblyAiRenderStatus {
|
||
PENDING
|
||
SUCCEEDED
|
||
FAILED
|
||
}
|
||
|
||
enum IntakeAiRenderOutputMode {
|
||
WHITE_BACKGROUND
|
||
EIGHT_VIEW
|
||
SCENE
|
||
}
|
||
|
||
enum IntakeAiRenderStorageClass {
|
||
TEMPORARY
|
||
PERMANENT
|
||
}
|
||
|
||
enum AssetSkuSyncEventStatus {
|
||
PENDING
|
||
PROCESSING
|
||
COMPLETED
|
||
FAILED
|
||
}
|
||
|
||
enum ProductSkuAssetSourceStatus {
|
||
ACTIVE
|
||
DELETED
|
||
MISSING
|
||
}
|
||
|
||
enum IntakeNonIntegratedSkuDataQualityStatus {
|
||
READY
|
||
INCOMPLETE
|
||
CONFLICT
|
||
}
|
||
|
||
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?
|
||
supplierCode String?
|
||
supplierName String?
|
||
|
||
// 角色与权限
|
||
role Role @default(USER)
|
||
businessRoles BusinessAccountType[] @default([])
|
||
|
||
// 组织架构
|
||
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")
|
||
deletedAssetFamilies AssetFamily[] @relation("AssetFamilyDeleter")
|
||
createdAssetVariants AssetVariant[] @relation("AssetVariantCreator")
|
||
submittedAssetVariants AssetVariant[] @relation("AssetVariantSubmitter")
|
||
reviewedAssetVariants AssetVariant[] @relation("AssetVariantReviewer")
|
||
rejectedAssetVariants AssetVariant[] @relation("AssetVariantRejecter")
|
||
modifiedAssetVariants AssetVariant[] @relation("AssetVariantLastModifier")
|
||
deletedAssetVariants AssetVariant[] @relation("AssetVariantDeleter")
|
||
createdAssetSeries AssetSeries[] @relation("AssetSeriesCreator")
|
||
createdDraftGroups DraftGroup[] @relation("DraftGroupCreator")
|
||
createdAiRenderWorkbenchSpus AiRenderWorkbenchSpu[] @relation("AiRenderWorkbenchSpuCreator")
|
||
createdAiRenderWorkbenchSkus AiRenderWorkbenchSku[] @relation("AiRenderWorkbenchSkuCreator")
|
||
assistedAssemblyAiRenders AssistedAssemblyAiRender[] @relation("AssistedAssemblyAiRenderCreator")
|
||
createdIntakeProductModelAssets IntakeProductModelAsset[] @relation("IntakeProductModelAssetCreator")
|
||
createdIntakeProductModelVersions IntakeProductModelVersion[] @relation("IntakeProductModelVersionCreator")
|
||
createdIntakeSkuAiProfiles IntakeSkuAiProfile[] @relation("IntakeSkuAiProfileCreator")
|
||
createdIntakeSkuAiMaterialVersions IntakeSkuAiMaterialVersion[] @relation("IntakeSkuAiMaterialVersionCreator")
|
||
collaboratedProducts ProductCollaborator[] @relation("ProductCollaboratorUser")
|
||
addedProductCollaborators ProductCollaborator[] @relation("ProductCollaboratorAddedBy")
|
||
feedbacks Feedback[] @relation("FeedbackSubmitter")
|
||
createdBomCostingCases BomCostingCase[] @relation("BomCostingCaseCreator")
|
||
createdBomCostingAnalysisRuns BomCostingAnalysisRun[] @relation("BomCostingAnalysisRunCreator")
|
||
createdBomCostingReviewInputs BomCostingReviewInput[] @relation("BomCostingReviewInputCreator")
|
||
assignedFeedbacks Feedback[] @relation("FeedbackAssignee")
|
||
createdApprovalWorkflows ApprovalWorkflow[] @relation("ApprovalWorkflowCreator")
|
||
approvalWorkflowChanges ApprovalWorkflowFieldChange[]
|
||
barcodePrintImportRecords BarcodePrintImportRecord[] @relation("BarcodePrintImportRecordCreator")
|
||
workRecordEntries WorkRecord[] @relation("WorkRecordDesigner")
|
||
|
||
// 操作日志
|
||
activityLogs ActivityLog[]
|
||
operationLogs OperationLog[] @relation("OperationLogActor")
|
||
createdReleaseNotes ReleaseNote[] @relation("ReleaseNoteCreator")
|
||
releaseNoteAcknowledgements ReleaseNoteAcknowledgement[] @relation("ReleaseNoteAcknowledgementReader")
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
lastLoginAt DateTime?
|
||
|
||
@@index([email])
|
||
@@index([name])
|
||
@@index([departmentId])
|
||
@@index([teamId])
|
||
@@index([status])
|
||
@@index([loginSource])
|
||
@@index([employeeDirectoryId])
|
||
@@index([supplierCode])
|
||
}
|
||
|
||
model EmployeeLookupCache {
|
||
id String @id @default(cuid())
|
||
|
||
cacheKey String @unique
|
||
keyType String
|
||
lookupValue String
|
||
result Json?
|
||
sourceStatus EmployeeSourceStatus?
|
||
expiresAt DateTime
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([keyType])
|
||
@@index([expiresAt])
|
||
}
|
||
|
||
// 部门
|
||
model Department {
|
||
id String @id @default(cuid())
|
||
|
||
name String @unique
|
||
fullPath String // 完整路径,如 "设计部-3D组-建模小组"
|
||
description String?
|
||
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)
|
||
isActive Boolean @default(true)
|
||
|
||
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)
|
||
isActive Boolean @default(true)
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([departmentId, name])
|
||
@@index([departmentId])
|
||
}
|
||
|
||
model ApprovalWorkflow {
|
||
id String @id @default(cuid())
|
||
|
||
approvalNumber String @unique
|
||
title String
|
||
status ApprovalWorkflowStatus @default(DRAFT)
|
||
productId String
|
||
product IntakeNonIntegratedProduct @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||
accessKey String @unique
|
||
businessRoles BusinessAccountType[] @default([])
|
||
participantUserIds String[] @default([])
|
||
initiatorId String
|
||
initiator User @relation("ApprovalWorkflowCreator", fields: [initiatorId], references: [id])
|
||
lastSyncedAt DateTime?
|
||
fieldMatrix Json?
|
||
summary Json?
|
||
metadata Json?
|
||
|
||
fieldChanges ApprovalWorkflowFieldChange[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([productId, createdAt])
|
||
@@index([status, updatedAt])
|
||
@@index([initiatorId])
|
||
}
|
||
|
||
model ApprovalWorkflowFieldChange {
|
||
id String @id @default(cuid())
|
||
|
||
workflowId String
|
||
workflow ApprovalWorkflow @relation(fields: [workflowId], references: [id], onDelete: Cascade)
|
||
userId String
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
fieldKey String
|
||
oldValue Json?
|
||
newValue Json?
|
||
source String
|
||
sourcePage String?
|
||
fromDingtalkPage Boolean @default(false)
|
||
metadata Json?
|
||
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([workflowId, createdAt])
|
||
@@index([userId, createdAt])
|
||
@@index([fieldKey])
|
||
}
|
||
|
||
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
|
||
}
|
||
|
||
enum OperationLogAction {
|
||
INTAKE_PRODUCT_CREATE
|
||
INTAKE_PRODUCT_WRITE
|
||
INTAKE_PRODUCT_DELETE
|
||
INTAKE_FILE_UPLOAD
|
||
INTAKE_WORKSPACE_CONFIRMATION
|
||
JUSHUITAN_PRODUCT_PUSH
|
||
JUSHUITAN_VIRTUAL_INVENTORY_PUSH
|
||
}
|
||
|
||
enum OperationLogStatus {
|
||
SUCCESS
|
||
FAILED
|
||
RETRY
|
||
}
|
||
|
||
enum OperationLogSource {
|
||
USER
|
||
SYSTEM
|
||
}
|
||
|
||
enum ReleaseNoteStatus {
|
||
DRAFT
|
||
PUBLISHED
|
||
ARCHIVED
|
||
}
|
||
|
||
model OperationLog {
|
||
id String @id @default(cuid())
|
||
|
||
action OperationLogAction
|
||
status OperationLogStatus
|
||
source OperationLogSource @default(USER)
|
||
|
||
actorId String?
|
||
actor User? @relation("OperationLogActor", fields: [actorId], references: [id], onDelete: SetNull)
|
||
actorName String
|
||
|
||
productId String?
|
||
productSpu String?
|
||
skuId String?
|
||
skuCode String?
|
||
workspaceCategory String?
|
||
|
||
errorCode String?
|
||
message String @db.Text
|
||
requestId String?
|
||
details Json?
|
||
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([status, createdAt])
|
||
@@index([action, createdAt])
|
||
@@index([actorId, createdAt])
|
||
@@index([productSpu, createdAt])
|
||
@@index([productId, createdAt])
|
||
}
|
||
|
||
model ReleaseNote {
|
||
id String @id @default(cuid())
|
||
|
||
title String
|
||
summary String @db.Text
|
||
content Json
|
||
|
||
// Empty means the release note is visible to every active account.
|
||
targetBusinessRoles BusinessAccountType[] @default([])
|
||
status ReleaseNoteStatus @default(DRAFT)
|
||
publishedAt DateTime?
|
||
|
||
createdById String?
|
||
createdBy User? @relation("ReleaseNoteCreator", fields: [createdById], references: [id], onDelete: SetNull)
|
||
|
||
acknowledgements ReleaseNoteAcknowledgement[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([status, publishedAt])
|
||
@@index([createdById])
|
||
}
|
||
|
||
model ReleaseNoteAcknowledgement {
|
||
id String @id @default(cuid())
|
||
|
||
releaseNoteId String
|
||
releaseNote ReleaseNote @relation(fields: [releaseNoteId], references: [id], onDelete: Cascade)
|
||
readerId String
|
||
reader User @relation("ReleaseNoteAcknowledgementReader", fields: [readerId], references: [id], onDelete: Cascade)
|
||
readAt DateTime @default(now())
|
||
|
||
@@unique([releaseNoteId, readerId])
|
||
@@index([readerId, readAt])
|
||
}
|
||
|
||
// 产品部工作记录与月度归档
|
||
model WorkRecordScoreRule {
|
||
id String @id @default(cuid())
|
||
primary String
|
||
secondary String @default("")
|
||
unitScore Decimal? @db.Decimal(12, 4)
|
||
sortOrder Int @default(0)
|
||
isActive Boolean @default(true)
|
||
|
||
records WorkRecord[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([primary, sortOrder])
|
||
@@index([isActive, sortOrder])
|
||
}
|
||
|
||
model WorkRecord {
|
||
id String @id @default(cuid())
|
||
|
||
workDate DateTime @db.Date
|
||
designerId String
|
||
designerName String
|
||
designer User @relation("WorkRecordDesigner", fields: [designerId], references: [id], onDelete: Restrict)
|
||
|
||
primary String
|
||
secondary String @default("")
|
||
scoreRuleId String?
|
||
scoreRule WorkRecordScoreRule? @relation(fields: [scoreRuleId], references: [id], onDelete: SetNull)
|
||
|
||
approvalOrTask String @default("") @db.Text
|
||
spu String @default("")
|
||
quantity Decimal @default(1) @db.Decimal(12, 2)
|
||
unitScore Decimal? @db.Decimal(12, 4)
|
||
taskTotal Decimal? @db.Decimal(14, 4)
|
||
fileStoragePath String @default("") @db.Text
|
||
|
||
createdById String
|
||
updatedById String
|
||
deletedAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([workDate, deletedAt])
|
||
@@index([designerId, workDate])
|
||
@@index([primary, workDate])
|
||
@@index([scoreRuleId])
|
||
}
|
||
|
||
model WorkRecordMonthlyArchive {
|
||
id String @id @default(cuid())
|
||
monthKey String @unique
|
||
periodStart DateTime @db.Date
|
||
periodEnd DateTime @db.Date
|
||
fileName String
|
||
summary Json
|
||
records Json
|
||
recordCount Int
|
||
generatedAt DateTime @default(now())
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([periodStart])
|
||
}
|
||
|
||
// 操作日志
|
||
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 IntakeNodePermissionOverride {
|
||
id String @id @default(cuid())
|
||
|
||
targetKey String
|
||
subjectKey String
|
||
action String
|
||
enabled Boolean @default(true)
|
||
|
||
createdBy String?
|
||
updatedBy String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([targetKey, subjectKey, action])
|
||
@@index([targetKey])
|
||
@@index([subjectKey])
|
||
}
|
||
|
||
model SupplyChainPermissionOverride {
|
||
id String @id @default(cuid())
|
||
|
||
targetKey String
|
||
subjectKey String
|
||
action String
|
||
enabled Boolean @default(true)
|
||
|
||
createdBy String?
|
||
updatedBy String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([targetKey, subjectKey, action])
|
||
@@index([targetKey])
|
||
@@index([subjectKey])
|
||
}
|
||
|
||
model NewProductTerminationFeedback {
|
||
id String @id @default(cuid())
|
||
|
||
openingCode String @unique
|
||
reasonCategory String
|
||
reason String @db.Text
|
||
feedbackById String?
|
||
feedbackByName String
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([updatedAt])
|
||
}
|
||
|
||
// ============================================
|
||
// 五金件库(标准五金件复用系统)
|
||
// ============================================
|
||
|
||
// 五金件分类
|
||
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])
|
||
}
|
||
|
||
// ============================================
|
||
// 系统配置(通用键值存储)
|
||
// ============================================
|
||
|
||
model SystemConfig {
|
||
key String @id
|
||
value Json
|
||
description String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
}
|
||
|
||
// ============================================
|
||
// AI 服务商配置(多份配置 + 激活切换)
|
||
// ============================================
|
||
|
||
model ProviderConfig {
|
||
id String @id @default(cuid())
|
||
serviceType String // ai-render | prompt-optimizer
|
||
name String // 用户自定义名称,如 "生产环境"、"测试 Key"
|
||
baseUrl String
|
||
apiKey String
|
||
model String?
|
||
extra Json? // 扩展字段
|
||
isActive Boolean @default(false)
|
||
sortOrder Int @default(0)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([serviceType])
|
||
@@index([serviceType, isActive])
|
||
}
|
||
|
||
// 安装演示工作台配置(按图纸存)
|
||
model InstallationDemoConfig {
|
||
id String @id @default(cuid())
|
||
drawingId String @unique
|
||
parts Json
|
||
steps Json
|
||
numbering Json?
|
||
toolIds Json?
|
||
uploadedModel Json?
|
||
explodeTemplate Json?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
}
|
||
|
||
// Global installation toolbox. Drawings only keep references to tool IDs.
|
||
model InstallationDemoTool {
|
||
id String @id @default(cuid())
|
||
toolCode String @unique
|
||
name String
|
||
modelUrl String
|
||
fileName String
|
||
mountOffset Json?
|
||
mountRotation Json?
|
||
motionType String @default("spin")
|
||
motionPivot Json?
|
||
motionAxis Json?
|
||
spinTurns Float @default(3)
|
||
strikeDistanceRatio Float @default(0.2)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
}
|