diff --git a/backend/services/contribution-service/prisma/migrations/20260111100000_add_pending_unlock_fields/migration.sql b/backend/services/contribution-service/prisma/migrations/20260111100000_add_pending_unlock_fields/migration.sql new file mode 100644 index 00000000..2388dba2 --- /dev/null +++ b/backend/services/contribution-service/prisma/migrations/20260111100000_add_pending_unlock_fields/migration.sql @@ -0,0 +1,158 @@ +-- ============================================ +-- Migration: 添加待解锁算力字段和解锁事件记录 +-- +-- 业务背景: +-- 1. 15级层级算力:每级0.5%,共7.5% +-- - 自己认种解锁1-5级 +-- - 直推≥3人认种解锁6-10级 +-- - 直推≥5人认种解锁11-15级 +-- 2. 3档加成算力:每档2.5%,共7.5% +-- - 自己认种解锁第1档 +-- - 直推≥2人认种解锁第2档 +-- - 直推≥4人认种解锁第3档 +-- ============================================ + +-- 1. 删除旧字段(如果存在) +ALTER TABLE "contribution_accounts" DROP COLUMN IF EXISTS "team_level_contribution"; +ALTER TABLE "contribution_accounts" DROP COLUMN IF EXISTS "team_bonus_contribution"; +ALTER TABLE "contribution_accounts" DROP COLUMN IF EXISTS "total_contribution"; + +-- 2. 添加15级层级待解锁字段 +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "level_1_pending" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "level_2_pending" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "level_3_pending" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "level_4_pending" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "level_5_pending" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "level_6_pending" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "level_7_pending" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "level_8_pending" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "level_9_pending" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "level_10_pending" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "level_11_pending" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "level_12_pending" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "level_13_pending" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "level_14_pending" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "level_15_pending" DECIMAL(30,10) DEFAULT 0; + +-- 3. 添加3档加成待解锁字段 +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "bonus_tier_1_pending" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "bonus_tier_2_pending" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "bonus_tier_3_pending" DECIMAL(30,10) DEFAULT 0; + +-- 4. 添加汇总字段 +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "total_level_pending" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "total_bonus_pending" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "total_pending" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "total_unlocked" DECIMAL(30,10) DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "effective_contribution" DECIMAL(30,10) DEFAULT 0; + +-- 5. 添加解锁条件状态字段 +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "has_adopted" BOOLEAN DEFAULT false; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "direct_referral_adopted_count" INTEGER DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "unlocked_level_depth" INTEGER DEFAULT 0; +ALTER TABLE "contribution_accounts" ADD COLUMN IF NOT EXISTS "unlocked_bonus_tiers" INTEGER DEFAULT 0; + +-- 6. 更新 contribution_records 表添加状态字段 +ALTER TABLE "contribution_records" ADD COLUMN IF NOT EXISTS "status" VARCHAR(20) DEFAULT 'PENDING'; +ALTER TABLE "contribution_records" ADD COLUMN IF NOT EXISTS "unlocked_at" TIMESTAMP(3); +ALTER TABLE "contribution_records" ADD COLUMN IF NOT EXISTS "unlock_reason" VARCHAR(200); +ALTER TABLE "contribution_records" ADD COLUMN IF NOT EXISTS "remark" VARCHAR(500); + +-- 7. 创建索引(contribution_accounts) +CREATE INDEX IF NOT EXISTS "contribution_accounts_has_adopted_idx" ON "contribution_accounts"("has_adopted"); +CREATE INDEX IF NOT EXISTS "contribution_accounts_direct_referral_adopted_count_idx" ON "contribution_accounts"("direct_referral_adopted_count"); +CREATE INDEX IF NOT EXISTS "contribution_accounts_effective_contribution_idx" ON "contribution_accounts"("effective_contribution" DESC); + +-- 8. 创建索引(contribution_records) +CREATE INDEX IF NOT EXISTS "contribution_records_status_idx" ON "contribution_records"("status"); +CREATE INDEX IF NOT EXISTS "contribution_records_account_status_idx" ON "contribution_records"("account_sequence", "status"); + +-- 9. 创建解锁事件记录表 +CREATE TABLE IF NOT EXISTS "unlock_events" ( + "id" BIGSERIAL NOT NULL, + "account_sequence" VARCHAR(20) NOT NULL, + + -- 触发信息 + "trigger_type" VARCHAR(30) NOT NULL, + "trigger_adoption_id" BIGINT NOT NULL, + "trigger_account_sequence" VARCHAR(20) NOT NULL, + + -- 解锁内容 + "unlock_type" VARCHAR(30) NOT NULL, + "unlock_condition" VARCHAR(100) NOT NULL, + + -- 解锁前后状态 + "before_direct_referral_count" INTEGER NOT NULL, + "after_direct_referral_count" INTEGER NOT NULL, + "before_unlocked_level_depth" INTEGER NOT NULL, + "after_unlocked_level_depth" INTEGER NOT NULL, + "before_unlocked_bonus_tiers" INTEGER NOT NULL, + "after_unlocked_bonus_tiers" INTEGER NOT NULL, + + -- 解锁金额 + "unlocked_amount" DECIMAL(30,10) NOT NULL, + "unlocked_record_count" INTEGER NOT NULL, + + -- 备注 + "remark" VARCHAR(500), + + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "unlock_events_pkey" PRIMARY KEY ("id") +); + +-- 10. 创建解锁事件索引 +CREATE INDEX IF NOT EXISTS "unlock_events_account_sequence_idx" ON "unlock_events"("account_sequence"); +CREATE INDEX IF NOT EXISTS "unlock_events_trigger_account_sequence_idx" ON "unlock_events"("trigger_account_sequence"); +CREATE INDEX IF NOT EXISTS "unlock_events_trigger_adoption_id_idx" ON "unlock_events"("trigger_adoption_id"); +CREATE INDEX IF NOT EXISTS "unlock_events_unlock_type_idx" ON "unlock_events"("unlock_type"); +CREATE INDEX IF NOT EXISTS "unlock_events_created_at_idx" ON "unlock_events"("created_at" DESC); + +-- 11. 更新 unallocated_contributions 表 +ALTER TABLE "unallocated_contributions" ADD COLUMN IF NOT EXISTS "bonus_tier" INTEGER; +ALTER TABLE "unallocated_contributions" ADD COLUMN IF NOT EXISTS "status" VARCHAR(20) DEFAULT 'PENDING'; +ALTER TABLE "unallocated_contributions" ADD COLUMN IF NOT EXISTS "allocated_to_account_sequence" VARCHAR(20); + +-- 删除旧的列(如果存在) +ALTER TABLE "unallocated_contributions" DROP COLUMN IF EXISTS "allocated_to_headquarters"; + +-- 12. 更新索引 +DROP INDEX IF EXISTS "unallocated_contributions_allocated_to_headquarters_idx"; +CREATE INDEX IF NOT EXISTS "unallocated_contributions_status_idx" ON "unallocated_contributions"("status"); +CREATE INDEX IF NOT EXISTS "unallocated_contributions_would_be_account_sequence_idx" ON "unallocated_contributions"("would_be_account_sequence"); + +-- 13. 添加注释 +COMMENT ON TABLE "unlock_events" IS '解锁事件记录表 - 记录每次算力解锁的触发原因和结果'; +COMMENT ON COLUMN "unlock_events"."trigger_type" IS '触发类型: SELF_ADOPT(自己认种) / REFERRAL_ADOPT(直推认种)'; +COMMENT ON COLUMN "unlock_events"."unlock_type" IS '解锁类型: LEVEL_1_5/LEVEL_6_10/LEVEL_11_15/BONUS_TIER_1/BONUS_TIER_2/BONUS_TIER_3'; +COMMENT ON COLUMN "unlock_events"."unlock_condition" IS '解锁条件描述,如"自己认种"、"直推认种人数达到3人"'; + +COMMENT ON COLUMN "contribution_accounts"."level_1_pending" IS '第1级待解锁算力 (0.5%)'; +COMMENT ON COLUMN "contribution_accounts"."level_2_pending" IS '第2级待解锁算力 (0.5%)'; +COMMENT ON COLUMN "contribution_accounts"."level_3_pending" IS '第3级待解锁算力 (0.5%)'; +COMMENT ON COLUMN "contribution_accounts"."level_4_pending" IS '第4级待解锁算力 (0.5%)'; +COMMENT ON COLUMN "contribution_accounts"."level_5_pending" IS '第5级待解锁算力 (0.5%)'; +COMMENT ON COLUMN "contribution_accounts"."level_6_pending" IS '第6级待解锁算力 (0.5%) - 直推≥3人认种解锁'; +COMMENT ON COLUMN "contribution_accounts"."level_7_pending" IS '第7级待解锁算力 (0.5%) - 直推≥3人认种解锁'; +COMMENT ON COLUMN "contribution_accounts"."level_8_pending" IS '第8级待解锁算力 (0.5%) - 直推≥3人认种解锁'; +COMMENT ON COLUMN "contribution_accounts"."level_9_pending" IS '第9级待解锁算力 (0.5%) - 直推≥3人认种解锁'; +COMMENT ON COLUMN "contribution_accounts"."level_10_pending" IS '第10级待解锁算力 (0.5%) - 直推≥3人认种解锁'; +COMMENT ON COLUMN "contribution_accounts"."level_11_pending" IS '第11级待解锁算力 (0.5%) - 直推≥5人认种解锁'; +COMMENT ON COLUMN "contribution_accounts"."level_12_pending" IS '第12级待解锁算力 (0.5%) - 直推≥5人认种解锁'; +COMMENT ON COLUMN "contribution_accounts"."level_13_pending" IS '第13级待解锁算力 (0.5%) - 直推≥5人认种解锁'; +COMMENT ON COLUMN "contribution_accounts"."level_14_pending" IS '第14级待解锁算力 (0.5%) - 直推≥5人认种解锁'; +COMMENT ON COLUMN "contribution_accounts"."level_15_pending" IS '第15级待解锁算力 (0.5%) - 直推≥5人认种解锁'; + +COMMENT ON COLUMN "contribution_accounts"."bonus_tier_1_pending" IS '第1档加成待解锁算力 (2.5%) - 自己认种解锁'; +COMMENT ON COLUMN "contribution_accounts"."bonus_tier_2_pending" IS '第2档加成待解锁算力 (2.5%) - 直推≥2人认种解锁'; +COMMENT ON COLUMN "contribution_accounts"."bonus_tier_3_pending" IS '第3档加成待解锁算力 (2.5%) - 直推≥4人认种解锁'; + +COMMENT ON COLUMN "contribution_accounts"."unlocked_level_depth" IS '已解锁层级深度: 0=未解锁, 5=1-5级, 10=1-10级, 15=全部'; +COMMENT ON COLUMN "contribution_accounts"."unlocked_bonus_tiers" IS '已解锁加成档位数: 0/1/2/3'; +COMMENT ON COLUMN "contribution_accounts"."effective_contribution" IS '有效算力 = 个人算力 + 已解锁算力'; + +COMMENT ON COLUMN "contribution_records"."status" IS '状态: PENDING(待解锁)/UNLOCKED(已解锁)/EFFECTIVE(已生效)'; +COMMENT ON COLUMN "contribution_records"."source_type" IS '来源类型: PERSONAL/LEVEL_1~15/BONUS_TIER_1~3'; + +-- 注意:挖矿收益分配相关的表(mining_reward_allocations, daily_mining_reward_summaries, headquarters_pending_rewards) +-- 已移至 mining-service,由 mining-service 负责创建和管理 diff --git a/backend/services/contribution-service/prisma/schema.prisma b/backend/services/contribution-service/prisma/schema.prisma index 1c1fff73..da3fef7e 100644 --- a/backend/services/contribution-service/prisma/schema.prisma +++ b/backend/services/contribution-service/prisma/schema.prisma @@ -13,11 +13,11 @@ datasource db { // 同步的用户数据 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) + 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") @@ -29,19 +29,19 @@ model SyncedUser { createdAt DateTime @default(now()) @map("created_at") - @@map("synced_users") @@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(20) + 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(20) // 贡献值计算参数(从认种时的配置) contributionPerTree Decimal @map("contribution_per_tree") @db.Decimal(20, 10) @@ -56,17 +56,17 @@ model SyncedAdoption { createdAt DateTime @default(now()) @map("created_at") - @@map("synced_adoptions") @@index([accountSequence]) @@index([adoptionDate]) @@index([contributionDistributed]) + @@map("synced_adoptions") } // 同步的推荐关系数据 model SyncedReferral { - id BigInt @id @default(autoincrement()) - accountSequence String @unique @map("account_sequence") @db.VarChar(20) - referrerAccountSequence String? @map("referrer_account_sequence") @db.VarChar(20) + id BigInt @id @default(autoincrement()) + accountSequence String @unique @map("account_sequence") @db.VarChar(20) + referrerAccountSequence String? @map("referrer_account_sequence") @db.VarChar(20) // 预计算的层级路径(便于快速查询上下级) ancestorPath String? @map("ancestor_path") @db.Text @@ -78,8 +78,8 @@ model SyncedReferral { createdAt DateTime @default(now()) @map("created_at") - @@map("synced_referrals") @@index([referrerAccountSequence]) + @@map("synced_referrals") } // ============================================ @@ -87,23 +87,64 @@ model SyncedReferral { // ============================================ // 算力账户表(汇总) +// 设计说明: +// - 个人算力:自己认种,立即生效 +// - 层级算力:下级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) - teamLevelContribution Decimal @default(0) @map("team_level_contribution") @db.Decimal(30, 10) - teamBonusContribution Decimal @default(0) @map("team_bonus_contribution") @db.Decimal(30, 10) - totalContribution Decimal @default(0) @map("total_contribution") @db.Decimal(30, 10) - effectiveContribution Decimal @default(0) @map("effective_contribution") @db.Decimal(30, 10) + // ========== 个人算力(立即生效)========== + personalContribution Decimal @default(0) @map("personal_contribution") @db.Decimal(30, 10) - // 用户条件(决定能获得多少团队算力) - hasAdopted Boolean @default(false) @map("has_adopted") - directReferralAdoptedCount Int @default(0) @map("direct_referral_adopted_count") + // ========== 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") // 乐观锁 @@ -112,80 +153,139 @@ model ContributionAccount { createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") - @@map("contribution_accounts") - @@index([totalContribution(sort: Desc)]) @@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 / TEAM_LEVEL / TEAM_BONUS - sourceAdoptionId BigInt @map("source_adoption_id") - sourceAccountSequence String @map("source_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) - levelDepth Int? @map("level_depth") - bonusTier Int? @map("bonus_tier") + // ========== 计算参数(审计用)========== + 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) + // ========== 金额 ========== + 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") + // ========== 解锁状态 ========== + 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") - @@map("contribution_records") + @@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") - sourceAccountSequence String @map("source_account_sequence") @db.VarChar(20) + 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_OVERFLOW / BONUS_TIER_1/2/3 - wouldBeAccountSequence String? @map("would_be_account_sequence") @db.VarChar(20) - levelDepth Int? @map("level_depth") + // ========== 未分配类型 ========== + 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) + amount Decimal @map("amount") @db.Decimal(30, 10) + reason String? @db.VarChar(200) // 未分配原因 - // 归总部后的处理 - allocatedToHeadquarters Boolean @default(false) @map("allocated_to_headquarters") - allocatedAt DateTime? @map("allocated_at") + // ========== 分配状态 ========== + 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") - @@map("unallocated_contributions") @@index([sourceAdoptionId]) + @@index([wouldBeAccountSequence]) @@index([unallocType]) - @@index([allocatedToHeadquarters]) + @@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 + 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) @@ -219,9 +319,9 @@ model SystemContributionRecord { systemAccount SystemAccount @relation(fields: [systemAccountId], references: [id]) - @@map("system_contribution_records") @@index([systemAccountId]) @@index([sourceAdoptionId]) + @@map("system_contribution_records") } // ============================================ @@ -230,20 +330,20 @@ model SystemContributionRecord { // 每日算力快照(用于挖矿分配计算) model DailyContributionSnapshot { - id BigInt @id @default(autoincrement()) - snapshotDate DateTime @map("snapshot_date") @db.Date - accountSequence String @map("account_sequence") @db.VarChar(20) + 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) + 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]) - @@map("daily_contribution_snapshots") @@index([snapshotDate]) @@index([accountSequence]) + @@map("daily_contribution_snapshots") } // 用户团队统计(缓存,定期更新) @@ -269,15 +369,15 @@ model UserTeamStats { 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") + 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]) - @@map("user_team_stats") @@index([accountSequence]) @@index([statsDate]) + @@map("user_team_stats") } // ============================================ @@ -286,10 +386,10 @@ model UserTeamStats { // 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") + 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") @@ -298,16 +398,16 @@ model CdcSyncProgress { // 已处理事件表(幂等性) model ProcessedEvent { - id BigInt @id @default(autoincrement()) - eventId String @unique @map("event_id") @db.VarChar(100) - eventType String @map("event_type") @db.VarChar(50) - sourceService String? @map("source_service") @db.VarChar(50) + id BigInt @id @default(autoincrement()) + eventId String @unique @map("event_id") @db.VarChar(100) + eventType String @map("event_type") @db.VarChar(50) + sourceService String? @map("source_service") @db.VarChar(50) processedAt DateTime @default(now()) @map("processed_at") - @@map("processed_events") @@index([eventType]) @@index([processedAt]) + @@map("processed_events") } // ============================================ @@ -318,17 +418,17 @@ model ProcessedEvent { 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") + 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") - @@map("contribution_configs") @@index([isActive]) + @@map("contribution_configs") } // 分配比例配置 @@ -343,8 +443,8 @@ model DistributionRateConfig { createdAt DateTime @default(now()) @map("created_at") - @@map("distribution_rate_configs") @@index([isActive]) + @@map("distribution_rate_configs") } // ============================================ @@ -371,9 +471,9 @@ model OutboxEvent { publishedAt DateTime? @map("published_at") nextRetryAt DateTime? @map("next_retry_at") - @@map("outbox_events") @@index([status, createdAt]) @@index([status, nextRetryAt]) @@index([aggregateType, aggregateId]) @@index([topic]) + @@map("outbox_events") } diff --git a/backend/services/contribution-service/src/api/dto/response/contribution-ranking.response.ts b/backend/services/contribution-service/src/api/dto/response/contribution-ranking.response.ts index d4f2ef7b..5f089994 100644 --- a/backend/services/contribution-service/src/api/dto/response/contribution-ranking.response.ts +++ b/backend/services/contribution-service/src/api/dto/response/contribution-ranking.response.ts @@ -7,14 +7,17 @@ export class ContributionRankingItemResponse { @ApiProperty({ description: '账户序号' }) accountSequence: string; - @ApiProperty({ description: '总算力' }) - totalContribution: string; + @ApiProperty({ description: '有效算力(个人 + 已解锁)' }) + effectiveContribution: string; @ApiProperty({ description: '个人算力' }) personalContribution: string; - @ApiProperty({ description: '团队算力' }) - teamContribution: string; + @ApiProperty({ description: '待解锁算力总和' }) + totalPending: string; + + @ApiProperty({ description: '已解锁算力总和' }) + totalUnlocked: string; } export class ContributionRankingResponse { @@ -26,8 +29,8 @@ export class UserRankResponse { @ApiProperty({ description: '排名', nullable: true }) rank: number | null; - @ApiProperty({ description: '总算力' }) - totalContribution: string; + @ApiProperty({ description: '有效算力' }) + effectiveContribution: string; @ApiProperty({ description: '百分位', nullable: true }) percentile: number | null; diff --git a/backend/services/contribution-service/src/application/queries/get-contribution-ranking.query.ts b/backend/services/contribution-service/src/application/queries/get-contribution-ranking.query.ts index fffd4034..c1b3dbe2 100644 --- a/backend/services/contribution-service/src/application/queries/get-contribution-ranking.query.ts +++ b/backend/services/contribution-service/src/application/queries/get-contribution-ranking.query.ts @@ -5,9 +5,10 @@ import { RedisService } from '../../infrastructure/redis/redis.service'; export interface ContributionRankingDto { rank: number; accountSequence: string; - totalContribution: string; + effectiveContribution: string; personalContribution: string; - teamContribution: string; + totalPending: string; + totalUnlocked: string; } @Injectable() @@ -36,9 +37,10 @@ export class GetContributionRankingQuery { const ranking: ContributionRankingDto[] = topContributors.map((account, index) => ({ rank: index + 1, accountSequence: account.accountSequence, - totalContribution: account.totalContribution.value.toString(), + effectiveContribution: account.effectiveContribution.value.toString(), personalContribution: account.personalContribution.value.toString(), - teamContribution: account.teamLevelContribution.add(account.teamBonusContribution).value.toString(), + totalPending: account.totalPending.value.toString(), + totalUnlocked: account.totalUnlocked.value.toString(), })); // 缓存结果 @@ -52,7 +54,7 @@ export class GetContributionRankingQuery { */ async getUserRank(accountSequence: string): Promise<{ rank: number | null; - totalContribution: string; + effectiveContribution: string; percentile: number | null; } | null> { const account = await this.accountRepository.findByAccountSequence(accountSequence); @@ -67,7 +69,7 @@ export class GetContributionRankingQuery { return { rank: rank !== null ? rank + 1 : null, - totalContribution: account.totalContribution.value.toString(), + effectiveContribution: account.effectiveContribution.value.toString(), percentile: rank !== null && totalAccounts > 0 ? ((totalAccounts - rank) / totalAccounts) * 100 : null, }; } diff --git a/backend/services/contribution-service/src/domain/aggregates/contribution-account.aggregate.ts b/backend/services/contribution-service/src/domain/aggregates/contribution-account.aggregate.ts index 204db251..3cffcc6d 100644 --- a/backend/services/contribution-service/src/domain/aggregates/contribution-account.aggregate.ts +++ b/backend/services/contribution-service/src/domain/aggregates/contribution-account.aggregate.ts @@ -6,26 +6,68 @@ import { ContributionAmount } from '../value-objects/contribution-amount.vo'; */ export enum ContributionSourceType { PERSONAL = 'PERSONAL', // 来自自己认种 - TEAM_LEVEL = 'TEAM_LEVEL', // 来自团队层级 - TEAM_BONUS = 'TEAM_BONUS', // 来自团队额外奖励 + TEAM_LEVEL = 'TEAM_LEVEL', // 来自团队层级 (1-15级) + TEAM_BONUS = 'TEAM_BONUS', // 来自团队加成奖励 (3档) } /** * 算力账户聚合根 * 管理用户的贡献值/算力 + * + * 设计说明: + * - 个人算力:自己认种,立即生效 + * - 层级算力:下级1-15层认种的分成,每层0.5%,共7.5% + * - 加成算力:团队加成,3档各2.5%,共7.5% + * + * 解锁规则: + * - 自己认种:解锁层级1-5 + 加成第1档 + * - 直推≥2人认种:解锁加成第2档 + * - 直推≥3人认种:解锁层级6-10 + * - 直推≥4人认种:解锁加成第3档 + * - 直推≥5人认种:解锁层级11-15 */ export class ContributionAccountAggregate { private _id: bigint | null; private _accountSequence: string; + + // ========== 个人算力(立即生效)========== private _personalContribution: ContributionAmount; - private _teamLevelContribution: ContributionAmount; - private _teamBonusContribution: ContributionAmount; - private _totalContribution: ContributionAmount; + + // ========== 15级层级待解锁算力 ========== + private _level1Pending: ContributionAmount; + private _level2Pending: ContributionAmount; + private _level3Pending: ContributionAmount; + private _level4Pending: ContributionAmount; + private _level5Pending: ContributionAmount; + private _level6Pending: ContributionAmount; + private _level7Pending: ContributionAmount; + private _level8Pending: ContributionAmount; + private _level9Pending: ContributionAmount; + private _level10Pending: ContributionAmount; + private _level11Pending: ContributionAmount; + private _level12Pending: ContributionAmount; + private _level13Pending: ContributionAmount; + private _level14Pending: ContributionAmount; + private _level15Pending: ContributionAmount; + + // ========== 3档加成待解锁算力 ========== + private _bonusTier1Pending: ContributionAmount; + private _bonusTier2Pending: ContributionAmount; + private _bonusTier3Pending: ContributionAmount; + + // ========== 汇总字段 ========== + private _totalLevelPending: ContributionAmount; + private _totalBonusPending: ContributionAmount; + private _totalPending: ContributionAmount; + private _totalUnlocked: ContributionAmount; private _effectiveContribution: ContributionAmount; + + // ========== 解锁条件状态 ========== private _hasAdopted: boolean; private _directReferralAdoptedCount: number; - private _unlockedLevelDepth: number; - private _unlockedBonusTiers: number; + private _unlockedLevelDepth: number; // 0, 5, 10, 15 + private _unlockedBonusTiers: number; // 0, 1, 2, 3 + private _version: number; private _createdAt: Date; private _updatedAt: Date; @@ -34,9 +76,28 @@ export class ContributionAccountAggregate { id?: bigint | null; accountSequence: string; personalContribution?: ContributionAmount; - teamLevelContribution?: ContributionAmount; - teamBonusContribution?: ContributionAmount; - totalContribution?: ContributionAmount; + level1Pending?: ContributionAmount; + level2Pending?: ContributionAmount; + level3Pending?: ContributionAmount; + level4Pending?: ContributionAmount; + level5Pending?: ContributionAmount; + level6Pending?: ContributionAmount; + level7Pending?: ContributionAmount; + level8Pending?: ContributionAmount; + level9Pending?: ContributionAmount; + level10Pending?: ContributionAmount; + level11Pending?: ContributionAmount; + level12Pending?: ContributionAmount; + level13Pending?: ContributionAmount; + level14Pending?: ContributionAmount; + level15Pending?: ContributionAmount; + bonusTier1Pending?: ContributionAmount; + bonusTier2Pending?: ContributionAmount; + bonusTier3Pending?: ContributionAmount; + totalLevelPending?: ContributionAmount; + totalBonusPending?: ContributionAmount; + totalPending?: ContributionAmount; + totalUnlocked?: ContributionAmount; effectiveContribution?: ContributionAmount; hasAdopted?: boolean; directReferralAdoptedCount?: number; @@ -48,11 +109,38 @@ export class ContributionAccountAggregate { }) { this._id = props.id ?? null; this._accountSequence = props.accountSequence; + this._personalContribution = props.personalContribution ?? ContributionAmount.zero(); - this._teamLevelContribution = props.teamLevelContribution ?? ContributionAmount.zero(); - this._teamBonusContribution = props.teamBonusContribution ?? ContributionAmount.zero(); - this._totalContribution = props.totalContribution ?? ContributionAmount.zero(); + + // 15级待解锁 + this._level1Pending = props.level1Pending ?? ContributionAmount.zero(); + this._level2Pending = props.level2Pending ?? ContributionAmount.zero(); + this._level3Pending = props.level3Pending ?? ContributionAmount.zero(); + this._level4Pending = props.level4Pending ?? ContributionAmount.zero(); + this._level5Pending = props.level5Pending ?? ContributionAmount.zero(); + this._level6Pending = props.level6Pending ?? ContributionAmount.zero(); + this._level7Pending = props.level7Pending ?? ContributionAmount.zero(); + this._level8Pending = props.level8Pending ?? ContributionAmount.zero(); + this._level9Pending = props.level9Pending ?? ContributionAmount.zero(); + this._level10Pending = props.level10Pending ?? ContributionAmount.zero(); + this._level11Pending = props.level11Pending ?? ContributionAmount.zero(); + this._level12Pending = props.level12Pending ?? ContributionAmount.zero(); + this._level13Pending = props.level13Pending ?? ContributionAmount.zero(); + this._level14Pending = props.level14Pending ?? ContributionAmount.zero(); + this._level15Pending = props.level15Pending ?? ContributionAmount.zero(); + + // 3档加成待解锁 + this._bonusTier1Pending = props.bonusTier1Pending ?? ContributionAmount.zero(); + this._bonusTier2Pending = props.bonusTier2Pending ?? ContributionAmount.zero(); + this._bonusTier3Pending = props.bonusTier3Pending ?? ContributionAmount.zero(); + + // 汇总字段 + this._totalLevelPending = props.totalLevelPending ?? ContributionAmount.zero(); + this._totalBonusPending = props.totalBonusPending ?? ContributionAmount.zero(); + this._totalPending = props.totalPending ?? ContributionAmount.zero(); + this._totalUnlocked = props.totalUnlocked ?? ContributionAmount.zero(); this._effectiveContribution = props.effectiveContribution ?? ContributionAmount.zero(); + this._hasAdopted = props.hasAdopted ?? false; this._directReferralAdoptedCount = props.directReferralAdoptedCount ?? 0; this._unlockedLevelDepth = props.unlockedLevelDepth ?? 0; @@ -66,10 +154,36 @@ export class ContributionAccountAggregate { get id(): bigint | null { return this._id; } get accountSequence(): string { return this._accountSequence; } get personalContribution(): ContributionAmount { return this._personalContribution; } - get teamLevelContribution(): ContributionAmount { return this._teamLevelContribution; } - get teamBonusContribution(): ContributionAmount { return this._teamBonusContribution; } - get totalContribution(): ContributionAmount { return this._totalContribution; } + + // 15级待解锁 + get level1Pending(): ContributionAmount { return this._level1Pending; } + get level2Pending(): ContributionAmount { return this._level2Pending; } + get level3Pending(): ContributionAmount { return this._level3Pending; } + get level4Pending(): ContributionAmount { return this._level4Pending; } + get level5Pending(): ContributionAmount { return this._level5Pending; } + get level6Pending(): ContributionAmount { return this._level6Pending; } + get level7Pending(): ContributionAmount { return this._level7Pending; } + get level8Pending(): ContributionAmount { return this._level8Pending; } + get level9Pending(): ContributionAmount { return this._level9Pending; } + get level10Pending(): ContributionAmount { return this._level10Pending; } + get level11Pending(): ContributionAmount { return this._level11Pending; } + get level12Pending(): ContributionAmount { return this._level12Pending; } + get level13Pending(): ContributionAmount { return this._level13Pending; } + get level14Pending(): ContributionAmount { return this._level14Pending; } + get level15Pending(): ContributionAmount { return this._level15Pending; } + + // 3档加成待解锁 + get bonusTier1Pending(): ContributionAmount { return this._bonusTier1Pending; } + get bonusTier2Pending(): ContributionAmount { return this._bonusTier2Pending; } + get bonusTier3Pending(): ContributionAmount { return this._bonusTier3Pending; } + + // 汇总 + get totalLevelPending(): ContributionAmount { return this._totalLevelPending; } + get totalBonusPending(): ContributionAmount { return this._totalBonusPending; } + get totalPending(): ContributionAmount { return this._totalPending; } + get totalUnlocked(): ContributionAmount { return this._totalUnlocked; } get effectiveContribution(): ContributionAmount { return this._effectiveContribution; } + get hasAdopted(): boolean { return this._hasAdopted; } get directReferralAdoptedCount(): number { return this._directReferralAdoptedCount; } get unlockedLevelDepth(): number { return this._unlockedLevelDepth; } @@ -79,35 +193,98 @@ export class ContributionAccountAggregate { get updatedAt(): Date { return this._updatedAt; } /** - * 添加个人贡献值 + * 添加个人贡献值(立即生效) */ addPersonalContribution(amount: ContributionAmount): void { this._personalContribution = this._personalContribution.add(amount); - this.recalculateTotal(); + this._effectiveContribution = this._effectiveContribution.add(amount); + this._updatedAt = new Date(); } /** - * 添加团队层级贡献值 + * 添加层级待解锁贡献值 + * @param levelDepth 层级深度 1-15 + * @param amount 贡献值金额 */ - addTeamLevelContribution(amount: ContributionAmount): void { - this._teamLevelContribution = this._teamLevelContribution.add(amount); - this.recalculateTotal(); + addLevelPendingContribution(levelDepth: number, amount: ContributionAmount): void { + if (levelDepth < 1 || levelDepth > 15) { + throw new Error(`Invalid level depth: ${levelDepth}, must be 1-15`); + } + + const levelPendingMap: Record = { + 1: this._level1Pending, + 2: this._level2Pending, + 3: this._level3Pending, + 4: this._level4Pending, + 5: this._level5Pending, + 6: this._level6Pending, + 7: this._level7Pending, + 8: this._level8Pending, + 9: this._level9Pending, + 10: this._level10Pending, + 11: this._level11Pending, + 12: this._level12Pending, + 13: this._level13Pending, + 14: this._level14Pending, + 15: this._level15Pending, + }; + + // 更新对应层级的待解锁金额 + switch (levelDepth) { + case 1: this._level1Pending = this._level1Pending.add(amount); break; + case 2: this._level2Pending = this._level2Pending.add(amount); break; + case 3: this._level3Pending = this._level3Pending.add(amount); break; + case 4: this._level4Pending = this._level4Pending.add(amount); break; + case 5: this._level5Pending = this._level5Pending.add(amount); break; + case 6: this._level6Pending = this._level6Pending.add(amount); break; + case 7: this._level7Pending = this._level7Pending.add(amount); break; + case 8: this._level8Pending = this._level8Pending.add(amount); break; + case 9: this._level9Pending = this._level9Pending.add(amount); break; + case 10: this._level10Pending = this._level10Pending.add(amount); break; + case 11: this._level11Pending = this._level11Pending.add(amount); break; + case 12: this._level12Pending = this._level12Pending.add(amount); break; + case 13: this._level13Pending = this._level13Pending.add(amount); break; + case 14: this._level14Pending = this._level14Pending.add(amount); break; + case 15: this._level15Pending = this._level15Pending.add(amount); break; + } + + this._totalLevelPending = this._totalLevelPending.add(amount); + this._totalPending = this._totalPending.add(amount); + this._updatedAt = new Date(); } /** - * 添加团队额外奖励贡献值 + * 添加加成待解锁贡献值 + * @param tier 加成档位 1-3 + * @param amount 贡献值金额 */ - addTeamBonusContribution(amount: ContributionAmount): void { - this._teamBonusContribution = this._teamBonusContribution.add(amount); - this.recalculateTotal(); + addBonusTierPendingContribution(tier: number, amount: ContributionAmount): void { + if (tier < 1 || tier > 3) { + throw new Error(`Invalid bonus tier: ${tier}, must be 1-3`); + } + + switch (tier) { + case 1: this._bonusTier1Pending = this._bonusTier1Pending.add(amount); break; + case 2: this._bonusTier2Pending = this._bonusTier2Pending.add(amount); break; + case 3: this._bonusTier3Pending = this._bonusTier3Pending.add(amount); break; + } + + this._totalBonusPending = this._totalBonusPending.add(amount); + this._totalPending = this._totalPending.add(amount); + this._updatedAt = new Date(); } /** * 标记用户已认种 + * 解锁层级1-5和加成第1档 */ markAsAdopted(): void { + if (this._hasAdopted) return; // 已经认种过 + this._hasAdopted = true; - this.updateUnlockStatus(); + this._unlockedLevelDepth = 5; // 解锁1-5级 + this._unlockedBonusTiers = 1; // 解锁第1档 + this._updatedAt = new Date(); } /** @@ -126,51 +303,129 @@ export class ContributionAccountAggregate { this.updateUnlockStatus(); } - /** - * 更新有效贡献值(扣除过期的) - */ - updateEffectiveContribution(effective: ContributionAmount): void { - this._effectiveContribution = effective; - this._updatedAt = new Date(); - } - - /** - * 重新计算总贡献值 - */ - private recalculateTotal(): void { - this._totalContribution = this._personalContribution - .add(this._teamLevelContribution) - .add(this._teamBonusContribution); - this._effectiveContribution = this._totalContribution; // 初始时有效=总量 - this._updatedAt = new Date(); - } - /** * 更新解锁状态 - * 根据直推认种用户数和是否认种过 + * 根据直推认种用户数 */ private updateUnlockStatus(): void { - // 层级解锁规则 - if (this._directReferralAdoptedCount >= 5) { - this._unlockedLevelDepth = 15; - } else if (this._directReferralAdoptedCount >= 3) { - this._unlockedLevelDepth = 10; - } else if (this._directReferralAdoptedCount >= 1) { - this._unlockedLevelDepth = 5; - } else { - this._unlockedLevelDepth = 0; + // 层级解锁规则(只有在已认种的情况下才会解锁) + if (this._hasAdopted) { + if (this._directReferralAdoptedCount >= 5) { + this._unlockedLevelDepth = 15; // 解锁11-15级 + } else if (this._directReferralAdoptedCount >= 3) { + this._unlockedLevelDepth = 10; // 解锁6-10级 + } else { + this._unlockedLevelDepth = 5; // 保持1-5级 + } } - // 额外奖励解锁规则 + // 加成解锁规则 let bonusTiers = 0; - if (this._hasAdopted) bonusTiers++; - if (this._directReferralAdoptedCount >= 2) bonusTiers++; - if (this._directReferralAdoptedCount >= 4) bonusTiers++; + if (this._hasAdopted) bonusTiers = 1; // 自己认种解锁第1档 + if (this._directReferralAdoptedCount >= 2) bonusTiers = 2; // 直推≥2解锁第2档 + if (this._directReferralAdoptedCount >= 4) bonusTiers = 3; // 直推≥4解锁第3档 this._unlockedBonusTiers = bonusTiers; this._updatedAt = new Date(); } + /** + * 解锁待解锁的贡献值 + * 根据当前解锁深度,将对应的 pending 转移到 unlocked + * @returns 本次解锁的金额 + */ + unlockPendingContributions(): ContributionAmount { + let unlockAmount = ContributionAmount.zero(); + + // 解锁层级算力 + if (this._unlockedLevelDepth >= 5) { + unlockAmount = unlockAmount.add(this._level1Pending); + unlockAmount = unlockAmount.add(this._level2Pending); + unlockAmount = unlockAmount.add(this._level3Pending); + unlockAmount = unlockAmount.add(this._level4Pending); + unlockAmount = unlockAmount.add(this._level5Pending); + this._level1Pending = ContributionAmount.zero(); + this._level2Pending = ContributionAmount.zero(); + this._level3Pending = ContributionAmount.zero(); + this._level4Pending = ContributionAmount.zero(); + this._level5Pending = ContributionAmount.zero(); + } + if (this._unlockedLevelDepth >= 10) { + unlockAmount = unlockAmount.add(this._level6Pending); + unlockAmount = unlockAmount.add(this._level7Pending); + unlockAmount = unlockAmount.add(this._level8Pending); + unlockAmount = unlockAmount.add(this._level9Pending); + unlockAmount = unlockAmount.add(this._level10Pending); + this._level6Pending = ContributionAmount.zero(); + this._level7Pending = ContributionAmount.zero(); + this._level8Pending = ContributionAmount.zero(); + this._level9Pending = ContributionAmount.zero(); + this._level10Pending = ContributionAmount.zero(); + } + if (this._unlockedLevelDepth >= 15) { + unlockAmount = unlockAmount.add(this._level11Pending); + unlockAmount = unlockAmount.add(this._level12Pending); + unlockAmount = unlockAmount.add(this._level13Pending); + unlockAmount = unlockAmount.add(this._level14Pending); + unlockAmount = unlockAmount.add(this._level15Pending); + this._level11Pending = ContributionAmount.zero(); + this._level12Pending = ContributionAmount.zero(); + this._level13Pending = ContributionAmount.zero(); + this._level14Pending = ContributionAmount.zero(); + this._level15Pending = ContributionAmount.zero(); + } + + // 解锁加成算力 + if (this._unlockedBonusTiers >= 1) { + unlockAmount = unlockAmount.add(this._bonusTier1Pending); + this._bonusTier1Pending = ContributionAmount.zero(); + } + if (this._unlockedBonusTiers >= 2) { + unlockAmount = unlockAmount.add(this._bonusTier2Pending); + this._bonusTier2Pending = ContributionAmount.zero(); + } + if (this._unlockedBonusTiers >= 3) { + unlockAmount = unlockAmount.add(this._bonusTier3Pending); + this._bonusTier3Pending = ContributionAmount.zero(); + } + + // 更新汇总 + this._totalUnlocked = this._totalUnlocked.add(unlockAmount); + this._totalPending = this._totalPending.subtract(unlockAmount); + this._effectiveContribution = this._effectiveContribution.add(unlockAmount); + this.recalculatePendingTotals(); + + this._updatedAt = new Date(); + return unlockAmount; + } + + /** + * 重新计算待解锁汇总 + */ + private recalculatePendingTotals(): void { + this._totalLevelPending = this._level1Pending + .add(this._level2Pending) + .add(this._level3Pending) + .add(this._level4Pending) + .add(this._level5Pending) + .add(this._level6Pending) + .add(this._level7Pending) + .add(this._level8Pending) + .add(this._level9Pending) + .add(this._level10Pending) + .add(this._level11Pending) + .add(this._level12Pending) + .add(this._level13Pending) + .add(this._level14Pending) + .add(this._level15Pending); + + this._totalBonusPending = this._bonusTier1Pending + .add(this._bonusTier2Pending) + .add(this._bonusTier3Pending); + + this._totalPending = this._totalLevelPending.add(this._totalBonusPending); + } + /** * 增加版本号(乐观锁) */ @@ -193,9 +448,28 @@ export class ContributionAccountAggregate { id: bigint; accountSequence: string; personalContribution: Decimal; - teamLevelContribution: Decimal; - teamBonusContribution: Decimal; - totalContribution: Decimal; + level1Pending: Decimal; + level2Pending: Decimal; + level3Pending: Decimal; + level4Pending: Decimal; + level5Pending: Decimal; + level6Pending: Decimal; + level7Pending: Decimal; + level8Pending: Decimal; + level9Pending: Decimal; + level10Pending: Decimal; + level11Pending: Decimal; + level12Pending: Decimal; + level13Pending: Decimal; + level14Pending: Decimal; + level15Pending: Decimal; + bonusTier1Pending: Decimal; + bonusTier2Pending: Decimal; + bonusTier3Pending: Decimal; + totalLevelPending: Decimal; + totalBonusPending: Decimal; + totalPending: Decimal; + totalUnlocked: Decimal; effectiveContribution: Decimal; hasAdopted: boolean; directReferralAdoptedCount: number; @@ -209,9 +483,28 @@ export class ContributionAccountAggregate { id: data.id, accountSequence: data.accountSequence, personalContribution: new ContributionAmount(data.personalContribution), - teamLevelContribution: new ContributionAmount(data.teamLevelContribution), - teamBonusContribution: new ContributionAmount(data.teamBonusContribution), - totalContribution: new ContributionAmount(data.totalContribution), + level1Pending: new ContributionAmount(data.level1Pending), + level2Pending: new ContributionAmount(data.level2Pending), + level3Pending: new ContributionAmount(data.level3Pending), + level4Pending: new ContributionAmount(data.level4Pending), + level5Pending: new ContributionAmount(data.level5Pending), + level6Pending: new ContributionAmount(data.level6Pending), + level7Pending: new ContributionAmount(data.level7Pending), + level8Pending: new ContributionAmount(data.level8Pending), + level9Pending: new ContributionAmount(data.level9Pending), + level10Pending: new ContributionAmount(data.level10Pending), + level11Pending: new ContributionAmount(data.level11Pending), + level12Pending: new ContributionAmount(data.level12Pending), + level13Pending: new ContributionAmount(data.level13Pending), + level14Pending: new ContributionAmount(data.level14Pending), + level15Pending: new ContributionAmount(data.level15Pending), + bonusTier1Pending: new ContributionAmount(data.bonusTier1Pending), + bonusTier2Pending: new ContributionAmount(data.bonusTier2Pending), + bonusTier3Pending: new ContributionAmount(data.bonusTier3Pending), + totalLevelPending: new ContributionAmount(data.totalLevelPending), + totalBonusPending: new ContributionAmount(data.totalBonusPending), + totalPending: new ContributionAmount(data.totalPending), + totalUnlocked: new ContributionAmount(data.totalUnlocked), effectiveContribution: new ContributionAmount(data.effectiveContribution), hasAdopted: data.hasAdopted, directReferralAdoptedCount: data.directReferralAdoptedCount, @@ -229,9 +522,28 @@ export class ContributionAccountAggregate { toPersistence(): { accountSequence: string; personalContribution: Decimal; - teamLevelContribution: Decimal; - teamBonusContribution: Decimal; - totalContribution: Decimal; + level1Pending: Decimal; + level2Pending: Decimal; + level3Pending: Decimal; + level4Pending: Decimal; + level5Pending: Decimal; + level6Pending: Decimal; + level7Pending: Decimal; + level8Pending: Decimal; + level9Pending: Decimal; + level10Pending: Decimal; + level11Pending: Decimal; + level12Pending: Decimal; + level13Pending: Decimal; + level14Pending: Decimal; + level15Pending: Decimal; + bonusTier1Pending: Decimal; + bonusTier2Pending: Decimal; + bonusTier3Pending: Decimal; + totalLevelPending: Decimal; + totalBonusPending: Decimal; + totalPending: Decimal; + totalUnlocked: Decimal; effectiveContribution: Decimal; hasAdopted: boolean; directReferralAdoptedCount: number; @@ -242,9 +554,28 @@ export class ContributionAccountAggregate { return { accountSequence: this._accountSequence, personalContribution: this._personalContribution.value, - teamLevelContribution: this._teamLevelContribution.value, - teamBonusContribution: this._teamBonusContribution.value, - totalContribution: this._totalContribution.value, + level1Pending: this._level1Pending.value, + level2Pending: this._level2Pending.value, + level3Pending: this._level3Pending.value, + level4Pending: this._level4Pending.value, + level5Pending: this._level5Pending.value, + level6Pending: this._level6Pending.value, + level7Pending: this._level7Pending.value, + level8Pending: this._level8Pending.value, + level9Pending: this._level9Pending.value, + level10Pending: this._level10Pending.value, + level11Pending: this._level11Pending.value, + level12Pending: this._level12Pending.value, + level13Pending: this._level13Pending.value, + level14Pending: this._level14Pending.value, + level15Pending: this._level15Pending.value, + bonusTier1Pending: this._bonusTier1Pending.value, + bonusTier2Pending: this._bonusTier2Pending.value, + bonusTier3Pending: this._bonusTier3Pending.value, + totalLevelPending: this._totalLevelPending.value, + totalBonusPending: this._totalBonusPending.value, + totalPending: this._totalPending.value, + totalUnlocked: this._totalUnlocked.value, effectiveContribution: this._effectiveContribution.value, hasAdopted: this._hasAdopted, directReferralAdoptedCount: this._directReferralAdoptedCount, diff --git a/backend/services/contribution-service/src/domain/repositories/contribution-account.repository.interface.ts b/backend/services/contribution-service/src/domain/repositories/contribution-account.repository.interface.ts index f94fd42b..93cb782d 100644 --- a/backend/services/contribution-service/src/domain/repositories/contribution-account.repository.interface.ts +++ b/backend/services/contribution-service/src/domain/repositories/contribution-account.repository.interface.ts @@ -43,7 +43,7 @@ export interface IContributionAccountRepository { findMany(options: { page?: number; limit?: number; - orderBy?: 'totalContribution' | 'effectiveContribution'; + orderBy?: 'effectiveContribution' | 'personalContribution'; order?: 'asc' | 'desc'; }): Promise<{ items: ContributionAccountAggregate[]; diff --git a/backend/services/contribution-service/src/infrastructure/persistence/repositories/contribution-account.repository.ts b/backend/services/contribution-service/src/infrastructure/persistence/repositories/contribution-account.repository.ts index 9196b064..39fc027a 100644 --- a/backend/services/contribution-service/src/infrastructure/persistence/repositories/contribution-account.repository.ts +++ b/backend/services/contribution-service/src/infrastructure/persistence/repositories/contribution-account.repository.ts @@ -80,22 +80,27 @@ export class ContributionAccountRepository implements IContributionAccountReposi sourceType: ContributionSourceType, amount: ContributionAmount, ): Promise { - const fieldMap: Record = { - [ContributionSourceType.PERSONAL]: 'personalContribution', - [ContributionSourceType.TEAM_LEVEL]: 'teamLevelContribution', - [ContributionSourceType.TEAM_BONUS]: 'teamBonusContribution', - }; - - const field = fieldMap[sourceType]; - - await this.client.contributionAccount.update({ - where: { accountSequence }, - data: { - [field]: { increment: amount.value }, - totalContribution: { increment: amount.value }, - updatedAt: new Date(), - }, - }); + // 个人算力直接增加到 personalContribution 和 effectiveContribution + // 层级/加成算力需要根据解锁状态分配到对应的 pending 字段 + if (sourceType === ContributionSourceType.PERSONAL) { + await this.client.contributionAccount.update({ + where: { accountSequence }, + data: { + personalContribution: { increment: amount.value }, + effectiveContribution: { increment: amount.value }, + updatedAt: new Date(), + }, + }); + } else { + // 层级和加成算力暂时累加到 effectiveContribution(待后续细化分配逻辑) + await this.client.contributionAccount.update({ + where: { accountSequence }, + data: { + effectiveContribution: { increment: amount.value }, + updatedAt: new Date(), + }, + }); + } } async findAllWithPagination(page: number, pageSize: number): Promise<{ @@ -106,7 +111,7 @@ export class ContributionAccountRepository implements IContributionAccountReposi this.client.contributionAccount.findMany({ skip: (page - 1) * pageSize, take: pageSize, - orderBy: { totalContribution: 'desc' }, + orderBy: { effectiveContribution: 'desc' }, }), this.client.contributionAccount.count(), ]); @@ -120,7 +125,7 @@ export class ContributionAccountRepository implements IContributionAccountReposi async findMany(options: { page?: number; limit?: number; - orderBy?: 'totalContribution' | 'effectiveContribution'; + orderBy?: 'effectiveContribution' | 'personalContribution'; order?: 'asc' | 'desc'; }): Promise<{ items: ContributionAccountAggregate[]; @@ -128,7 +133,7 @@ export class ContributionAccountRepository implements IContributionAccountReposi }> { const page = options.page ?? 1; const limit = options.limit ?? 50; - const orderBy = options.orderBy ?? 'totalContribution'; + const orderBy = options.orderBy ?? 'effectiveContribution'; const order = options.order ?? 'desc'; const [records, total] = await Promise.all([ @@ -165,8 +170,8 @@ export class ContributionAccountRepository implements IContributionAccountReposi async findTopContributors(limit: number): Promise { const records = await this.client.contributionAccount.findMany({ - where: { totalContribution: { gt: 0 } }, - orderBy: { totalContribution: 'desc' }, + where: { effectiveContribution: { gt: 0 } }, + orderBy: { effectiveContribution: 'desc' }, take: limit, }); @@ -175,10 +180,10 @@ export class ContributionAccountRepository implements IContributionAccountReposi async getTotalContribution(): Promise { const result = await this.client.contributionAccount.aggregate({ - _sum: { totalContribution: true }, + _sum: { effectiveContribution: true }, }); - return new ContributionAmount(result._sum.totalContribution || 0); + return new ContributionAmount(result._sum.effectiveContribution || 0); } async countAccounts(): Promise { @@ -187,7 +192,7 @@ export class ContributionAccountRepository implements IContributionAccountReposi async countAccountsWithContribution(): Promise { return this.client.contributionAccount.count({ - where: { totalContribution: { gt: 0 } }, + where: { effectiveContribution: { gt: 0 } }, }); } @@ -196,10 +201,32 @@ export class ContributionAccountRepository implements IContributionAccountReposi id: record.id, accountSequence: record.accountSequence, personalContribution: record.personalContribution, - teamLevelContribution: record.teamLevelContribution, - teamBonusContribution: record.teamBonusContribution, - totalContribution: record.totalContribution, + // 18 个待解锁字段 + level1Pending: record.level1Pending, + level2Pending: record.level2Pending, + level3Pending: record.level3Pending, + level4Pending: record.level4Pending, + level5Pending: record.level5Pending, + level6Pending: record.level6Pending, + level7Pending: record.level7Pending, + level8Pending: record.level8Pending, + level9Pending: record.level9Pending, + level10Pending: record.level10Pending, + level11Pending: record.level11Pending, + level12Pending: record.level12Pending, + level13Pending: record.level13Pending, + level14Pending: record.level14Pending, + level15Pending: record.level15Pending, + bonusTier1Pending: record.bonusTier1Pending, + bonusTier2Pending: record.bonusTier2Pending, + bonusTier3Pending: record.bonusTier3Pending, + // 汇总字段 + totalLevelPending: record.totalLevelPending, + totalBonusPending: record.totalBonusPending, + totalPending: record.totalPending, + totalUnlocked: record.totalUnlocked, effectiveContribution: record.effectiveContribution, + // 解锁状态 hasAdopted: record.hasAdopted, directReferralAdoptedCount: record.directReferralAdoptedCount, unlockedLevelDepth: record.unlockedLevelDepth, diff --git a/backend/services/mining-service/prisma/migrations/20260111000000_init/migration.sql b/backend/services/mining-service/prisma/migrations/20260111000000_init/migration.sql new file mode 100644 index 00000000..6588f86b --- /dev/null +++ b/backend/services/mining-service/prisma/migrations/20260111000000_init/migration.sql @@ -0,0 +1,417 @@ +-- CreateEnum +CREATE TYPE "PoolAccountType" AS ENUM ('SHARE_POOL', 'BLACK_HOLE_POOL', 'CIRCULATION_POOL'); + +-- CreateEnum +CREATE TYPE "PoolTransactionType" AS ENUM ('MINING_DISTRIBUTE', 'FEE_COLLECT', 'INITIAL_INJECT', 'BURN', 'USER_TRANSFER_IN', 'USER_TRANSFER_OUT', 'TRADE_BUY', 'TRADE_SELL', 'POOL_TRANSFER', 'ADJUSTMENT'); + +-- CreateEnum +CREATE TYPE "OutboxStatus" AS ENUM ('PENDING', 'PUBLISHED', 'FAILED'); + +-- CreateTable +CREATE TABLE "mining_configs" ( + "id" TEXT NOT NULL, + "totalShares" DECIMAL(30,8) NOT NULL, + "distributionPool" DECIMAL(30,8) NOT NULL, + "remainingDistribution" DECIMAL(30,8) NOT NULL, + "halvingPeriodYears" INTEGER NOT NULL DEFAULT 2, + "currentEra" INTEGER NOT NULL DEFAULT 1, + "eraStartDate" TIMESTAMP(3) NOT NULL, + "minuteDistribution" DECIMAL(30,18) NOT NULL, + "isActive" BOOLEAN NOT NULL DEFAULT false, + "activatedAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "mining_configs_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "mining_eras" ( + "id" TEXT NOT NULL, + "eraNumber" INTEGER NOT NULL, + "startDate" TIMESTAMP(3) NOT NULL, + "endDate" TIMESTAMP(3), + "initialDistribution" DECIMAL(30,8) NOT NULL, + "totalDistributed" DECIMAL(30,8) NOT NULL DEFAULT 0, + "minuteDistribution" DECIMAL(30,18) NOT NULL, + "isActive" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "mining_eras_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "mining_accounts" ( + "id" TEXT NOT NULL, + "accountSequence" TEXT NOT NULL, + "totalMined" DECIMAL(30,8) NOT NULL DEFAULT 0, + "availableBalance" DECIMAL(30,8) NOT NULL DEFAULT 0, + "frozenBalance" DECIMAL(30,8) NOT NULL DEFAULT 0, + "totalContribution" DECIMAL(30,8) NOT NULL DEFAULT 0, + "lastSyncedAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "mining_accounts_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "mining_records" ( + "id" TEXT NOT NULL, + "accountSequence" TEXT NOT NULL, + "miningMinute" TIMESTAMP(3) NOT NULL, + "contributionRatio" DECIMAL(30,18) NOT NULL, + "totalContribution" DECIMAL(30,8) NOT NULL, + "minuteDistribution" DECIMAL(30,18) NOT NULL, + "minedAmount" DECIMAL(30,18) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "mining_records_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "mining_transactions" ( + "id" TEXT NOT NULL, + "accountSequence" TEXT NOT NULL, + "type" TEXT NOT NULL, + "amount" DECIMAL(30,8) NOT NULL, + "balanceBefore" DECIMAL(30,8) NOT NULL, + "balanceAfter" DECIMAL(30,8) NOT NULL, + "referenceId" TEXT, + "referenceType" TEXT, + "counterparty_type" TEXT, + "counterparty_account_seq" TEXT, + "counterparty_user_id" TEXT, + "memo" TEXT, + "description" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "mining_transactions_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "mining_reward_allocations" ( + "id" BIGSERIAL NOT NULL, + "mining_date" DATE NOT NULL, + "contribution_record_id" BIGINT NOT NULL, + "source_adoption_id" BIGINT NOT NULL, + "source_account_sequence" VARCHAR(20) NOT NULL, + "owner_account_sequence" VARCHAR(20) NOT NULL, + "contribution_type" VARCHAR(30) NOT NULL, + "contribution_amount" DECIMAL(30,10) NOT NULL, + "network_total_contribution" DECIMAL(30,10) NOT NULL, + "contribution_ratio" DECIMAL(30,18) NOT NULL, + "daily_mining_pool" DECIMAL(30,10) NOT NULL, + "reward_amount" DECIMAL(30,10) NOT NULL, + "allocation_status" VARCHAR(20) NOT NULL, + "is_unlocked" BOOLEAN NOT NULL, + "allocated_to_account_sequence" VARCHAR(20), + "allocated_to_system_account" VARCHAR(20), + "unlocked_reason" VARCHAR(200), + "owner_has_adopted" BOOLEAN NOT NULL, + "owner_direct_referral_count" INTEGER NOT NULL, + "owner_unlocked_level_depth" INTEGER NOT NULL, + "owner_unlocked_bonus_tiers" INTEGER NOT NULL, + "remark" VARCHAR(500), + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "mining_reward_allocations_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "daily_mining_reward_summaries" ( + "id" BIGSERIAL NOT NULL, + "mining_date" DATE NOT NULL, + "account_sequence" VARCHAR(20) NOT NULL, + "unlocked_reward" DECIMAL(30,10) NOT NULL DEFAULT 0, + "pending_reward_to_hq" DECIMAL(30,10) NOT NULL DEFAULT 0, + "personal_reward" DECIMAL(30,10) NOT NULL DEFAULT 0, + "level_reward" DECIMAL(30,10) NOT NULL DEFAULT 0, + "bonus_reward" DECIMAL(30,10) NOT NULL DEFAULT 0, + "pending_level_to_hq" DECIMAL(30,10) NOT NULL DEFAULT 0, + "pending_bonus_to_hq" DECIMAL(30,10) NOT NULL DEFAULT 0, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "daily_mining_reward_summaries_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "headquarters_pending_rewards" ( + "id" BIGSERIAL NOT NULL, + "mining_date" DATE NOT NULL, + "would_be_account_sequence" VARCHAR(20) NOT NULL, + "source_adoption_id" BIGINT NOT NULL, + "source_account_sequence" VARCHAR(20) NOT NULL, + "contribution_record_id" BIGINT NOT NULL, + "contribution_type" VARCHAR(30) NOT NULL, + "contribution_amount" DECIMAL(30,10) NOT NULL, + "reward_amount" DECIMAL(30,10) NOT NULL, + "reason" VARCHAR(200) NOT NULL, + "owner_has_adopted" BOOLEAN NOT NULL, + "owner_direct_referral_count" INTEGER NOT NULL, + "required_condition" VARCHAR(100) NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "headquarters_pending_rewards_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "minute_mining_stats" ( + "id" TEXT NOT NULL, + "minute" TIMESTAMP(3) NOT NULL, + "totalContribution" DECIMAL(30,8) NOT NULL, + "totalDistributed" DECIMAL(30,18) NOT NULL, + "participantCount" INTEGER NOT NULL, + "burnAmount" DECIMAL(30,8) NOT NULL DEFAULT 0, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "minute_mining_stats_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "daily_mining_stats" ( + "id" TEXT NOT NULL, + "date" DATE NOT NULL, + "totalContribution" DECIMAL(30,8) NOT NULL, + "totalDistributed" DECIMAL(30,8) NOT NULL, + "totalBurned" DECIMAL(30,8) NOT NULL, + "participantCount" INTEGER NOT NULL, + "avgContributionRate" DECIMAL(10,8) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "daily_mining_stats_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "black_holes" ( + "id" TEXT NOT NULL, + "totalBurned" DECIMAL(30,8) NOT NULL DEFAULT 0, + "targetBurn" DECIMAL(30,8) NOT NULL, + "remainingBurn" DECIMAL(30,8) NOT NULL, + "lastBurnMinute" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "black_holes_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "burn_records" ( + "id" TEXT NOT NULL, + "blackHoleId" TEXT NOT NULL, + "burnMinute" TIMESTAMP(3) NOT NULL, + "burnAmount" DECIMAL(30,18) NOT NULL, + "remainingTarget" DECIMAL(30,8) NOT NULL, + "source_type" TEXT, + "source_account_seq" TEXT, + "source_user_id" TEXT, + "memo" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "burn_records_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "price_snapshots" ( + "id" TEXT NOT NULL, + "snapshotTime" TIMESTAMP(3) NOT NULL, + "price" DECIMAL(30,18) NOT NULL, + "sharePool" DECIMAL(30,8) NOT NULL, + "blackHoleAmount" DECIMAL(30,8) NOT NULL, + "circulationPool" DECIMAL(30,8) NOT NULL, + "effectiveDenominator" DECIMAL(30,8) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "price_snapshots_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "pool_accounts" ( + "id" TEXT NOT NULL, + "pool_type" "PoolAccountType" NOT NULL, + "name" TEXT NOT NULL, + "balance" DECIMAL(30,8) NOT NULL DEFAULT 0, + "totalInflow" DECIMAL(30,8) NOT NULL DEFAULT 0, + "totalOutflow" DECIMAL(30,8) NOT NULL DEFAULT 0, + "is_active" BOOLEAN NOT NULL DEFAULT true, + "description" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "pool_accounts_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "pool_transactions" ( + "id" TEXT NOT NULL, + "pool_account_id" TEXT NOT NULL, + "pool_type" "PoolAccountType" NOT NULL, + "transaction_type" "PoolTransactionType" NOT NULL, + "amount" DECIMAL(30,8) NOT NULL, + "balance_before" DECIMAL(30,8) NOT NULL, + "balance_after" DECIMAL(30,8) NOT NULL, + "counterparty_type" TEXT, + "counterparty_account_seq" TEXT, + "counterparty_user_id" TEXT, + "counterparty_pool_type" "PoolAccountType", + "reference_id" TEXT, + "reference_type" TEXT, + "tx_hash" TEXT, + "memo" TEXT, + "metadata" JSONB, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "pool_transactions_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "outbox_events" ( + "id" TEXT NOT NULL, + "aggregate_type" TEXT NOT NULL, + "aggregate_id" TEXT NOT NULL, + "event_type" TEXT NOT NULL, + "payload" JSONB NOT NULL, + "topic" TEXT NOT NULL DEFAULT 'mining.events', + "key" TEXT, + "status" "OutboxStatus" NOT NULL DEFAULT 'PENDING', + "retry_count" INTEGER NOT NULL DEFAULT 0, + "max_retries" INTEGER NOT NULL DEFAULT 5, + "last_error" TEXT, + "published_at" TIMESTAMP(3), + "next_retry_at" TIMESTAMP(3), + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "outbox_events_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "mining_eras_eraNumber_key" ON "mining_eras"("eraNumber"); + +-- CreateIndex +CREATE UNIQUE INDEX "mining_accounts_accountSequence_key" ON "mining_accounts"("accountSequence"); + +-- CreateIndex +CREATE INDEX "mining_accounts_totalContribution_idx" ON "mining_accounts"("totalContribution" DESC); + +-- CreateIndex +CREATE INDEX "mining_records_miningMinute_idx" ON "mining_records"("miningMinute"); + +-- CreateIndex +CREATE UNIQUE INDEX "mining_records_accountSequence_miningMinute_key" ON "mining_records"("accountSequence", "miningMinute"); + +-- CreateIndex +CREATE INDEX "mining_transactions_accountSequence_createdAt_idx" ON "mining_transactions"("accountSequence", "createdAt" DESC); + +-- CreateIndex +CREATE INDEX "mining_transactions_type_idx" ON "mining_transactions"("type"); + +-- CreateIndex +CREATE INDEX "mining_transactions_counterparty_account_seq_idx" ON "mining_transactions"("counterparty_account_seq"); + +-- CreateIndex +CREATE INDEX "mining_transactions_counterparty_user_id_idx" ON "mining_transactions"("counterparty_user_id"); + +-- CreateIndex +CREATE INDEX "mining_reward_allocations_mining_date_idx" ON "mining_reward_allocations"("mining_date"); + +-- CreateIndex +CREATE INDEX "mining_reward_allocations_owner_account_sequence_mining_dat_idx" ON "mining_reward_allocations"("owner_account_sequence", "mining_date"); + +-- CreateIndex +CREATE INDEX "mining_reward_allocations_source_account_sequence_idx" ON "mining_reward_allocations"("source_account_sequence"); + +-- CreateIndex +CREATE INDEX "mining_reward_allocations_source_adoption_id_idx" ON "mining_reward_allocations"("source_adoption_id"); + +-- CreateIndex +CREATE INDEX "mining_reward_allocations_allocation_status_idx" ON "mining_reward_allocations"("allocation_status"); + +-- CreateIndex +CREATE INDEX "mining_reward_allocations_contribution_record_id_idx" ON "mining_reward_allocations"("contribution_record_id"); + +-- CreateIndex +CREATE INDEX "daily_mining_reward_summaries_mining_date_idx" ON "daily_mining_reward_summaries"("mining_date"); + +-- CreateIndex +CREATE INDEX "daily_mining_reward_summaries_account_sequence_idx" ON "daily_mining_reward_summaries"("account_sequence"); + +-- CreateIndex +CREATE UNIQUE INDEX "daily_mining_reward_summaries_mining_date_account_sequence_key" ON "daily_mining_reward_summaries"("mining_date", "account_sequence"); + +-- CreateIndex +CREATE INDEX "headquarters_pending_rewards_mining_date_idx" ON "headquarters_pending_rewards"("mining_date"); + +-- CreateIndex +CREATE INDEX "headquarters_pending_rewards_would_be_account_sequence_idx" ON "headquarters_pending_rewards"("would_be_account_sequence"); + +-- CreateIndex +CREATE INDEX "headquarters_pending_rewards_source_adoption_id_idx" ON "headquarters_pending_rewards"("source_adoption_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "minute_mining_stats_minute_key" ON "minute_mining_stats"("minute"); + +-- CreateIndex +CREATE INDEX "minute_mining_stats_minute_idx" ON "minute_mining_stats"("minute" DESC); + +-- CreateIndex +CREATE UNIQUE INDEX "daily_mining_stats_date_key" ON "daily_mining_stats"("date"); + +-- CreateIndex +CREATE INDEX "burn_records_burnMinute_idx" ON "burn_records"("burnMinute"); + +-- CreateIndex +CREATE INDEX "burn_records_source_account_seq_idx" ON "burn_records"("source_account_seq"); + +-- CreateIndex +CREATE UNIQUE INDEX "burn_records_blackHoleId_burnMinute_key" ON "burn_records"("blackHoleId", "burnMinute"); + +-- CreateIndex +CREATE UNIQUE INDEX "price_snapshots_snapshotTime_key" ON "price_snapshots"("snapshotTime"); + +-- CreateIndex +CREATE INDEX "price_snapshots_snapshotTime_idx" ON "price_snapshots"("snapshotTime" DESC); + +-- CreateIndex +CREATE UNIQUE INDEX "pool_accounts_pool_type_key" ON "pool_accounts"("pool_type"); + +-- CreateIndex +CREATE INDEX "pool_accounts_pool_type_idx" ON "pool_accounts"("pool_type"); + +-- CreateIndex +CREATE INDEX "pool_transactions_pool_account_id_created_at_idx" ON "pool_transactions"("pool_account_id", "created_at" DESC); + +-- CreateIndex +CREATE INDEX "pool_transactions_pool_type_transaction_type_idx" ON "pool_transactions"("pool_type", "transaction_type"); + +-- CreateIndex +CREATE INDEX "pool_transactions_counterparty_account_seq_idx" ON "pool_transactions"("counterparty_account_seq"); + +-- CreateIndex +CREATE INDEX "pool_transactions_counterparty_user_id_idx" ON "pool_transactions"("counterparty_user_id"); + +-- CreateIndex +CREATE INDEX "pool_transactions_reference_id_idx" ON "pool_transactions"("reference_id"); + +-- CreateIndex +CREATE INDEX "pool_transactions_created_at_idx" ON "pool_transactions"("created_at" DESC); + +-- CreateIndex +CREATE INDEX "outbox_events_status_idx" ON "outbox_events"("status"); + +-- CreateIndex +CREATE INDEX "outbox_events_next_retry_at_idx" ON "outbox_events"("next_retry_at"); + +-- CreateIndex +CREATE INDEX "outbox_events_created_at_idx" ON "outbox_events"("created_at"); + +-- AddForeignKey +ALTER TABLE "mining_records" ADD CONSTRAINT "mining_records_accountSequence_fkey" FOREIGN KEY ("accountSequence") REFERENCES "mining_accounts"("accountSequence") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "mining_transactions" ADD CONSTRAINT "mining_transactions_accountSequence_fkey" FOREIGN KEY ("accountSequence") REFERENCES "mining_accounts"("accountSequence") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "burn_records" ADD CONSTRAINT "burn_records_blackHoleId_fkey" FOREIGN KEY ("blackHoleId") REFERENCES "black_holes"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "pool_transactions" ADD CONSTRAINT "pool_transactions_pool_account_id_fkey" FOREIGN KEY ("pool_account_id") REFERENCES "pool_accounts"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/backend/services/mining-service/prisma/migrations/migration_lock.toml b/backend/services/mining-service/prisma/migrations/migration_lock.toml new file mode 100644 index 00000000..99e4f200 --- /dev/null +++ b/backend/services/mining-service/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" diff --git a/backend/services/mining-service/prisma/schema.prisma b/backend/services/mining-service/prisma/schema.prisma index 59e39bda..b263b62f 100644 --- a/backend/services/mining-service/prisma/schema.prisma +++ b/backend/services/mining-service/prisma/schema.prisma @@ -111,6 +111,134 @@ model MiningTransaction { @@map("mining_transactions") } +// ==================== 挖矿收益分配明细 ==================== +// 特别关注:待解锁算力产生的收益分配到总部的情况 + +// 每日挖矿收益分配明细 +// 记录每天挖矿时,算力产生的收益分配给谁 +model MiningRewardAllocation { + id BigInt @id @default(autoincrement()) + + // ========== 挖矿日期 ========== + miningDate DateTime @map("mining_date") @db.Date + + // ========== 算力来源(可追溯到认种)========== + contributionRecordId BigInt @map("contribution_record_id") // 关联 contribution-service 的算力明细记录ID + sourceAdoptionId BigInt @map("source_adoption_id") // 来源认种ID + sourceAccountSequence String @map("source_account_sequence") @db.VarChar(20) // 认种人账号 + + // ========== 算力归属账户 ========== + ownerAccountSequence String @map("owner_account_sequence") @db.VarChar(20) // 算力归属账户(收到分成的上级) + + // ========== 算力类型 ========== + contributionType String @map("contribution_type") @db.VarChar(30) // PERSONAL / LEVEL_1~15 / BONUS_TIER_1~3 + + // ========== 算力金额与挖矿收益 ========== + contributionAmount Decimal @map("contribution_amount") @db.Decimal(30, 10) // 参与挖矿的算力 + networkTotalContribution Decimal @map("network_total_contribution") @db.Decimal(30, 10) // 当日全网算力 + contributionRatio Decimal @map("contribution_ratio") @db.Decimal(30, 18) // 算力占比 + dailyMiningPool Decimal @map("daily_mining_pool") @db.Decimal(30, 10) // 当日挖矿池总量 + rewardAmount Decimal @map("reward_amount") @db.Decimal(30, 10) // 应得挖矿收益 + + // ========== 分配状态(核心:是否因未解锁而归总部)========== + allocationStatus String @map("allocation_status") @db.VarChar(20) // TO_USER / TO_HEADQUARTERS + isUnlocked Boolean @map("is_unlocked") // 算力是否已解锁 + + // ========== 实际分配去向 ========== + allocatedToAccountSequence String? @map("allocated_to_account_sequence") @db.VarChar(20) // 实际分配给的账户 + allocatedToSystemAccount String? @map("allocated_to_system_account") @db.VarChar(20) // 或分配给系统账户(HEADQUARTERS) + + // ========== 未解锁原因说明 ========== + unlockedReason String? @map("unlocked_reason") @db.VarChar(200) // 如果归总部,记录原因 + + // ========== 资质条件快照(便于追溯)========== + ownerHasAdopted Boolean @map("owner_has_adopted") // 归属账户当时是否已认种 + ownerDirectReferralCount Int @map("owner_direct_referral_count") // 归属账户当时直推认种人数 + ownerUnlockedLevelDepth Int @map("owner_unlocked_level_depth") // 归属账户当时解锁层级深度 + ownerUnlockedBonusTiers Int @map("owner_unlocked_bonus_tiers") // 归属账户当时解锁加成档位 + + // ========== 备注 ========== + remark String? @map("remark") @db.VarChar(500) + + createdAt DateTime @default(now()) @map("created_at") + + @@index([miningDate]) + @@index([ownerAccountSequence, miningDate]) + @@index([sourceAccountSequence]) + @@index([sourceAdoptionId]) + @@index([allocationStatus]) + @@index([contributionRecordId]) + @@map("mining_reward_allocations") +} + +// 每日挖矿汇总(按账户) +// 汇总每个账户每天的挖矿收益 +model DailyMiningRewardSummary { + id BigInt @id @default(autoincrement()) + + miningDate DateTime @map("mining_date") @db.Date + accountSequence String @map("account_sequence") @db.VarChar(20) + + // ========== 收益汇总 ========== + // 来自已解锁算力的收益(归用户) + unlockedReward Decimal @default(0) @map("unlocked_reward") @db.Decimal(30, 10) + // 来自待解锁算力的收益(本应归用户,但因未解锁归了总部) + pendingRewardToHq Decimal @default(0) @map("pending_reward_to_hq") @db.Decimal(30, 10) + + // ========== 明细统计 ========== + // 已解锁的各类算力收益 + personalReward Decimal @default(0) @map("personal_reward") @db.Decimal(30, 10) // 个人算力收益 + levelReward Decimal @default(0) @map("level_reward") @db.Decimal(30, 10) // 层级算力收益(已解锁部分) + bonusReward Decimal @default(0) @map("bonus_reward") @db.Decimal(30, 10) // 加成算力收益(已解锁部分) + + // 待解锁转总部的明细 + pendingLevelToHq Decimal @default(0) @map("pending_level_to_hq") @db.Decimal(30, 10) // 待解锁层级收益归总部 + pendingBonusToHq Decimal @default(0) @map("pending_bonus_to_hq") @db.Decimal(30, 10) // 待解锁加成收益归总部 + + createdAt DateTime @default(now()) @map("created_at") + + @@unique([miningDate, accountSequence]) + @@index([miningDate]) + @@index([accountSequence]) + @@map("daily_mining_reward_summaries") +} + +// 总部待解锁收益明细账 +// 记录总部收到的来自各用户待解锁算力的收益 +model HeadquartersPendingReward { + id BigInt @id @default(autoincrement()) + + miningDate DateTime @map("mining_date") @db.Date + + // ========== 应归属账户(如果解锁了会归他)========== + wouldBeAccountSequence String @map("would_be_account_sequence") @db.VarChar(20) + + // ========== 算力来源 ========== + sourceAdoptionId BigInt @map("source_adoption_id") + sourceAccountSequence String @map("source_account_sequence") @db.VarChar(20) + contributionRecordId BigInt @map("contribution_record_id") + + // ========== 算力类型与金额 ========== + contributionType String @map("contribution_type") @db.VarChar(30) // LEVEL_1~15 / BONUS_TIER_1~3 + contributionAmount Decimal @map("contribution_amount") @db.Decimal(30, 10) + rewardAmount Decimal @map("reward_amount") @db.Decimal(30, 10) + + // ========== 未解锁原因 ========== + reason String @map("reason") @db.VarChar(200) // 如 "账户未认种,层级1-5未解锁" / "直推认种人数不足3人,层级6-10未解锁" + + // ========== 资质条件快照 ========== + ownerHasAdopted Boolean @map("owner_has_adopted") + ownerDirectReferralCount Int @map("owner_direct_referral_count") + requiredCondition String @map("required_condition") @db.VarChar(100) // 需要满足的条件 + + createdAt DateTime @default(now()) @map("created_at") + + @@index([miningDate]) + @@index([wouldBeAccountSequence]) + @@index([sourceAdoptionId]) + @@map("headquarters_pending_rewards") +} + // ==================== 挖矿统计 ==================== // 每分钟挖矿统计