376 lines
13 KiB
Plaintext
376 lines
13 KiB
Plaintext
generator client {
|
||
provider = "prisma-client-js"
|
||
}
|
||
|
||
datasource db {
|
||
provider = "postgresql"
|
||
url = env("DATABASE_URL")
|
||
}
|
||
|
||
// ============================================
|
||
// 认种订单表 (状态表)
|
||
// ============================================
|
||
model PlantingOrder {
|
||
id BigInt @id @default(autoincrement()) @map("order_id")
|
||
orderNo String @unique @map("order_no") @db.VarChar(50)
|
||
userId BigInt @map("user_id")
|
||
|
||
// 认种信息
|
||
treeCount Int @map("tree_count")
|
||
totalAmount Decimal @map("total_amount") @db.Decimal(20, 8)
|
||
|
||
// 省市选择 (不可修改)
|
||
selectedProvince String? @map("selected_province") @db.VarChar(10)
|
||
selectedCity String? @map("selected_city") @db.VarChar(10)
|
||
provinceCitySelectedAt DateTime? @map("province_city_selected_at")
|
||
provinceCityConfirmedAt DateTime? @map("province_city_confirmed_at")
|
||
|
||
// 订单状态
|
||
status String @default("CREATED") @map("status") @db.VarChar(30)
|
||
|
||
// 底池信息
|
||
poolInjectionBatchId BigInt? @map("pool_injection_batch_id")
|
||
poolInjectionScheduledTime DateTime? @map("pool_injection_scheduled_time")
|
||
poolInjectionActualTime DateTime? @map("pool_injection_actual_time")
|
||
poolInjectionTxHash String? @map("pool_injection_tx_hash") @db.VarChar(100)
|
||
|
||
// 挖矿
|
||
miningEnabledAt DateTime? @map("mining_enabled_at")
|
||
|
||
// 时间戳
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
paidAt DateTime? @map("paid_at")
|
||
fundAllocatedAt DateTime? @map("fund_allocated_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
// 关联
|
||
fundAllocations FundAllocation[]
|
||
batch PoolInjectionBatch? @relation(fields: [poolInjectionBatchId], references: [id])
|
||
|
||
@@index([userId])
|
||
@@index([orderNo])
|
||
@@index([status])
|
||
@@index([poolInjectionBatchId])
|
||
@@index([selectedProvince, selectedCity])
|
||
@@index([createdAt])
|
||
@@index([paidAt])
|
||
@@map("planting_orders")
|
||
}
|
||
|
||
// ============================================
|
||
// 资金分配明细表 (行为表, append-only)
|
||
// ============================================
|
||
model FundAllocation {
|
||
id BigInt @id @default(autoincrement()) @map("allocation_id")
|
||
orderId BigInt @map("order_id")
|
||
|
||
// 分配信息
|
||
targetType String @map("target_type") @db.VarChar(50)
|
||
amount Decimal @map("amount") @db.Decimal(20, 8)
|
||
targetAccountId String? @map("target_account_id") @db.VarChar(100)
|
||
|
||
// 元数据
|
||
metadata Json? @map("metadata")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
// 关联
|
||
order PlantingOrder @relation(fields: [orderId], references: [id])
|
||
|
||
@@index([orderId])
|
||
@@index([targetType, targetAccountId])
|
||
@@index([createdAt])
|
||
@@map("fund_allocations")
|
||
}
|
||
|
||
// ============================================
|
||
// 用户持仓表 (状态表)
|
||
// ============================================
|
||
model PlantingPosition {
|
||
id BigInt @id @default(autoincrement()) @map("position_id")
|
||
userId BigInt @unique @map("user_id")
|
||
|
||
// 持仓统计
|
||
totalTreeCount Int @default(0) @map("total_tree_count")
|
||
effectiveTreeCount Int @default(0) @map("effective_tree_count")
|
||
pendingTreeCount Int @default(0) @map("pending_tree_count")
|
||
|
||
// 挖矿状态
|
||
firstMiningStartAt DateTime? @map("first_mining_start_at")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
// 关联
|
||
distributions PositionDistribution[]
|
||
|
||
@@index([userId])
|
||
@@index([totalTreeCount])
|
||
@@map("planting_positions")
|
||
}
|
||
|
||
// ============================================
|
||
// 持仓省市分布表
|
||
// ============================================
|
||
model PositionDistribution {
|
||
id BigInt @id @default(autoincrement()) @map("distribution_id")
|
||
userId BigInt @map("user_id")
|
||
|
||
// 省市信息
|
||
provinceCode String? @map("province_code") @db.VarChar(10)
|
||
cityCode String? @map("city_code") @db.VarChar(10)
|
||
|
||
// 数量
|
||
treeCount Int @default(0) @map("tree_count")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
// 关联
|
||
position PlantingPosition @relation(fields: [userId], references: [userId])
|
||
|
||
@@unique([userId, provinceCode, cityCode])
|
||
@@index([userId])
|
||
@@index([provinceCode])
|
||
@@index([cityCode])
|
||
@@map("position_province_city_distribution")
|
||
}
|
||
|
||
// ============================================
|
||
// 底池注入批次表 (状态表)
|
||
// ============================================
|
||
model PoolInjectionBatch {
|
||
id BigInt @id @default(autoincrement()) @map("batch_id")
|
||
batchNo String @unique @map("batch_no") @db.VarChar(50)
|
||
|
||
// 批次时间窗口 (5天)
|
||
startDate DateTime @map("start_date") @db.Date
|
||
endDate DateTime @map("end_date") @db.Date
|
||
|
||
// 统计信息
|
||
orderCount Int @default(0) @map("order_count")
|
||
totalAmount Decimal @default(0) @map("total_amount") @db.Decimal(20, 8)
|
||
|
||
// 注入状态
|
||
status String @default("PENDING") @map("status") @db.VarChar(20)
|
||
scheduledInjectionTime DateTime? @map("scheduled_injection_time")
|
||
actualInjectionTime DateTime? @map("actual_injection_time")
|
||
injectionTxHash String? @map("injection_tx_hash") @db.VarChar(100)
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
// 关联
|
||
orders PlantingOrder[]
|
||
|
||
@@index([batchNo])
|
||
@@index([startDate, endDate])
|
||
@@index([status])
|
||
@@index([scheduledInjectionTime])
|
||
@@map("pool_injection_batches")
|
||
}
|
||
|
||
// ============================================
|
||
// 认种事件表 (行为表, append-only)
|
||
// ============================================
|
||
model PlantingEvent {
|
||
id BigInt @id @default(autoincrement()) @map("event_id")
|
||
eventType String @map("event_type") @db.VarChar(50)
|
||
|
||
// 聚合根信息
|
||
aggregateId String @map("aggregate_id") @db.VarChar(100)
|
||
aggregateType String @map("aggregate_type") @db.VarChar(50)
|
||
|
||
// 事件数据
|
||
eventData Json @map("event_data")
|
||
|
||
// 元数据
|
||
userId BigInt? @map("user_id")
|
||
occurredAt DateTime @default(now()) @map("occurred_at")
|
||
version Int @default(1) @map("version")
|
||
|
||
@@index([aggregateType, aggregateId])
|
||
@@index([eventType])
|
||
@@index([userId])
|
||
@@index([occurredAt])
|
||
@@map("planting_events")
|
||
}
|
||
|
||
// ============================================
|
||
// Outbox 事件发件箱表 (Outbox Pattern)
|
||
// 保证事件发布的可靠性:
|
||
// 1. 业务数据和 Outbox 记录在同一个事务中写入
|
||
// 2. 后台任务轮询 Outbox 表并发布到 Kafka
|
||
// 3. 发布成功后标记为已处理
|
||
// ============================================
|
||
model OutboxEvent {
|
||
id BigInt @id @default(autoincrement()) @map("outbox_id")
|
||
|
||
// 事件信息
|
||
eventType String @map("event_type") @db.VarChar(100)
|
||
topic String @map("topic") @db.VarChar(100)
|
||
key String @map("key") @db.VarChar(200)
|
||
payload Json @map("payload")
|
||
|
||
// 聚合根信息 (用于幂等性检查)
|
||
aggregateId String @map("aggregate_id") @db.VarChar(100)
|
||
aggregateType String @map("aggregate_type") @db.VarChar(50)
|
||
|
||
// 发布状态
|
||
status String @default("PENDING") @map("status") @db.VarChar(20) // PENDING, PUBLISHED, FAILED
|
||
retryCount Int @default(0) @map("retry_count")
|
||
maxRetries Int @default(5) @map("max_retries")
|
||
lastError String? @map("last_error") @db.Text
|
||
|
||
// 时间戳
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
publishedAt DateTime? @map("published_at")
|
||
nextRetryAt DateTime? @map("next_retry_at")
|
||
|
||
@@index([status, createdAt])
|
||
@@index([status, nextRetryAt])
|
||
@@index([aggregateType, aggregateId])
|
||
@@index([topic])
|
||
@@map("outbox_events")
|
||
}
|
||
|
||
// ============================================
|
||
// 支付补偿表 (用于处理支付失败需要补偿的订单)
|
||
// 当订单支付过程中发生以下情况时创建补偿记录:
|
||
// 1. 资金已冻结/扣款但数据库事务失败
|
||
// 2. 数据库事务成功但确认扣款失败
|
||
// 3. 确认扣款成功但资金分配失败
|
||
// ============================================
|
||
model PaymentCompensation {
|
||
id BigInt @id @default(autoincrement()) @map("compensation_id")
|
||
orderNo String @map("order_no") @db.VarChar(50)
|
||
userId BigInt @map("user_id")
|
||
|
||
// 补偿类型
|
||
compensationType String @map("compensation_type") @db.VarChar(50) // UNFREEZE, REFUND, RETRY_CONFIRM, RETRY_ALLOCATE
|
||
|
||
// 金额信息
|
||
amount Decimal @map("amount") @db.Decimal(20, 8)
|
||
|
||
// 状态
|
||
status String @default("PENDING") @map("status") @db.VarChar(20) // PENDING, PROCESSING, COMPLETED, FAILED
|
||
|
||
// 失败信息
|
||
failureReason String? @map("failure_reason") @db.Text
|
||
failureStage String? @map("failure_stage") @db.VarChar(50) // FREEZE, DB_TRANSACTION, CONFIRM, ALLOCATE
|
||
|
||
// 重试信息
|
||
retryCount Int @default(0) @map("retry_count")
|
||
maxRetries Int @default(5) @map("max_retries")
|
||
nextRetryAt DateTime? @map("next_retry_at")
|
||
lastError String? @map("last_error") @db.Text
|
||
|
||
// 时间戳
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
processedAt DateTime? @map("processed_at")
|
||
completedAt DateTime? @map("completed_at")
|
||
|
||
@@unique([orderNo, compensationType])
|
||
@@index([status, nextRetryAt])
|
||
@@index([userId])
|
||
@@index([orderNo])
|
||
@@index([createdAt])
|
||
@@map("payment_compensations")
|
||
}
|
||
|
||
// ============================================
|
||
// 合同模板表
|
||
// 存储电子认种合同的模板内容,支持版本管理
|
||
// ============================================
|
||
model ContractTemplate {
|
||
id Int @id @default(autoincrement()) @map("template_id")
|
||
version String @unique @map("version") @db.VarChar(20) // 版本号: v1.0.0
|
||
|
||
// 合同内容
|
||
title String @map("title") @db.VarChar(200) // 合同标题
|
||
content String @map("content") @db.Text // 合同HTML内容,包含占位符
|
||
|
||
// 生效时间
|
||
effectiveFrom DateTime @map("effective_from")
|
||
effectiveTo DateTime? @map("effective_to") // null表示永久有效
|
||
isActive Boolean @default(true) @map("is_active")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
// 关联
|
||
signingTasks ContractSigningTask[]
|
||
|
||
@@index([isActive, effectiveFrom])
|
||
@@map("contract_templates")
|
||
}
|
||
|
||
// ============================================
|
||
// 合同签署任务表
|
||
// 独立模块,不影响现有认种流程
|
||
// 通过订阅 OrderPaid 事件创建签署任务
|
||
// ============================================
|
||
model ContractSigningTask {
|
||
id BigInt @id @default(autoincrement()) @map("task_id")
|
||
|
||
// 关联信息
|
||
orderNo String @unique @map("order_no") @db.VarChar(50)
|
||
userId BigInt @map("user_id")
|
||
accountSequence String @map("account_sequence") @db.VarChar(20)
|
||
|
||
// 合同信息
|
||
templateId Int @map("template_id")
|
||
contractVersion String @map("contract_version") @db.VarChar(20)
|
||
contractContent String @map("contract_content") @db.Text // 已填入用户信息的完整合同
|
||
|
||
// 用户信息快照 (签署时填入合同的信息)
|
||
userPhoneNumber String? @map("user_phone_number") @db.VarChar(20)
|
||
userRealName String? @map("user_real_name") @db.VarChar(50)
|
||
userIdCardNumber String? @map("user_id_card_number") @db.VarChar(50) // 脱敏存储
|
||
|
||
// 订单信息快照
|
||
treeCount Int @map("tree_count")
|
||
totalAmount Decimal @map("total_amount") @db.Decimal(20, 8)
|
||
provinceCode String @map("province_code") @db.VarChar(10)
|
||
provinceName String @map("province_name") @db.VarChar(50)
|
||
cityCode String @map("city_code") @db.VarChar(10)
|
||
cityName String @map("city_name") @db.VarChar(50)
|
||
|
||
// 签署状态
|
||
// PENDING: 待签署
|
||
// SCROLLED: 已滚动到底部
|
||
// ACKNOWLEDGED: 已确认法律效力
|
||
// SIGNED: 已签署完成
|
||
// UNSIGNED_TIMEOUT: 超时未签署
|
||
status String @default("PENDING") @map("status") @db.VarChar(30)
|
||
expiresAt DateTime @map("expires_at") // 24小时过期时间
|
||
|
||
// 签署进度时间戳
|
||
scrolledToBottomAt DateTime? @map("scrolled_to_bottom_at")
|
||
acknowledgedAt DateTime? @map("acknowledged_at")
|
||
signedAt DateTime? @map("signed_at")
|
||
|
||
// 签名数据
|
||
signatureCloudUrl String? @map("signature_cloud_url") @db.VarChar(500)
|
||
signatureHash String? @map("signature_hash") @db.VarChar(64) // SHA256
|
||
|
||
// 法律合规证据链
|
||
signingIpAddress String? @map("signing_ip_address") @db.VarChar(50)
|
||
signingDeviceInfo String? @map("signing_device_info") @db.Text // JSON格式
|
||
signingUserAgent String? @map("signing_user_agent") @db.VarChar(500)
|
||
signingLatitude Decimal? @map("signing_latitude") @db.Decimal(10, 8)
|
||
signingLongitude Decimal? @map("signing_longitude") @db.Decimal(11, 8)
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
// 关联
|
||
template ContractTemplate @relation(fields: [templateId], references: [id])
|
||
|
||
@@index([userId])
|
||
@@index([status])
|
||
@@index([expiresAt])
|
||
@@index([status, expiresAt])
|
||
@@map("contract_signing_tasks")
|
||
}
|