generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ============================================ // CDC 同步数据表(从其他服务同步) // ============================================ // 同步的用户数据 model SyncedUser { id BigInt @id @default(autoincrement()) accountSequence String @unique @map("account_sequence") @db.VarChar(20) originalUserId BigInt @map("original_user_id") phone String? @db.VarChar(20) status String? @db.VarChar(20) // CDC 同步元数据 sourceSequenceNum BigInt @map("source_sequence_num") syncedAt DateTime @default(now()) @map("synced_at") // 算力计算状态 contributionCalculated Boolean @default(false) @map("contribution_calculated") contributionCalculatedAt DateTime? @map("contribution_calculated_at") createdAt DateTime @default(now()) @map("created_at") @@index([originalUserId]) @@index([contributionCalculated]) @@map("synced_users") } // 同步的认种数据 model SyncedAdoption { id BigInt @id @default(autoincrement()) originalAdoptionId BigInt @unique @map("original_adoption_id") accountSequence String @map("account_sequence") @db.VarChar(20) treeCount Int @map("tree_count") adoptionDate DateTime @map("adoption_date") @db.Date status String? @db.VarChar(30) // 与1.0 planting_orders.status保持一致 // 认种选择的省市(用于系统账户分配) selectedProvince String? @map("selected_province") @db.VarChar(10) selectedCity String? @map("selected_city") @db.VarChar(10) // 贡献值计算参数(从认种时的配置) contributionPerTree Decimal @map("contribution_per_tree") @db.Decimal(20, 10) // CDC 同步元数据 sourceSequenceNum BigInt @map("source_sequence_num") syncedAt DateTime @default(now()) @map("synced_at") // 算力分配状态 contributionDistributed Boolean @default(false) @map("contribution_distributed") contributionDistributedAt DateTime? @map("contribution_distributed_at") // 分配明细摘要 (JSON 字符串格式) // 格式: { personal: {rate, amount}, operation: {rate, amount}, province: {rate, amount}, city: {rate, amount}, team: {rate, amount, distributed, unallocated} } distributionSummary String? @map("distribution_summary") @db.Text createdAt DateTime @default(now()) @map("created_at") @@index([accountSequence]) @@index([adoptionDate]) @@index([contributionDistributed]) @@index([selectedProvince, selectedCity]) @@map("synced_adoptions") } // 同步的推荐关系数据 model SyncedReferral { id BigInt @id @default(autoincrement()) accountSequence String @unique @map("account_sequence") @db.VarChar(20) // 推荐人信息:优先使用 account_sequence,但也保存 user_id 以便后续解析 referrerAccountSequence String? @map("referrer_account_sequence") @db.VarChar(20) referrerUserId BigInt? @map("referrer_user_id") // 1.0 的 referrer_id originalUserId BigInt? @map("original_user_id") // 1.0 的 user_id // 预计算的层级路径(便于快速查询上下级) // 1.0 存储的是 BigInt[],这里转换为逗号分隔的字符串 ancestorPath String? @map("ancestor_path") @db.Text depth Int @default(0) // CDC 同步元数据 sourceSequenceNum BigInt @map("source_sequence_num") syncedAt DateTime @default(now()) @map("synced_at") createdAt DateTime @default(now()) @map("created_at") @@index([referrerAccountSequence]) @@index([referrerUserId]) @@index([originalUserId]) @@map("synced_referrals") } // ============================================ // 算力账户与明细表 // ============================================ // 算力账户表(汇总) // 设计说明: // - 个人算力:自己认种,立即生效 // - 层级算力:下级1-15层认种的分成,每层0.5%,共7.5% // - 加成算力:团队加成,3档各2.5%,共7.5% // 解锁规则: // - 自己认种:解锁层级1-5 + 加成第1档 // - 直推≥2人认种:解锁加成第2档 // - 直推≥3人认种:解锁层级6-10 // - 直推≥4人认种:解锁加成第3档 // - 直推≥5人认种:解锁层级11-15 model ContributionAccount { id BigInt @id @default(autoincrement()) accountSequence String @unique @map("account_sequence") @db.VarChar(20) // ========== 个人算力(立即生效)========== personalContribution Decimal @default(0) @map("personal_contribution") @db.Decimal(30, 10) // ========== 15级层级算力(待解锁/已解锁)========== // 每级0.5%,分三档解锁 // 第1档(1-5级):自己认种解锁 level1Pending Decimal @default(0) @map("level_1_pending") @db.Decimal(30, 10) level2Pending Decimal @default(0) @map("level_2_pending") @db.Decimal(30, 10) level3Pending Decimal @default(0) @map("level_3_pending") @db.Decimal(30, 10) level4Pending Decimal @default(0) @map("level_4_pending") @db.Decimal(30, 10) level5Pending Decimal @default(0) @map("level_5_pending") @db.Decimal(30, 10) // 第2档(6-10级):直推≥3人认种解锁 level6Pending Decimal @default(0) @map("level_6_pending") @db.Decimal(30, 10) level7Pending Decimal @default(0) @map("level_7_pending") @db.Decimal(30, 10) level8Pending Decimal @default(0) @map("level_8_pending") @db.Decimal(30, 10) level9Pending Decimal @default(0) @map("level_9_pending") @db.Decimal(30, 10) level10Pending Decimal @default(0) @map("level_10_pending") @db.Decimal(30, 10) // 第3档(11-15级):直推≥5人认种解锁 level11Pending Decimal @default(0) @map("level_11_pending") @db.Decimal(30, 10) level12Pending Decimal @default(0) @map("level_12_pending") @db.Decimal(30, 10) level13Pending Decimal @default(0) @map("level_13_pending") @db.Decimal(30, 10) level14Pending Decimal @default(0) @map("level_14_pending") @db.Decimal(30, 10) level15Pending Decimal @default(0) @map("level_15_pending") @db.Decimal(30, 10) // ========== 3档加成算力(待解锁)========== // 每档2.5% bonusTier1Pending Decimal @default(0) @map("bonus_tier_1_pending") @db.Decimal(30, 10) // 自己认种解锁 bonusTier2Pending Decimal @default(0) @map("bonus_tier_2_pending") @db.Decimal(30, 10) // 直推≥2人认种解锁 bonusTier3Pending Decimal @default(0) @map("bonus_tier_3_pending") @db.Decimal(30, 10) // 直推≥4人认种解锁 // ========== 汇总字段 ========== totalLevelPending Decimal @default(0) @map("total_level_pending") @db.Decimal(30, 10) // sum(level1-15 pending) totalBonusPending Decimal @default(0) @map("total_bonus_pending") @db.Decimal(30, 10) // sum(tier1-3 pending) totalPending Decimal @default(0) @map("total_pending") @db.Decimal(30, 10) // 所有待解锁总和 totalUnlocked Decimal @default(0) @map("total_unlocked") @db.Decimal(30, 10) // 所有已解锁总和 effectiveContribution Decimal @default(0) @map("effective_contribution") @db.Decimal(30, 10) // personal + unlocked // ========== 解锁条件状态 ========== hasAdopted Boolean @default(false) @map("has_adopted") directReferralAdoptedCount Int @default(0) @map("direct_referral_adopted_count") // 解锁深度:0=未解锁, 5=第1档, 10=第2档, 15=全部解锁 unlockedLevelDepth Int @default(0) @map("unlocked_level_depth") // 解锁加成档位:0=未解锁, 1/2/3=对应档位 unlockedBonusTiers Int @default(0) @map("unlocked_bonus_tiers") // 乐观锁 version Int @default(1) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") @@index([effectiveContribution(sort: Desc)]) @@index([hasAdopted]) @@index([directReferralAdoptedCount]) @@map("contribution_accounts") } // 算力明细表(分类账) // 每笔算力变动都记录在此表,用于审计和追溯 // 状态流转:PENDING -> UNLOCKED -> EXPIRED (或直接 EFFECTIVE -> EXPIRED) model ContributionRecord { id BigInt @id @default(autoincrement()) accountSequence String @map("account_sequence") @db.VarChar(20) // ========== 来源信息(可追溯)========== sourceType String @map("source_type") @db.VarChar(30) // PERSONAL / LEVEL_1~15 / BONUS_TIER_1~3 sourceAdoptionId BigInt @map("source_adoption_id") // 来源认种ID sourceAccountSequence String @map("source_account_sequence") @db.VarChar(20) // 认种人账号 // ========== 计算参数(审计用)========== treeCount Int @map("tree_count") // 认种棵数 baseContribution Decimal @map("base_contribution") @db.Decimal(20, 10) // 基础贡献值/棵 distributionRate Decimal @map("distribution_rate") @db.Decimal(10, 6) // 分配比例 (0.005=0.5%, 0.025=2.5%) levelDepth Int? @map("level_depth") // 层级深度 (1-15) bonusTier Int? @map("bonus_tier") // 加成档位 (1-3) // ========== 金额 ========== amount Decimal @map("amount") @db.Decimal(30, 10) // 算力金额 // ========== 解锁状态 ========== status String @default("PENDING") @map("status") @db.VarChar(20) // PENDING/UNLOCKED/EFFECTIVE unlockedAt DateTime? @map("unlocked_at") // 解锁时间 unlockReason String? @map("unlock_reason") @db.VarChar(200) // 解锁原因描述 // ========== 有效期 ========== effectiveDate DateTime @map("effective_date") @db.Date // 生效日期 expireDate DateTime @map("expire_date") @db.Date // 过期日期 isExpired Boolean @default(false) @map("is_expired") expiredAt DateTime? @map("expired_at") // ========== 备注 ========== remark String? @map("remark") @db.VarChar(500) // 备注说明 createdAt DateTime @default(now()) @map("created_at") @@index([accountSequence, status]) @@index([accountSequence, createdAt(sort: Desc)]) @@index([sourceAdoptionId]) @@index([sourceAccountSequence]) @@index([sourceType]) @@index([status]) @@index([expireDate]) @@index([isExpired]) @@map("contribution_records") } // 解锁事件记录表 // 记录每次解锁的触发原因,便于审计和追溯 model UnlockEvent { id BigInt @id @default(autoincrement()) accountSequence String @map("account_sequence") @db.VarChar(20) // 被解锁的账户 // ========== 触发信息 ========== triggerType String @map("trigger_type") @db.VarChar(30) // SELF_ADOPT / REFERRAL_ADOPT triggerAdoptionId BigInt @map("trigger_adoption_id") // 触发认种的ID triggerAccountSequence String @map("trigger_account_sequence") @db.VarChar(20) // 触发认种的账户 // ========== 解锁内容 ========== unlockType String @map("unlock_type") @db.VarChar(30) // LEVEL_1_5 / LEVEL_6_10 / LEVEL_11_15 / BONUS_TIER_1 / BONUS_TIER_2 / BONUS_TIER_3 unlockCondition String @map("unlock_condition") @db.VarChar(100) // 解锁条件描述,如 "自己认种" / "直推认种人数达到3人" // ========== 解锁前后状态 ========== beforeDirectReferralCount Int @map("before_direct_referral_count") // 解锁前直推认种人数 afterDirectReferralCount Int @map("after_direct_referral_count") // 解锁后直推认种人数 beforeUnlockedLevelDepth Int @map("before_unlocked_level_depth") // 解锁前层级深度 afterUnlockedLevelDepth Int @map("after_unlocked_level_depth") // 解锁后层级深度 beforeUnlockedBonusTiers Int @map("before_unlocked_bonus_tiers") // 解锁前加成档位 afterUnlockedBonusTiers Int @map("after_unlocked_bonus_tiers") // 解锁后加成档位 // ========== 解锁金额 ========== unlockedAmount Decimal @map("unlocked_amount") @db.Decimal(30, 10) // 本次解锁的算力金额 unlockedRecordCount Int @map("unlocked_record_count") // 本次解锁的明细记录数 // ========== 备注 ========== remark String? @map("remark") @db.VarChar(500) createdAt DateTime @default(now()) @map("created_at") @@index([accountSequence]) @@index([triggerAccountSequence]) @@index([triggerAdoptionId]) @@index([unlockType]) @@index([createdAt(sort: Desc)]) @@map("unlock_events") } // 未分配算力记录(归总部) // 当上级未解锁对应层级/加成时,算力暂存于此 model UnallocatedContribution { id BigInt @id @default(autoincrement()) sourceAdoptionId BigInt @map("source_adoption_id") // 来源认种ID sourceAccountSequence String @map("source_account_sequence") @db.VarChar(20) // 认种人账号 // ========== 未分配类型 ========== unallocType String @map("unalloc_type") @db.VarChar(30) // LEVEL_6~15_PENDING / BONUS_TIER_2_PENDING / BONUS_TIER_3_PENDING wouldBeAccountSequence String? @map("would_be_account_sequence") @db.VarChar(20) // 应该归属的账户 levelDepth Int? @map("level_depth") // 层级深度 bonusTier Int? @map("bonus_tier") // 加成档位 amount Decimal @map("amount") @db.Decimal(30, 10) reason String? @db.VarChar(200) // 未分配原因 // ========== 分配状态 ========== status String @default("PENDING") @map("status") @db.VarChar(20) // PENDING / ALLOCATED_TO_USER / ALLOCATED_TO_HQ allocatedAt DateTime? @map("allocated_at") allocatedToAccountSequence String? @map("allocated_to_account_sequence") @db.VarChar(20) // 最终分配给的账户 // ========== 有效期 ========== effectiveDate DateTime @map("effective_date") @db.Date expireDate DateTime @map("expire_date") @db.Date createdAt DateTime @default(now()) @map("created_at") @@index([sourceAdoptionId]) @@index([wouldBeAccountSequence]) @@index([unallocType]) @@index([status]) @@map("unallocated_contributions") } // 系统账户(运营/省/市/总部) model SystemAccount { id BigInt @id @default(autoincrement()) accountType String @unique @map("account_type") @db.VarChar(20) // OPERATION / PROVINCE / CITY / HEADQUARTERS name String @db.VarChar(100) contributionBalance Decimal @default(0) @map("contribution_balance") @db.Decimal(30, 10) contributionNeverExpires Boolean @default(false) @map("contribution_never_expires") version Int @default(1) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") records SystemContributionRecord[] @@map("system_accounts") } // 系统账户算力明细 model SystemContributionRecord { id BigInt @id @default(autoincrement()) systemAccountId BigInt @map("system_account_id") sourceAdoptionId BigInt @map("source_adoption_id") sourceAccountSequence String @map("source_account_sequence") @db.VarChar(20) distributionRate Decimal @map("distribution_rate") @db.Decimal(10, 6) amount Decimal @map("amount") @db.Decimal(30, 10) effectiveDate DateTime @map("effective_date") @db.Date expireDate DateTime? @map("expire_date") @db.Date isExpired Boolean @default(false) @map("is_expired") createdAt DateTime @default(now()) @map("created_at") systemAccount SystemAccount @relation(fields: [systemAccountId], references: [id]) @@index([systemAccountId]) @@index([sourceAdoptionId]) @@map("system_contribution_records") } // ============================================ // 快照与统计表 // ============================================ // 每日算力快照(用于挖矿分配计算) model DailyContributionSnapshot { id BigInt @id @default(autoincrement()) snapshotDate DateTime @map("snapshot_date") @db.Date accountSequence String @map("account_sequence") @db.VarChar(20) effectiveContribution Decimal @map("effective_contribution") @db.Decimal(30, 10) networkTotalContribution Decimal @map("network_total_contribution") @db.Decimal(30, 10) contributionRatio Decimal @map("contribution_ratio") @db.Decimal(30, 18) createdAt DateTime @default(now()) @map("created_at") @@unique([snapshotDate, accountSequence]) @@index([snapshotDate]) @@index([accountSequence]) @@map("daily_contribution_snapshots") } // 用户团队统计(缓存,定期更新) model UserTeamStats { id BigInt @id @default(autoincrement()) accountSequence String @map("account_sequence") @db.VarChar(20) statsDate DateTime @map("stats_date") @db.Date // 各级认种统计 level1Trees Int @default(0) @map("level_1_trees") level2Trees Int @default(0) @map("level_2_trees") level3Trees Int @default(0) @map("level_3_trees") level4Trees Int @default(0) @map("level_4_trees") level5Trees Int @default(0) @map("level_5_trees") level6Trees Int @default(0) @map("level_6_trees") level7Trees Int @default(0) @map("level_7_trees") level8Trees Int @default(0) @map("level_8_trees") level9Trees Int @default(0) @map("level_9_trees") level10Trees Int @default(0) @map("level_10_trees") level11Trees Int @default(0) @map("level_11_trees") level12Trees Int @default(0) @map("level_12_trees") level13Trees Int @default(0) @map("level_13_trees") level14Trees Int @default(0) @map("level_14_trees") level15Trees Int @default(0) @map("level_15_trees") totalTeamTrees Int @default(0) @map("total_team_trees") directAdoptedReferrals Int @default(0) @map("direct_adopted_referrals") createdAt DateTime @default(now()) @map("created_at") @@unique([accountSequence, statsDate]) @@index([accountSequence]) @@index([statsDate]) @@map("user_team_stats") } // ============================================ // CDC 同步状态追踪 // ============================================ // CDC 同步进度表 model CdcSyncProgress { id BigInt @id @default(autoincrement()) sourceTopic String @unique @map("source_topic") @db.VarChar(100) lastSequenceNum BigInt @default(0) @map("last_sequence_num") lastSyncedAt DateTime? @map("last_synced_at") updatedAt DateTime @updatedAt @map("updated_at") @@map("cdc_sync_progress") } // 已处理 CDC 事件表(幂等性) // 使用 (sourceTopic, offset) 作为复合唯一键 // 这是事务性幂等消费的关键:在同一事务中插入此记录 + 执行业务逻辑 model ProcessedCdcEvent { id BigInt @id @default(autoincrement()) sourceTopic String @map("source_topic") @db.VarChar(200) // CDC topic 名称 offset BigInt @map("offset") // Kafka offset 作为唯一标识 tableName String @map("table_name") @db.VarChar(100) // 表名 operation String @map("operation") @db.VarChar(10) // c/u/d/r processedAt DateTime @default(now()) @map("processed_at") @@unique([sourceTopic, offset]) @@index([processedAt]) @@map("processed_cdc_events") } // 已处理 Outbox 事件表(用于 2.0 服务间同步) model ProcessedEvent { id BigInt @id @default(autoincrement()) eventId String @map("event_id") @db.VarChar(100) eventType String @map("event_type") @db.VarChar(50) sourceService String @map("source_service") @db.VarChar(100) processedAt DateTime @default(now()) @map("processed_at") @@unique([sourceService, eventId]) @@index([eventType]) @@index([processedAt]) @@map("processed_events") } // ============================================ // 配置表 // ============================================ // 贡献值递增配置 model ContributionConfig { id BigInt @id @default(autoincrement()) baseContribution Decimal @default(22617) @map("base_contribution") @db.Decimal(20, 10) incrementPercentage Decimal @default(0.003) @map("increment_percentage") @db.Decimal(10, 6) unitSize Int @default(100) @map("unit_size") startTreeNumber Int @default(1000) @map("start_tree_number") isActive Boolean @default(true) @map("is_active") createdAt DateTime @default(now()) @map("created_at") @@index([isActive]) @@map("contribution_configs") } // 分配比例配置 model DistributionRateConfig { id BigInt @id @default(autoincrement()) rateType String @unique @map("rate_type") @db.VarChar(30) rateValue Decimal @map("rate_value") @db.Decimal(10, 6) description String? @db.VarChar(100) isActive Boolean @default(true) @map("is_active") createdAt DateTime @default(now()) @map("created_at") @@index([isActive]) @@map("distribution_rate_configs") } // 全网认种进度表(单行记录,实时更新) // 记录当前全网累计认种数量和对应的算力系数 model NetworkAdoptionProgress { id BigInt @id @default(autoincrement()) // ========== 全网累计认种统计 ========== totalTreeCount Int @default(0) @map("total_tree_count") // 全网累计认种棵数 totalAdoptionOrders Int @default(0) @map("total_adoption_orders") // 全网累计认种订单数 totalAdoptedUsers Int @default(0) @map("total_adopted_users") // 全网累计认种用户数 // ========== 当前算力系数 ========== // 基础值: 22617 (从第1棵到第999棵) // 从第1000棵开始,每100棵为1个单位,每个单位递增 0.3% // currentMultiplier = 1 + (currentUnit * 0.003) // currentContributionPerTree = baseContribution * currentMultiplier currentUnit Int @default(0) @map("current_unit") // 当前单位数 (0表示还没到1000棵) currentMultiplier Decimal @default(1.0) @map("current_multiplier") @db.Decimal(10, 6) // 当前系数 (1.000, 1.003, 1.006...) currentContributionPerTree Decimal @default(22617) @map("current_contribution_per_tree") @db.Decimal(20, 10) // 当前每棵树贡献值 // ========== 下一个单位的触发点 ========== nextUnitTreeCount Int @default(1000) @map("next_unit_tree_count") // 下一个单位触发的棵数 // ========== 最后更新信息 ========== lastAdoptionId BigInt? @map("last_adoption_id") // 最后处理的认种ID lastAdoptionDate DateTime? @map("last_adoption_date") @db.Date // 最后认种日期 updatedAt DateTime @updatedAt @map("updated_at") createdAt DateTime @default(now()) @map("created_at") @@map("network_adoption_progress") } // 每日算力系数快照表 // 记录每天的算力系数,确保同一天认种的用户使用相同系数 model DailyContributionRate { id BigInt @id @default(autoincrement()) // ========== 日期 ========== effectiveDate DateTime @unique @map("effective_date") @db.Date // 生效日期 // ========== 当日起始状态 ========== startTreeCount Int @default(0) @map("start_tree_count") // 当日开始时的全网棵数 startUnit Int @default(0) @map("start_unit") // 当日开始时的单位数 startMultiplier Decimal @default(1.0) @map("start_multiplier") @db.Decimal(10, 6) // 当日开始时的系数 contributionPerTree Decimal @map("contribution_per_tree") @db.Decimal(20, 10) // 当日每棵树贡献值(同一天内不变) // ========== 当日结束状态 ========== endTreeCount Int? @map("end_tree_count") // 当日结束时的全网棵数 endUnit Int? @map("end_unit") // 当日结束时的单位数 endMultiplier Decimal? @map("end_multiplier") @db.Decimal(10, 6) // 当日结束时的系数 // ========== 当日统计 ========== dailyTreeCount Int @default(0) @map("daily_tree_count") // 当日新增认种棵数 dailyAdoptionOrders Int @default(0) @map("daily_adoption_orders") // 当日新增认种订单数 dailyAdoptedUsers Int @default(0) @map("daily_adopted_users") // 当日新增认种用户数 // ========== 状态 ========== isClosed Boolean @default(false) @map("is_closed") // 是否已结算(日终处理后置为true) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") @@index([effectiveDate]) @@index([isClosed]) @@map("daily_contribution_rates") } // ============================================ // Outbox 事件表(可靠事件发布) // ============================================ 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) 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") }