rwadurian/backend/services/planting-service/prisma/schema.prisma

376 lines
13 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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")
}