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

280 lines
9.1 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")
}