- 明细新增图片、产品标签、含大单近30日日销(条码/SKU 数值快照) - 表头漏斗筛选(模糊/多选/数值比较/日期区间)、批量搜索、筛选设置 - 列设置拖动排序、快捷查询保存与套用 - 视图统计:日/周/月趋势、部门/需求人/原因/状态分布、饼图与明细下钻 - 通知中心:停产确认/待走流程/需求驳回通知、待办快照同步、钉钉全屏链接 - 新增迁移与历史数据回填脚本
5271 lines
186 KiB
Plaintext
5271 lines
186 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
|
||
sampleStandardDays Float?
|
||
oaSampleActualDays Float?
|
||
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[]
|
||
suppliers SampleProgressSupplier[]
|
||
|
||
@@index([status])
|
||
@@index([brand])
|
||
@@index([sampleDueDate])
|
||
@@index([actualSampleCompletedAt])
|
||
@@index([oaReachedAt])
|
||
@@index([designer])
|
||
}
|
||
|
||
model SampleProgressSupplier {
|
||
id String @id @default(cuid())
|
||
itemId String
|
||
item SampleProgressItem @relation(fields: [itemId], references: [id], onDelete: Cascade)
|
||
sortOrder Int @default(0)
|
||
supplier String
|
||
supplierConfirmedAt DateTime?
|
||
sampleNotifiedAt DateTime?
|
||
sampleDueDate DateTime @db.Date
|
||
courierTrackingNumber String?
|
||
status String
|
||
requestedCost Decimal? @db.Decimal(12, 2)
|
||
actualCost Decimal? @db.Decimal(12, 2)
|
||
progressDescription String?
|
||
actualSampleCompletedAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([itemId, sortOrder])
|
||
@@index([itemId])
|
||
@@index([status])
|
||
@@index([sampleDueDate])
|
||
}
|
||
|
||
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 FinishedProductProgressItem {
|
||
id String @id @default(cuid())
|
||
sourceOpeningCode String?
|
||
sourceProductId String?
|
||
sourceSkuId String?
|
||
sourceGroupKey String?
|
||
sourceSupplierCode String?
|
||
sourceImportedAt DateTime?
|
||
supplierCode String @default("")
|
||
contractDateNumber String @default("")
|
||
orderPlacedDate DateTime? @db.Date
|
||
orderSequenceNo String @default("")
|
||
contractNumber String
|
||
followUpOwner String @default("")
|
||
imageUrl String? @db.Text
|
||
itemNumber String @default("")
|
||
barcode String?
|
||
productName String @default("")
|
||
supplier String @default("")
|
||
brand String @default("")
|
||
orderedQuantity Int @default(0)
|
||
inTransitQuantity Int @default(0)
|
||
firstDueDate DateTime? @db.Date
|
||
orderDueDate DateTime? @db.Date
|
||
realtimeDate DateTime? @db.Date
|
||
deliveryQuantity Int @default(0)
|
||
currentNode String @default("")
|
||
completedAt DateTime?
|
||
remarks String @default("") @db.Text
|
||
createdById String?
|
||
createdByName String @default("")
|
||
updatedById String?
|
||
updatedByName String @default("")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
changeLogs FinishedProductProgressChangeLog[]
|
||
deliveryRecords FinishedProductDeliveryRecord[]
|
||
|
||
@@index([contractDateNumber])
|
||
@@unique([sourceOpeningCode, sourceProductId, sourceSkuId], map: "FinishedProductProgressItem_factory_source_key")
|
||
@@index([sourceGroupKey])
|
||
@@index([sourceImportedAt])
|
||
@@index([orderSequenceNo])
|
||
@@index([itemNumber])
|
||
@@index([supplier])
|
||
@@index([brand])
|
||
@@index([currentNode])
|
||
@@index([orderDueDate])
|
||
@@index([updatedAt])
|
||
}
|
||
|
||
model FinishedProductProgressChangeLog {
|
||
id String @id @default(cuid())
|
||
itemId String
|
||
item FinishedProductProgressItem @relation(fields: [itemId], references: [id], onDelete: Cascade)
|
||
operation String
|
||
actorId String?
|
||
actorName String
|
||
changes Json
|
||
changeReason String?
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([itemId, createdAt])
|
||
@@index([createdAt])
|
||
}
|
||
|
||
model FinishedProductDeliveryRecord {
|
||
id String @id @default(cuid())
|
||
itemId String
|
||
item FinishedProductProgressItem @relation(fields: [itemId], references: [id], onDelete: Cascade)
|
||
deliveryDate DateTime @db.Date
|
||
quantity Int
|
||
status String @default("ACTIVE")
|
||
createdById String?
|
||
createdByName String @default("")
|
||
updatedById String?
|
||
updatedByName String @default("")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([itemId, status, deliveryDate])
|
||
@@index([itemId, createdAt])
|
||
}
|
||
|
||
model SupplierRecommendedProduct {
|
||
id String @id @default(cuid())
|
||
recommendationDate DateTime? @db.Date
|
||
category String @default("")
|
||
supplier String @default("")
|
||
purchaser String @default("")
|
||
productCode String @default("")
|
||
imageUrl String? @db.Text
|
||
sampleRequest String @default("")
|
||
dimensions String @default("")
|
||
price String @default("")
|
||
material String @default("")
|
||
boardGrade String @default("")
|
||
weight String @default("")
|
||
orderQuantity Int @default(0)
|
||
productionCycle String @default("")
|
||
shippingMode String @default("")
|
||
dropShippingFee String @default("")
|
||
selectionStatus String @default("UNSELECTED")
|
||
selectedById String?
|
||
selectedByName String @default("")
|
||
selectedAt DateTime?
|
||
createdById String?
|
||
createdByName String @default("")
|
||
updatedById String?
|
||
updatedByName String @default("")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
changeLogs SupplierRecommendedProductChangeLog[]
|
||
|
||
@@index([recommendationDate])
|
||
@@index([category])
|
||
@@index([supplier])
|
||
@@index([purchaser])
|
||
@@index([productCode])
|
||
@@index([shippingMode])
|
||
@@index([selectionStatus])
|
||
}
|
||
|
||
model SupplierRecommendedProductChangeLog {
|
||
id String @id @default(cuid())
|
||
itemId String
|
||
item SupplierRecommendedProduct @relation(fields: [itemId], references: [id], onDelete: Cascade)
|
||
operation String
|
||
actorId String?
|
||
actorName String
|
||
changes Json
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([itemId, createdAt])
|
||
@@index([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[]
|
||
photoUploadGrants IntakeProductPhotoUploadGrant[]
|
||
|
||
@@index([spu])
|
||
@@index([spuBase])
|
||
@@index([createdBy])
|
||
@@index([departmentId])
|
||
@@index([teamId])
|
||
@@index([visibility])
|
||
@@index([deletedAt, createdAt])
|
||
}
|
||
|
||
model IntakeProductPhotoUploadGrant {
|
||
id String @id
|
||
productId String
|
||
userId String
|
||
grantedBy String
|
||
createdAt DateTime @default(now())
|
||
product IntakeNonIntegratedProduct @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||
user User @relation("IntakePhotoUploadRecipient", fields: [userId], references: [id], onDelete: Cascade)
|
||
grantor User @relation("IntakePhotoUploadGrantor", fields: [grantedBy], references: [id], onDelete: Restrict)
|
||
|
||
@@unique([productId, userId])
|
||
@@index([userId])
|
||
}
|
||
|
||
model BarcodePrintImportRecord {
|
||
id String @id @default(cuid())
|
||
kind String
|
||
// System-created barcode rows point to their SKU source. Imported rows keep
|
||
// this null, so identical Excel rows remain independently valid.
|
||
sourceSkuItemId String? @unique @map("sourcePricingRowId")
|
||
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("")
|
||
productionType 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("")
|
||
productAttribute String @default("")
|
||
productTag String @default("")
|
||
installDocId String @default("")
|
||
installMethod 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("")
|
||
stableLeadTime String @default("")
|
||
palletSpec String @default("")
|
||
backupSupplier String @default("")
|
||
dailySales String @default("")
|
||
sales3d String @default("")
|
||
sales7d String @default("")
|
||
sales15d String @default("")
|
||
sales30d String @default("0.00")
|
||
sales30dWithBig String @default("0.00")
|
||
stockJianshang String @default("")
|
||
stockClearance String @default("")
|
||
stockFactory String @default("")
|
||
purchasePrice String @default("")
|
||
costPrice String @default("")
|
||
costPriceSecondary String @default("")
|
||
sabcRating String @default("") @db.VarChar(1)
|
||
sabcUpdatedAt DateTime?
|
||
fieldSourceMap Json @default("{}")
|
||
sources SupplyChainProductArchiveSource[]
|
||
imageEmbedding ProductArchiveImageEmbedding?
|
||
imageCache SupplyChainProductArchiveImageCache?
|
||
salesPerformance SupplyChainProductArchiveSalesPerformance?
|
||
sabcResults SupplyChainProductArchiveSabcResult[]
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([spu])
|
||
@@index([sku])
|
||
@@index([businessCreatedAt])
|
||
@@index([categoryL1])
|
||
@@index([categoryL2])
|
||
@@index([productStatus])
|
||
@@index([inOutWarehouse])
|
||
@@index([stockJianshang])
|
||
@@index([firstSourceCode, createdAt])
|
||
}
|
||
|
||
// 产品全量档案日销同步状态:保存来源指纹和固定每日同步计划。
|
||
model ProductArchiveMarketingSalesState {
|
||
id String @id
|
||
state Json
|
||
snapshot Json
|
||
updatedAt DateTime @updatedAt
|
||
}
|
||
|
||
model ProductArchiveMarketingSalesCheck {
|
||
id String @id @default(cuid())
|
||
checkedAt DateTime @default(now())
|
||
status String
|
||
fingerprint String @default("")
|
||
sourceDate String @default("")
|
||
rowCount Int @default(0)
|
||
skuCount Int @default(0)
|
||
changedRows Int @default(0)
|
||
errorCode String @default("")
|
||
|
||
@@index([checkedAt])
|
||
}
|
||
|
||
// 月度 SABC 核算快照。主表只保留当前标签,历史月份和计算过程在这里追溯。
|
||
model SupplyChainProductArchiveSabcResult {
|
||
id String @id @default(cuid())
|
||
archiveId String
|
||
archive SupplyChainProductArchive @relation(fields: [archiveId], references: [id], onDelete: Cascade)
|
||
barcode String @db.VarChar(100)
|
||
periodStart DateTime @db.Date
|
||
periodEnd DateTime @db.Date
|
||
eligible Boolean
|
||
sabcRating String @default("") @db.VarChar(1)
|
||
category String @default("")
|
||
performanceScale String @default("")
|
||
salesScale String @default("")
|
||
finalScale String @default("")
|
||
profitLabel String @default("")
|
||
averageDailySales Decimal? @db.Decimal(24, 8)
|
||
feeRatio Decimal? @db.Decimal(20, 8)
|
||
grossMargin Decimal? @db.Decimal(20, 8)
|
||
shipAmount Decimal @db.Decimal(24, 4)
|
||
realPerformance Decimal @db.Decimal(24, 4)
|
||
shipQty BigInt
|
||
grossProfit Decimal @db.Decimal(24, 4)
|
||
afterSaleAmount Decimal @db.Decimal(24, 4)
|
||
status String @default("CALCULATED") @db.VarChar(24)
|
||
reason String @default("") @db.Text
|
||
// 生成时的整行明细快照:核算结果一经生成即冻结,不再随档案/销售状态变化。
|
||
snapshot Json?
|
||
calculatedAt DateTime @default(now())
|
||
|
||
@@unique([barcode, periodStart, periodEnd])
|
||
@@index([periodStart, periodEnd])
|
||
@@index([sabcRating, periodEnd])
|
||
@@index([archiveId, periodEnd])
|
||
}
|
||
|
||
model SupplyChainProductArchiveSabcRun {
|
||
id String @id @default(cuid())
|
||
periodStart DateTime @db.Date
|
||
periodEnd DateTime @db.Date
|
||
mode String @db.VarChar(16)
|
||
status String @db.VarChar(24)
|
||
sourceRows Int @default(0)
|
||
eligibleRows Int @default(0)
|
||
taggedRows Int @default(0)
|
||
comparedRows Int @default(0)
|
||
mismatchRows Int @default(0)
|
||
errorMessage String? @db.Text
|
||
startedAt DateTime @default(now())
|
||
completedAt DateTime?
|
||
|
||
@@unique([periodStart, periodEnd, mode])
|
||
@@index([status, startedAt])
|
||
}
|
||
|
||
// 销售宽表按条码汇总的每日快照。
|
||
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])
|
||
}
|
||
|
||
// Source sales facts retained at barcode/day grain for period-aware archive views.
|
||
// This table intentionally has no archive FK so unmatched upstream barcodes are
|
||
// retained until the product archive catches up.
|
||
model SupplyChainProductArchiveSalesPerformanceDaily {
|
||
barcode String @db.VarChar(100)
|
||
statDate DateTime @db.Date
|
||
// DF daily enrichment; empty/null until the source history is backfilled.
|
||
skuOperator String @default("")
|
||
unitCost Decimal? @db.Decimal(24, 4)
|
||
estimatedFreight Decimal? @db.Decimal(24, 4)
|
||
// Separate from shipAmount; NULL denotes history not yet refreshed.
|
||
salesAmount Decimal? @db.Decimal(24, 4)
|
||
shipAmount Decimal @db.Decimal(24, 4)
|
||
returnAmount Decimal @db.Decimal(24, 4)
|
||
realPerformance Decimal @db.Decimal(24, 4)
|
||
shipQty BigInt
|
||
returnQty BigInt
|
||
clearSalesCost Decimal @db.Decimal(24, 4)
|
||
sendFreight Decimal @db.Decimal(24, 4)
|
||
platformCommission Decimal @db.Decimal(24, 4)
|
||
xyCost Decimal @db.Decimal(24, 4)
|
||
afterSaleAmount Decimal @db.Decimal(24, 4)
|
||
pushFeeTotal Decimal @db.Decimal(24, 4)
|
||
marketingFeeTotal Decimal @db.Decimal(24, 4)
|
||
advertisementFeeTotal Decimal @db.Decimal(24, 4)
|
||
sharedWarehousingSum Decimal @db.Decimal(24, 4)
|
||
otherFee Decimal @db.Decimal(24, 4)
|
||
operatingFee Decimal @db.Decimal(24, 4)
|
||
sourceRowCount Int
|
||
syncedAt DateTime @default(now())
|
||
|
||
@@id([barcode, statDate])
|
||
@@index([statDate])
|
||
@@index([syncedAt])
|
||
}
|
||
|
||
// 1688 代发商品的采购寻源承接快照。
|
||
// 不建立业务外键:商品档案或账号变化后,仍需保留承接时的条码、人员和成本依据。
|
||
model Dropship1688SourcingTask {
|
||
id String @id @default(cuid())
|
||
sourceArchiveId String
|
||
sourceSpu String @default("")
|
||
sourceBarcode String @db.VarChar(100)
|
||
sourceCostPrice Decimal? @db.Decimal(24, 4)
|
||
sourceSkuOperator String @default("")
|
||
sourceDateFrom DateTime @db.Date
|
||
sourceDateTo DateTime @db.Date
|
||
purchaserUserId String
|
||
purchaserName String
|
||
supplierName String @default("")
|
||
quotedPriceTaxIncluded Decimal? @map("quoted_price_tax_included") @db.Decimal(24, 4)
|
||
invoiceTaxPoint Decimal? @db.Decimal(8, 4)
|
||
sourcingMode String @default("")
|
||
jianshangBarcode String @default("")
|
||
acceptedAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([sourceBarcode, purchaserUserId])
|
||
@@index([sourceArchiveId])
|
||
@@index([purchaserName])
|
||
@@index([sourceSpu])
|
||
@@index([sourceSkuOperator])
|
||
@@index([purchaserUserId, acceptedAt])
|
||
@@index([acceptedAt])
|
||
}
|
||
|
||
model SupplyChainProductArchiveSalesSyncRun {
|
||
id String @id @default(cuid())
|
||
startedAt DateTime @default(now())
|
||
completedAt DateTime?
|
||
status String @db.VarChar(24)
|
||
rangeStart DateTime @db.Date
|
||
rangeEnd DateTime @db.Date
|
||
sourceLatestDate DateTime? @db.Date
|
||
rowsRead Int @default(0)
|
||
rowsWritten Int @default(0)
|
||
unmatchedBarcodes Int @default(0)
|
||
errorMessage String? @db.Text
|
||
|
||
@@index([startedAt])
|
||
@@index([status, startedAt])
|
||
}
|
||
|
||
// Operations-maintained checklist per dropship (DF) SPU: invoice type,
|
||
// return address and assembly type (整装/拆装) are filled in on the 1688
|
||
// dropship issues page. Read-only columns (image, ops owner, drawing/video
|
||
// flags) always come live from the product archive, so only these fields
|
||
// live here.
|
||
model SupplyChainDropshipIssue {
|
||
id String @id @default(cuid())
|
||
spu String @unique @db.VarChar(120)
|
||
invoiceType String @default("") @db.VarChar(16)
|
||
returnAddress String @default("") @db.Text
|
||
assemblyType String @default("") @db.VarChar(16)
|
||
updatedByName String @default("")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([updatedAt])
|
||
}
|
||
|
||
// 图纸与视频导出来源:按条码保存当前有效的图纸/视频状态,支持月度增量覆盖。
|
||
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
|
||
categoryGroup String @default("")
|
||
scaleLabel String @default("")
|
||
performanceStart Decimal? @db.Decimal(14, 2)
|
||
performanceEnd Decimal? @db.Decimal(14, 2)
|
||
dailySalesStart Decimal? @db.Decimal(12, 2)
|
||
dailySalesEnd Decimal? @db.Decimal(12, 2)
|
||
status String @default("ACTIVE")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([categoryL1])
|
||
@@index([categoryGroup])
|
||
@@index([status])
|
||
}
|
||
|
||
model SupplyChainProductArchiveCategoryScaleRow {
|
||
id String @id @default(cuid())
|
||
categoryL1 String @default("")
|
||
categoryL2 String
|
||
categoryGroup String @default("")
|
||
scaleLabel String
|
||
performanceStart Decimal? @db.Decimal(14, 2)
|
||
performanceEnd Decimal? @db.Decimal(14, 2)
|
||
dailySalesStart Decimal? @db.Decimal(12, 2)
|
||
dailySalesEnd Decimal? @db.Decimal(12, 2)
|
||
sortOrder Int @default(0)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([categoryL2, scaleLabel])
|
||
@@index([categoryL2])
|
||
@@index([categoryGroup])
|
||
}
|
||
|
||
model SupplyChainProductArchiveWarehouseConfig {
|
||
id String @id @default(cuid())
|
||
warehouseName String @unique
|
||
warehouseOwnership String @default("")
|
||
warehouseHead String @default("")
|
||
storageType String
|
||
status String @default("ACTIVE")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([storageType])
|
||
@@index([status])
|
||
}
|
||
|
||
/// 仓库属性人工覆盖:聚水潭 remark2 异常时,在这里改成正确的展示值。
|
||
model SupplyChainProductArchiveWarehouseAttributeOverride {
|
||
warehouseName String @unique
|
||
attribute String @default("")
|
||
updatedById String?
|
||
updatedByName String @default("")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([updatedAt])
|
||
}
|
||
|
||
model DownloadTask {
|
||
id String @id @default(cuid())
|
||
userId String
|
||
userName String @default("")
|
||
type String
|
||
title String @default("")
|
||
paramsJson String @default("{}")
|
||
status String @default("pending")
|
||
progress Int @default(0)
|
||
fileName String @default("")
|
||
filePath String @default("")
|
||
fileSize Int @default(0)
|
||
rowCount Int @default(0)
|
||
error String @default("")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
startedAt DateTime?
|
||
finishedAt DateTime?
|
||
|
||
@@index([userId, createdAt(sort: Desc)])
|
||
@@index([status])
|
||
}
|
||
|
||
model SupplyChainManagementOwnerConfig {
|
||
id String @id @default(cuid())
|
||
sourceEmployeeId String? @unique
|
||
sourceEmployee EmployeeDirectory? @relation(fields: [sourceEmployeeId], references: [id], onDelete: Cascade)
|
||
operationOwner String @default("")
|
||
department String @default("")
|
||
secondaryDepartment String @default("")
|
||
profitCenter String @default("")
|
||
departmentOwner String @default("")
|
||
generalOwner String @default("")
|
||
manualOverride Boolean @default(false)
|
||
isHidden Boolean @default(false)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([isHidden, updatedAt])
|
||
}
|
||
|
||
/// 商品SABC打标「各部门数据分类」的可维护配置(品牌范围、负责人归属例外、数据权限例外、目标参数)。
|
||
model SupplyChainProductArchiveDepartmentSetting {
|
||
id String @id @default(cuid())
|
||
key String @unique @db.VarChar(64)
|
||
value Json
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
}
|
||
|
||
/// 商品SABC打标「各部门数据分类」月度快照:核算生成时落库,之后页面直接读结果。
|
||
model SupplyChainProductArchiveDepartmentSnapshot {
|
||
id String @id @default(cuid())
|
||
periodStart DateTime @db.Date
|
||
periodEnd DateTime @db.Date
|
||
payload Json
|
||
rowTotal Int @default(0)
|
||
generatedAt DateTime @default(now())
|
||
|
||
@@unique([periodStart, periodEnd])
|
||
@@index([generatedAt])
|
||
}
|
||
|
||
/// 简尚「替换款管理」换货规则:sku = 原编码(老),replaceSku = 替换编码(新)。
|
||
/// 商品SABC打标的「同款商品编码(业绩&费比合并计算)」与业绩合并都依赖这张表。
|
||
model SupplyChainProductArchiveReplacementRule {
|
||
id String @id
|
||
sourceId Int @unique
|
||
sku String
|
||
replaceSku String @default("")
|
||
replaceType Int @default(0)
|
||
isSwapping Int @default(0)
|
||
differentPoints String @default("")
|
||
creator String @default("")
|
||
sourceCreatedAt DateTime? @db.Timestamp(6)
|
||
syncedAt DateTime @default(now()) @db.Timestamp(6)
|
||
|
||
@@index([sku])
|
||
@@index([replaceSku])
|
||
|
||
}
|
||
|
||
model ProductionStopDemand {
|
||
id String @id @default(cuid())
|
||
demandNo String @unique
|
||
requesterId String
|
||
requesterName String
|
||
department String @default("")
|
||
reasonCategory String
|
||
reasonDetail String @default("") @db.Text
|
||
submittedAt DateTime @default(now())
|
||
items ProductionStopDemandItem[]
|
||
confirmationDeadline DateTime?
|
||
confirmationStatus String @default("PENDING")
|
||
confirmationAt DateTime?
|
||
confirmationReasons String @default("") @db.Text
|
||
confirmationPushedAt DateTime?
|
||
confirmationPushMessage String @default("") @db.Text
|
||
confirmationVotes ProductionStopDemandConfirmationVote[]
|
||
demandStatus String @default("PENDING_CONFIRMATION")
|
||
demandStatusAt DateTime?
|
||
demandStatusNote String @default("") @db.Text
|
||
batchNo String @default("")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([submittedAt])
|
||
@@index([requesterId, submittedAt])
|
||
@@index([confirmationStatus, confirmationDeadline])
|
||
@@index([demandStatus, updatedAt])
|
||
@@index([batchNo])
|
||
}
|
||
|
||
// 停产需求明细行(一条码一行):档案快照 + 替换信息 + 采购反馈。
|
||
|
||
model ProductionStopDemandItem {
|
||
id String @id @default(cuid())
|
||
demandId String
|
||
demand ProductionStopDemand @relation(fields: [demandId], references: [id], onDelete: Cascade)
|
||
barcode String
|
||
categoryL1 String @default("")
|
||
categoryL2 String @default("")
|
||
spu String @default("")
|
||
sku String @default("")
|
||
productName String @default("")
|
||
productStatus String @default("")
|
||
supplierName String @default("")
|
||
purchaseOwner String @default("")
|
||
brand String @default("")
|
||
productTag String @default("")
|
||
productLabel String @default("")
|
||
image String @default("")
|
||
barcodeCreatedAt DateTime?
|
||
postStopSkuStatus String @default("")
|
||
sales30dWithBig String @default("")
|
||
sales30dWithBigSku String @default("")
|
||
sales30dWithBigValue Decimal? @db.Decimal(18, 4)
|
||
sales30dWithBigSkuValue Decimal? @db.Decimal(18, 4)
|
||
opsDirector String @default("")
|
||
topSalesOpsDirector String @default("")
|
||
stopSuggestion String @default("")
|
||
replaceType String @default("")
|
||
replaceBarcode String @default("")
|
||
imageChange String @default("")
|
||
modificationPoint String @default("") @db.Text
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([demandId, barcode])
|
||
@@index([barcode])
|
||
@@index([sku])
|
||
@@index([purchaseOwner])
|
||
@@index([updatedAt])
|
||
}
|
||
|
||
// 停产原因分类基础选项配置(系统管理员维护,需求人只能选择已配置内容)。
|
||
|
||
model ProductionStopReasonCategory {
|
||
id String @id @default(cuid())
|
||
name String @unique
|
||
sortOrder Int @default(0)
|
||
isActive Boolean @default(true)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([isActive, sortOrder])
|
||
}
|
||
|
||
// 停产需求确认人(指定运营总监/指定其他人),由系统管理员维护,用于推送与确认权限判定。
|
||
|
||
model ProductionStopConfirmationApprover {
|
||
id String @id @default(cuid())
|
||
userId String @unique
|
||
userName String
|
||
dingtalkUnionId String @default("")
|
||
isActive Boolean @default(true)
|
||
sortOrder Int @default(0)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
votes ProductionStopDemandConfirmationVote[]
|
||
|
||
@@index([isActive, sortOrder])
|
||
}
|
||
|
||
// 停产需求「是否同意停产」确认票:一人一票,超时未反馈按默认同意记录来源 TIMEOUT。
|
||
|
||
model ProductionStopDemandConfirmationVote {
|
||
id String @id @default(cuid())
|
||
demandId String
|
||
demand ProductionStopDemand @relation(fields: [demandId], references: [id], onDelete: Cascade)
|
||
approverId String
|
||
approver ProductionStopConfirmationApprover @relation(fields: [approverId], references: [id], onDelete: Restrict)
|
||
approverUserId String
|
||
approverName String
|
||
decision String @default("AGREED")
|
||
reason String @default("") @db.Text
|
||
source String @default("USER")
|
||
respondedAt DateTime @default(now())
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([demandId, approverId])
|
||
@@index([demandId, decision])
|
||
@@index([respondedAt])
|
||
}
|
||
|
||
// 停产流程负责人:负责「走流程」与强制停产确认,权限单独授予(系统管理员维护)。
|
||
|
||
model ProductionStopFlowOwner {
|
||
id String @id @default(cuid())
|
||
userId String @unique
|
||
userName String
|
||
isActive Boolean @default(true)
|
||
sortOrder Int @default(0)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([isActive, sortOrder])
|
||
}
|
||
|
||
// 通知中心待办同步状态:按「待办类型 + 接收人」记录上次推送的待办快照。
|
||
// 待办集合变化时推送新的条数卡片;集合清空时推送一次「已全部处理完成」。
|
||
model ProductionStopNotificationSync {
|
||
id String @id @default(cuid())
|
||
kind String
|
||
recipientKey String
|
||
recipientName String @default("")
|
||
pendingCount Int @default(0)
|
||
snapshot String @default("")
|
||
lastSentAt DateTime?
|
||
completedAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([kind, recipientKey])
|
||
}
|
||
|
||
// 盘停建议配置:人工批量维护「条码 → 盘停建议」,提单时按条码自动带出。
|
||
|
||
model ProductionStopSuggestionConfig {
|
||
id String @id @default(cuid())
|
||
barcode String @unique
|
||
suggestion String @default("")
|
||
updatedById String @default("")
|
||
updatedByName String @default("")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([updatedAt])
|
||
}
|
||
|
||
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])
|
||
}
|
||
|
||
/// Durable execution state for image generation. Business results remain in their owning module.
|
||
model NativeImageGenerationJob {
|
||
id String @id
|
||
createdBy String
|
||
providerConfigId String
|
||
providerFingerprint String
|
||
requestHash String
|
||
request Json
|
||
completion Json
|
||
progress Json @default("[]")
|
||
status String @default("QUEUED")
|
||
retryCount Int @default(0)
|
||
nextRunAt DateTime @default(now())
|
||
leaseOwner String?
|
||
leaseUntil DateTime?
|
||
errorMessage String? @db.Text
|
||
finishedAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([status, nextRunAt, leaseUntil])
|
||
@@index([createdBy, createdAt])
|
||
}
|
||
|
||
model DiyScheme {
|
||
id String @id @default(cuid())
|
||
code String @unique
|
||
name String
|
||
parentCode String?
|
||
createdBy String
|
||
creator User @relation("DiySchemeCreator", fields: [createdBy], references: [id], onDelete: Cascade)
|
||
snapshot Json
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([createdBy, createdAt, id])
|
||
}
|
||
|
||
/// DIY 共享参考目录;一个导入事务发布完整目录,不创建正式产品或 SKU。
|
||
model DiyCatalog {
|
||
spu String @id
|
||
name String
|
||
version Int @default(1)
|
||
sourceHash String
|
||
importedAt DateTime @default(now())
|
||
styles DiyCabinetStyle[]
|
||
patterns DiyDoorPattern[]
|
||
materials DiyMaterial[]
|
||
files DiyAssetFile[]
|
||
}
|
||
|
||
/// panels/cells 是专用几何数据,身份、实际尺寸使用结构化列。
|
||
model DiyCabinetStyle {
|
||
code String @id
|
||
spu String
|
||
catalog DiyCatalog @relation(fields: [spu], references: [spu], onDelete: Restrict)
|
||
name String
|
||
displaySize String
|
||
width Float
|
||
height Float
|
||
depth Float
|
||
cellCount Int
|
||
panelCount Int
|
||
panels Json
|
||
cells Json
|
||
sortOrder Int
|
||
variants DiyCabinetVariant[]
|
||
modelFileId String?
|
||
model DiyAssetFile? @relation("DiyStyleModel", fields: [modelFileId], references: [id], onDelete: Restrict)
|
||
mainNodeNames String[] @default([])
|
||
shelfNodeNames String[] @default([])
|
||
modelRotationY Float @default(0)
|
||
@@index([spu, sortOrder])
|
||
}
|
||
|
||
model DiyCabinetVariant {
|
||
itemCode String @id
|
||
styleCode String
|
||
style DiyCabinetStyle @relation(fields: [styleCode], references: [code], onDelete: Restrict)
|
||
name String
|
||
barcodes String[]
|
||
factoryModel String
|
||
factoryCode String
|
||
orderQty Int
|
||
sortOrder Int
|
||
thumbnailFileId String
|
||
thumbnail DiyAssetFile @relation(fields: [thumbnailFileId], references: [id], onDelete: Restrict)
|
||
mainMaterialCode String?
|
||
mainMaterial DiyMaterial? @relation("DiyVariantMainMaterial", fields: [mainMaterialCode], references: [itemCode], onDelete: Restrict)
|
||
shelfMaterialCode String?
|
||
shelfMaterial DiyMaterial? @relation("DiyVariantShelfMaterial", fields: [shelfMaterialCode], references: [itemCode], onDelete: Restrict)
|
||
@@unique([styleCode, name])
|
||
}
|
||
|
||
model DiyDoorPattern {
|
||
itemCode String @id
|
||
spu String
|
||
catalog DiyCatalog @relation(fields: [spu], references: [spu], onDelete: Restrict)
|
||
name String
|
||
barcodes String[]
|
||
factoryModel String
|
||
factoryCode String
|
||
orderQty Int
|
||
sortOrder Int
|
||
imageFileId String
|
||
image DiyAssetFile @relation(fields: [imageFileId], references: [id], onDelete: Restrict)
|
||
width Float?
|
||
height Float?
|
||
thickness Float?
|
||
deletedAt DateTime?
|
||
deletedBy String?
|
||
@@index([spu, sortOrder])
|
||
@@index([deletedAt])
|
||
}
|
||
|
||
model DiyMaterial {
|
||
itemCode String @id
|
||
spu String
|
||
catalog DiyCatalog @relation(fields: [spu], references: [spu], onDelete: Restrict)
|
||
name String
|
||
kind String
|
||
colorHex String
|
||
barcodes String[]
|
||
factoryModel String
|
||
factoryCode String
|
||
sortOrder Int
|
||
textureFileId String?
|
||
texture DiyAssetFile? @relation(fields: [textureFileId], references: [id], onDelete: Restrict)
|
||
mainVariants DiyCabinetVariant[] @relation("DiyVariantMainMaterial")
|
||
shelfVariants DiyCabinetVariant[] @relation("DiyVariantShelfMaterial")
|
||
deletedAt DateTime?
|
||
deletedBy String?
|
||
@@unique([spu, name])
|
||
@@index([deletedAt])
|
||
}
|
||
|
||
/// 文件索引;url 为平台 /api/storage/object,校验和用于幂等导入。
|
||
model DiyAssetFile {
|
||
id String @id
|
||
spu String
|
||
catalog DiyCatalog @relation(fields: [spu], references: [spu], onDelete: Restrict)
|
||
storageKey String @unique
|
||
url String
|
||
sourcePath String
|
||
sha256 String
|
||
bytes Int
|
||
contentType String
|
||
kind String
|
||
createdAt DateTime @default(now())
|
||
variants DiyCabinetVariant[]
|
||
patterns DiyDoorPattern[]
|
||
materials DiyMaterial[]
|
||
models DiyCabinetStyle[] @relation("DiyStyleModel")
|
||
@@index([spu])
|
||
}
|
||
|
||
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([])
|
||
permissionRoleId String?
|
||
permissionRole PermissionRole? @relation(fields: [permissionRoleId], references: [id], onDelete: SetNull)
|
||
|
||
// 组织架构
|
||
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")
|
||
intakePhotoUploadGrants IntakeProductPhotoUploadGrant[] @relation("IntakePhotoUploadRecipient")
|
||
issuedIntakePhotoUploadGrants IntakeProductPhotoUploadGrant[] @relation("IntakePhotoUploadGrantor")
|
||
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")
|
||
createdDiySchemes DiyScheme[] @relation("DiySchemeCreator")
|
||
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([permissionRoleId])
|
||
@@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?
|
||
directoryManagerDingtalkUserIds String[] @default([])
|
||
|
||
// 层级关系
|
||
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?
|
||
managerDingtalkUserId String?
|
||
sourceStatus EmployeeSourceStatus @default(EMPLOYED)
|
||
primaryDepartmentExternalId String?
|
||
departmentExternalIds Json?
|
||
syncedAt DateTime @default(now())
|
||
rawSnapshot Json?
|
||
|
||
user User?
|
||
supplyChainManagementOwnerConfig SupplyChainManagementOwnerConfig?
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([name])
|
||
@@index([jobNumber])
|
||
@@index([mobile])
|
||
@@index([email])
|
||
@@index([sourceStatus])
|
||
@@index([managerDingtalkUserId])
|
||
@@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
|
||
SUPPLY_CHAIN_DROPSHIP_ISSUE_WRITE
|
||
PRODUCTION_STOP_DEMAND_DELETE
|
||
PRODUCTION_STOP_DEMAND_UPDATE
|
||
}
|
||
|
||
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])
|
||
@@index([workspaceCategory, source, status, createdAt], map: "OperationLog_retention_scope_createdAt_idx")
|
||
}
|
||
|
||
// 家具系统发往统一通知中心的本地审计副本。
|
||
// 不建立 User 外键,确保账号后续停用或删除后仍可追溯当时的收件人与内容。
|
||
model NotificationDeliveryLog {
|
||
id String @id @default(cuid())
|
||
|
||
idempotencyKey String @unique
|
||
payloadHash String
|
||
externalDeliveryId String? @unique
|
||
|
||
businessEvent String
|
||
businessRecordId String?
|
||
source String @default("SYSTEM")
|
||
initiatedById String?
|
||
initiatedByName String
|
||
|
||
recipientUserId String
|
||
recipientName String
|
||
recipientEmail String
|
||
recipientDingtalkUserId String
|
||
|
||
title String
|
||
renderedTitle String?
|
||
content String @db.Text
|
||
actionTitle String
|
||
actionUrl String @db.Text
|
||
|
||
status String @default("pending")
|
||
lastErrorCode String?
|
||
lastErrorMessage String? @db.Text
|
||
sentAt DateTime?
|
||
readAt DateTime?
|
||
lastSyncedAt DateTime?
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([status, createdAt])
|
||
@@index([recipientUserId, createdAt])
|
||
@@index([businessEvent, createdAt])
|
||
@@index([createdAt])
|
||
}
|
||
|
||
// 产品开发提醒的一次性业务尝试标记。通知正文仍由 NotificationDeliveryLog 审计。
|
||
model ProductDevelopmentTodoAttempt {
|
||
id String @id @default(cuid())
|
||
|
||
attemptKey String @unique
|
||
ruleId String
|
||
productId String
|
||
recipientKey String
|
||
status String @default("claimed")
|
||
lastErrorCode String?
|
||
lastErrorMessage String? @db.Text
|
||
attemptedAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([productId, ruleId])
|
||
@@index([recipientKey, attemptedAt])
|
||
}
|
||
|
||
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("")
|
||
workFrequency String @default("")
|
||
initiativeType 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([workFrequency, sortOrder])
|
||
@@index([initiativeType, sortOrder])
|
||
@@index([isActive, sortOrder])
|
||
}
|
||
|
||
model WorkRecordBusinessOption {
|
||
id String @id @default(cuid())
|
||
type String
|
||
value String
|
||
sortOrder Int @default(0)
|
||
isActive Boolean @default(true)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([type, value])
|
||
@@index([type, sortOrder])
|
||
@@index([type, isActive, sortOrder])
|
||
}
|
||
|
||
model EmployeeHoursRecord {
|
||
id String @id @default(cuid())
|
||
|
||
workDate DateTime @db.Date
|
||
nickname String
|
||
businessContent String
|
||
businessNote String @default("") @db.Text
|
||
businessCategory String
|
||
workFrequency String
|
||
businessSource String
|
||
startTime String @db.VarChar(16)
|
||
endTime String @db.VarChar(16)
|
||
minutes Int
|
||
hours Decimal @db.Decimal(10, 2)
|
||
estimatedScore Decimal @db.Decimal(12, 4)
|
||
approvedScore Decimal? @db.Decimal(12, 4)
|
||
department String @default("")
|
||
attachment Json?
|
||
|
||
createdById String
|
||
updatedById String
|
||
deletedAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([workDate, deletedAt])
|
||
@@index([nickname, workDate])
|
||
@@index([businessCategory, workDate])
|
||
@@index([department, workDate])
|
||
}
|
||
|
||
model EmployeeHoursAccessGrant {
|
||
userId String @id
|
||
scope String @default("SELF")
|
||
updatedById String
|
||
updatedAt DateTime @updatedAt
|
||
}
|
||
|
||
// Retained for migration compatibility; employee-hours no longer writes or exposes audit logs.
|
||
model EmployeeHoursAuditLog {
|
||
id String @id @default(cuid())
|
||
area String
|
||
action String
|
||
status String
|
||
actorId String
|
||
actorName String
|
||
targetId String?
|
||
message String @db.Text
|
||
details Json?
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([area, createdAt])
|
||
@@index([actorId, createdAt])
|
||
@@index([status, createdAt])
|
||
}
|
||
|
||
model EmployeeHoursBusinessOption {
|
||
id String @id @default(cuid())
|
||
type String
|
||
value String
|
||
sortOrder Int @default(0)
|
||
isActive Boolean @default(true)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([type, value])
|
||
@@index([type, sortOrder])
|
||
@@index([type, isActive, sortOrder])
|
||
}
|
||
|
||
model EmployeeHoursScoreRule {
|
||
id String @id @default(cuid())
|
||
primary String
|
||
secondary String @default("")
|
||
workFrequency String @default("")
|
||
initiativeType String @default("")
|
||
unitScore Decimal? @db.Decimal(12, 4)
|
||
sortOrder Int @default(0)
|
||
isActive Boolean @default(true)
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([primary, sortOrder])
|
||
@@index([workFrequency, sortOrder])
|
||
@@index([initiativeType, 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 PermissionRole {
|
||
id String @id @default(cuid())
|
||
name String
|
||
normalizedName String @unique
|
||
description String @default("")
|
||
sortOrder Int @default(0)
|
||
isActive Boolean @default(true)
|
||
createdBy String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
users User[]
|
||
|
||
@@index([isActive, sortOrder])
|
||
}
|
||
|
||
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 ProductArchiveFieldPermissionSubject {
|
||
subjectKey String @id
|
||
createdBy String?
|
||
updatedBy String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
blocks ProductArchiveFieldPermissionBlock[]
|
||
}
|
||
|
||
/// 产品全量档案字段权限(黑名单):subjectKey(岗位/角色/个人)对 fieldKey(列 prop)的屏蔽条目。
|
||
/// 只存被屏蔽的字段;未出现即默认可见,与页面级权限的「先有页面可见权,再看字段」层级一致。
|
||
model ProductArchiveFieldPermissionBlock {
|
||
id String @id @default(cuid())
|
||
|
||
subjectKey String
|
||
fieldKey String
|
||
subject ProductArchiveFieldPermissionSubject @relation(fields: [subjectKey], references: [subjectKey], onDelete: Cascade)
|
||
|
||
createdBy String?
|
||
updatedBy String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([subjectKey, fieldKey])
|
||
@@index([subjectKey])
|
||
}
|
||
|
||
// Store account data is maintained manually in this system.
|
||
model StoreAccount {
|
||
id String @id @default(cuid())
|
||
storeName String
|
||
operationOwner String @default("")
|
||
operationDepartmentHead String @default("")
|
||
operationDirector String @default("")
|
||
profitCenter String @default("")
|
||
accountType String
|
||
account String
|
||
passwordCiphertext String @default("") @db.Text
|
||
boundPhone String @default("")
|
||
verificationContact String @default("")
|
||
jstOrderService String @default("")
|
||
accountGroupName String @default("")
|
||
shopState Int?
|
||
subject String @default("")
|
||
boundBankAccountNumberCiphertext String @default("") @db.Text
|
||
createdById String?
|
||
updatedById String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([storeName, accountType, account])
|
||
@@index([storeName])
|
||
@@index([account])
|
||
@@index([accountType])
|
||
@@index([updatedAt])
|
||
}
|
||
|
||
// Dedicated append-only access audit. Never store password/ciphertext/request bodies.
|
||
model StoreAccountPasswordAccessAudit {
|
||
requestId String @id @db.Uuid
|
||
requesterId String? @db.VarChar(128)
|
||
serviceId String? @db.VarChar(64)
|
||
accountId String? @db.VarChar(128)
|
||
purpose String @db.VarChar(8)
|
||
status Int
|
||
createdAt DateTime @default(dbgenerated("clock_timestamp()")) @db.Timestamptz(3)
|
||
|
||
@@index([serviceId, createdAt])
|
||
@@index([accountId, createdAt])
|
||
}
|
||
|
||
model StoreAccountStoreProfile {
|
||
id String @id @default(cuid())
|
||
storeName String
|
||
platform String
|
||
merchantNumber String @default("")
|
||
secondaryMerchantNumber String @default("")
|
||
merchantStoreId String @default("")
|
||
companyId String @default("")
|
||
createdById String?
|
||
updatedById String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([storeName, platform])
|
||
@@index([storeName])
|
||
@@index([platform])
|
||
}
|
||
|
||
model StoreAccountChangeLog {
|
||
id String @id @default(cuid())
|
||
storeAccountId String?
|
||
storeName String
|
||
operation String
|
||
actorId String?
|
||
actorName String
|
||
changes Json
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([storeAccountId, createdAt])
|
||
@@index([createdAt])
|
||
@@index([storeName, createdAt])
|
||
}
|
||
|
||
model StoreAccountFieldPermission {
|
||
id String @id @default(cuid())
|
||
fieldKey String
|
||
subjectKey String
|
||
visibility String @default("READONLY")
|
||
updatedById String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([fieldKey, subjectKey])
|
||
@@index([fieldKey])
|
||
@@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 NewProductTerminationReasonArchive {
|
||
id String @id @default(cuid())
|
||
|
||
processInstanceId String @unique
|
||
openingCode String?
|
||
reasonCategory String
|
||
reason String? @db.Text
|
||
fetchedAt DateTime @default(now())
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([openingCode])
|
||
@@index([fetchedAt])
|
||
}
|
||
|
||
// ============================================
|
||
// 五金件库(标准五金件复用系统)
|
||
// ============================================
|
||
|
||
// 五金件分类
|
||
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)
|
||
articulationConfig Json?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
}
|
||
|
||
// ============================================
|
||
// 供应链脚本监控
|
||
// ============================================
|
||
|
||
enum ScriptMonitorNetworkMode {
|
||
INTRANET
|
||
INTERNET
|
||
AUTO
|
||
}
|
||
|
||
enum ScriptMonitorConnectionKind {
|
||
INTRANET
|
||
INTERNET
|
||
}
|
||
|
||
enum ScriptMonitorJobType {
|
||
CRON
|
||
PM2
|
||
SYSTEMD
|
||
MANUAL
|
||
OTHER
|
||
}
|
||
|
||
enum ScriptMonitorJobStatus {
|
||
UNKNOWN
|
||
RUNNING
|
||
SUCCESS
|
||
FAILED
|
||
SILENT
|
||
DISABLED
|
||
}
|
||
|
||
enum ScriptMonitorRunStatus {
|
||
RUNNING
|
||
SUCCESS
|
||
FAILED
|
||
}
|
||
|
||
enum ScriptMonitorCommandStatus {
|
||
PENDING
|
||
RUNNING
|
||
SUCCESS
|
||
FAILED
|
||
EXPIRED
|
||
}
|
||
|
||
model ScriptMonitorAgent {
|
||
id String @id @default(cuid())
|
||
name String
|
||
hostname String?
|
||
networkMode ScriptMonitorNetworkMode @default(AUTO)
|
||
lastConnectionKind ScriptMonitorConnectionKind?
|
||
tokenHash String @unique
|
||
tokenHint String
|
||
isActive Boolean @default(true)
|
||
os String?
|
||
agentVersion String?
|
||
reportIntervalSec Int @default(60)
|
||
lastSeenAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
scripts ScriptMonitorScript[]
|
||
runs ScriptMonitorRun[]
|
||
commands ScriptMonitorCommand[]
|
||
|
||
@@index([isActive, lastSeenAt])
|
||
}
|
||
|
||
model ScriptMonitorScript {
|
||
id String @id @default(cuid())
|
||
agentId String
|
||
agent ScriptMonitorAgent @relation(fields: [agentId], references: [id], onDelete: Cascade)
|
||
externalKey String
|
||
name String
|
||
taskDescription String @default("") @db.Text
|
||
jobType ScriptMonitorJobType
|
||
schedule String?
|
||
commandPreview String? @db.Text
|
||
expectedIntervalSec Int?
|
||
status ScriptMonitorJobStatus @default(UNKNOWN)
|
||
isEnabled Boolean @default(true)
|
||
canManualRun Boolean @default(false)
|
||
lastStartedAt DateTime?
|
||
lastFinishedAt DateTime?
|
||
lastDurationMs Int?
|
||
lastExitCode Int?
|
||
lastResultSummary String? @db.Text
|
||
lastOutputTail String? @db.Text
|
||
lastErrorTail String? @db.Text
|
||
lastReportedAt DateTime
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
runs ScriptMonitorRun[]
|
||
commands ScriptMonitorCommand[]
|
||
|
||
@@unique([agentId, externalKey])
|
||
@@index([agentId, status])
|
||
@@index([status, lastReportedAt])
|
||
}
|
||
|
||
model ScriptMonitorRun {
|
||
id String @id @default(cuid())
|
||
agentId String
|
||
agent ScriptMonitorAgent @relation(fields: [agentId], references: [id], onDelete: Cascade)
|
||
scriptId String
|
||
script ScriptMonitorScript @relation(fields: [scriptId], references: [id], onDelete: Cascade)
|
||
externalRunId String
|
||
status ScriptMonitorRunStatus
|
||
trigger String?
|
||
startedAt DateTime
|
||
finishedAt DateTime?
|
||
durationMs Int?
|
||
exitCode Int?
|
||
resultSummary String? @db.Text
|
||
outputTail String? @db.Text
|
||
errorTail String? @db.Text
|
||
createdAt DateTime @default(now())
|
||
|
||
@@unique([agentId, externalRunId])
|
||
@@index([scriptId, startedAt(sort: Desc)])
|
||
@@index([agentId, startedAt(sort: Desc)])
|
||
}
|
||
|
||
model ScriptMonitorCommand {
|
||
id String @id @default(cuid())
|
||
agentId String
|
||
agent ScriptMonitorAgent @relation(fields: [agentId], references: [id], onDelete: Cascade)
|
||
scriptId String
|
||
script ScriptMonitorScript @relation(fields: [scriptId], references: [id], onDelete: Cascade)
|
||
status ScriptMonitorCommandStatus @default(PENDING)
|
||
requestedById String?
|
||
requestedByName String?
|
||
requestedAt DateTime @default(now())
|
||
expiresAt DateTime
|
||
claimedAt DateTime?
|
||
finishedAt DateTime?
|
||
externalRunId String?
|
||
errorMessage String? @db.Text
|
||
|
||
@@index([agentId, status, requestedAt])
|
||
@@index([scriptId, requestedAt(sort: Desc)])
|
||
}
|
||
|
||
/// 首页快捷网址(管理员配置,内部人员在首页下拉框中点击跳转)
|
||
model HomeQuickLink {
|
||
id String @id @default(cuid())
|
||
title String
|
||
description String @default("")
|
||
url String
|
||
iconKey String @default("link")
|
||
sortOrder Int @default(0)
|
||
enabled Boolean @default(true)
|
||
createdBy String
|
||
updatedBy String
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([enabled, sortOrder])
|
||
}
|
||
|
||
/// BOM agent 旁路引擎的单 SKU 估算缓存(工程域;主键语义:skuId + engineVersion)。
|
||
/// 纯新增表,不与现有业务模型建外键关系;在 agent 流程过实测闸门前仅供工程侧工具使用。
|
||
model BomCostingAgentSkuEstimate {
|
||
id String @id @default(cuid())
|
||
caseId String
|
||
skuId String
|
||
externalSkuId String @default("")
|
||
engineVersion String
|
||
status String
|
||
estimate Json
|
||
usage Json?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([skuId, engineVersion])
|
||
@@index([caseId])
|
||
}
|
||
|
||
/// 物流到货签收时长看板的订单明细快照批次。
|
||
/// 每日由 scripts/sync-logistics-arrival-signing-duration-snapshot.ts 生成一批;
|
||
/// 看板只读取 status = COMPLETED 的最新批次,写入中途的批次对读路径不可见。
|
||
model LogisticsArrivalSigningDurationSnapshotRun {
|
||
id String @id @default(cuid())
|
||
snapshotDate DateTime @unique @db.Date
|
||
status String @default("RUNNING")
|
||
windowFrom String @default("")
|
||
windowTo String @default("")
|
||
/// 该批快照实际覆盖到的最大签收时间('YYYY-MM-DD HH:MM:SS')。跑批当天数据只到跑批那一刻,
|
||
/// 读路径据此把"声明窗口"收窄到"实际覆盖",避免把当天误判为已完整覆盖。
|
||
dataThrough String @default("")
|
||
rowCount Int @default(0)
|
||
message String @default("")
|
||
startedAt DateTime @default(now())
|
||
finishedAt DateTime?
|
||
|
||
@@index([status, snapshotDate], map: "LogisticsArrivalSigningDurationSnapshotRun_status_date_idx")
|
||
}
|
||
|
||
/// 物流到货签收时长看板的订单明细快照行。
|
||
/// 字段与远端报表视图 rpt.jst_order_express_details 的看板口径一一对应,
|
||
/// 条码/供应商/采购在快照时点固化,避免看板每次刷新都回查远端明细与产品档案。
|
||
model LogisticsArrivalSigningDurationSnapshot {
|
||
id String @id @default(cuid())
|
||
snapshotDate DateTime @db.Date
|
||
rowKey String
|
||
innerOrderId String
|
||
soId String @default("")
|
||
logisticId String @default("")
|
||
logisticCompanyName String @default("")
|
||
logisticStatusName String @default("")
|
||
logisticReturnFlag String @default("")
|
||
orderLabels String @default("")
|
||
remark String @default("")
|
||
buyerMessage String @default("")
|
||
shopName String @default("")
|
||
platformName String @default("")
|
||
warehouseName String @default("")
|
||
receiverAddress String @default("")
|
||
payTime String?
|
||
sendTime String?
|
||
fetchTime String?
|
||
signTime String?
|
||
barcode String @default("")
|
||
productName String @default("")
|
||
supplier String @default("")
|
||
purchaser String @default("")
|
||
productItems Json?
|
||
createdAt DateTime @default(now())
|
||
|
||
@@unique([snapshotDate, rowKey], map: "LogisticsArrivalSigningDurationSnapshot_date_row_key_key")
|
||
@@index([snapshotDate, signTime], map: "LogisticsArrivalSigningDurationSnapshot_date_sign_time_idx")
|
||
/// 看板热路径的覆盖索引:时间戳与 4 个维度列直接随索引读取,避免整表扫描。
|
||
@@index([snapshotDate, signTime], map: "LogisticsArrivalSigningDurationSnapshot_cover_idx")
|
||
}
|
||
|
||
/// 限价管理 · 破价订单审核:聚水潭订单实收与限价表比对后生成的破价单
|
||
model SupplyChainBreachOrder {
|
||
id String @id @default(cuid())
|
||
platform String @default("")
|
||
shopName String @default("")
|
||
soId String @default("")
|
||
oId String @default("")
|
||
payDate DateTime?
|
||
itemCode String @default("")
|
||
shopItemCode String @default("")
|
||
shopStyleCode String @default("")
|
||
linkGroupKey String @default("")
|
||
qty Int @default(1)
|
||
buyerPaidAmount Decimal @default(0) @db.Decimal(18, 4)
|
||
platformBearAmount Decimal @default(0) @db.Decimal(18, 4)
|
||
sellerIncomeAmount Decimal @default(0) @db.Decimal(18, 4)
|
||
limitUnitPrice Decimal? @db.Decimal(18, 4)
|
||
limitTotal Decimal? @db.Decimal(18, 4)
|
||
gapAmount Decimal? @db.Decimal(18, 4)
|
||
ownerName String @default("")
|
||
profitCenter String @default("")
|
||
orderStatus String @default("")
|
||
refundStatus String @default("")
|
||
listedAt DateTime?
|
||
flowEndDate DateTime?
|
||
status String @default("PENDING_APPEAL")
|
||
appealType String @default("")
|
||
appealScope String @default("ORDER")
|
||
processNo String @default("")
|
||
appealRound Int @default(1)
|
||
previousAppeal Json?
|
||
appealDesc String @default("") @db.Text
|
||
appealImages Json @default("[]")
|
||
appliedFromLink Boolean @default(false)
|
||
autoSuggested String @default("")
|
||
autoSuggestedNote String @default("") @db.Text
|
||
reviewedBy String @default("")
|
||
reviewedAt DateTime?
|
||
reviewDecision String @default("")
|
||
reviewReason String @default("")
|
||
reviewNote String @default("") @db.Text
|
||
fineAmount Decimal @default(0) @db.Decimal(10, 2)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([oId, itemCode])
|
||
@@index([status, payDate(sort: Desc)])
|
||
@@index([linkGroupKey])
|
||
@@index([ownerName, status])
|
||
@@index([shopName, itemCode])
|
||
}
|
||
|
||
/// 破价单操作日志(系统生成、申诉、审核)
|
||
model SupplyChainBreachOrderLog {
|
||
id String @id @default(cuid())
|
||
orderId String
|
||
action String
|
||
operatorId String @default("")
|
||
operatorName String @default("")
|
||
detail Json @default("{}")
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([orderId, createdAt])
|
||
}
|
||
|
||
/// 限价表(链接限价):店铺名称 + 商品编码 → 限价单价、上架时间、限价流程结束日期
|
||
model SupplyChainLinkLimitPrice {
|
||
id String @id @default(cuid())
|
||
shopName String
|
||
itemCode String
|
||
spu String @default("")
|
||
limitUnitPrice Decimal @db.Decimal(18, 4)
|
||
listedAt DateTime?
|
||
flowEndDate DateTime?
|
||
source String @default("MANUAL")
|
||
ownerName String @default("")
|
||
updatedById String @default("")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([shopName, itemCode])
|
||
@@index([itemCode])
|
||
}
|
||
|
||
/// 限价管理 · 破价判定规则配置(单行 JSON + 修改留痕)
|
||
model SupplyChainBreachRuleConfig {
|
||
id String @id @default("default")
|
||
data Json @default("{}")
|
||
updatedById String @default("")
|
||
updatedByName String @default("")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
}
|
||
|
||
/// 限价流程同步 · 货号负责人限价(按货号维度;来源:限价流程的「期望日常限价」,
|
||
/// 同价申请 / 活动限价类流程不覆盖)。
|
||
model SupplyChainStyleLimitPrice {
|
||
id String @id @default(cuid())
|
||
styleCode String @unique
|
||
ownerLimitPrice Decimal? @db.Decimal(18, 4)
|
||
processInstanceId String @default("")
|
||
processNo String @default("")
|
||
applyType String @default("")
|
||
initiator String @default("")
|
||
/// 流程审核通过时间(限价生效时间)
|
||
approvedAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
}
|
||
|
||
/// 限价流程同步 · 价格授权(同价申请 / 活动限价;供破价判定豁免与超期判定)。
|
||
model SupplyChainLimitFlowAuth {
|
||
id String @id @default(cuid())
|
||
processInstanceId String
|
||
processNo String @default("")
|
||
applyType String @default("")
|
||
styleCode String @default("")
|
||
spu String @default("")
|
||
/// 同价开始时间 = 流程审核通过时间
|
||
approvedAt DateTime?
|
||
/// 活动开始时间(同价类型为空)
|
||
startDate DateTime?
|
||
/// 结束时间(同价 / 活动共用)
|
||
endDate DateTime?
|
||
/// 活动限价:调整后限价(价格 ≥ 该值不判破价)
|
||
adjustedLimitPrice Decimal? @db.Decimal(18, 4)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([processInstanceId, styleCode, applyType])
|
||
@@index([styleCode])
|
||
@@index([applyType])
|
||
}
|
||
|
||
/// 商品库存快照:条码 × 实际仓库粒度的库存数据,由 15 分钟定时任务全量替换写入。
|
||
/// 数据来源于聚水潭库存明细/云仓链路与产品档案,失败时保留上一份快照。
|
||
model ProductInventorySnapshot {
|
||
id String @id @default(cuid())
|
||
barcode String
|
||
wmsCoId Int
|
||
productName String @default("")
|
||
category String @default("")
|
||
sku String @default("")
|
||
productionStatus String @default("")
|
||
supplier String @default("")
|
||
purchaser String @default("")
|
||
stockQty Int @default(0)
|
||
orderLockQty Int @default(0)
|
||
availableQty Int @default(0)
|
||
cloudAvailableQty Int @default(0)
|
||
warehouseName String @default("")
|
||
warehouseAttribute String @default("")
|
||
warehouseOwnership String @default("")
|
||
warehouseHead String @default("")
|
||
syncedAt DateTime
|
||
createdAt DateTime @default(now())
|
||
|
||
@@unique([barcode, wmsCoId])
|
||
@@index([warehouseName])
|
||
@@index([syncedAt])
|
||
}
|
||
|
||
/// 商品库存快照同步运行记录:用于排查定时任务状态与失败原因,不记录连接串或 SQL 载荷。
|
||
model ProductInventorySnapshotRun {
|
||
id String @id @default(cuid())
|
||
startedAt DateTime @default(now())
|
||
completedAt DateTime?
|
||
status String @db.VarChar(24)
|
||
rowsRead Int @default(0)
|
||
rowsWritten Int @default(0)
|
||
syncedAt DateTime?
|
||
errorMessage String? @db.Text
|
||
|
||
@@index([startedAt])
|
||
@@index([status, startedAt])
|
||
}
|