feat(contribution-service, mining-service): 添加18级待解锁算力字段和挖矿收益分配表

contribution-service:
- 添加15级层级待解锁字段 (level1-15Pending)
- 添加3档加成待解锁字段 (bonusTier1-3Pending)
- 添加解锁状态追踪字段 (hasAdopted, directReferralAdoptedCount等)
- 重构ContributionAccountAggregate支持新字段结构
- 更新repository和query处理effectiveContribution

mining-service:
- 添加MiningRewardAllocation表追踪每日挖矿收益分配明细
- 添加DailyMiningRewardSummary表汇总账户每日收益
- 添加HeadquartersPendingReward表记录未解锁算力收益归总部明细
- 创建初始migration文件

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-11 06:16:15 -08:00
parent 4bb995f2c2
commit 6261679f5a
10 changed files with 1368 additions and 199 deletions

View File

@ -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 负责创建和管理

View File

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

View File

@ -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;

View File

@ -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,
};
}

View File

@ -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-150.5%7.5%
* - 32.5%7.5%
*
*
* - 1-5 + 1
* - 22
* - 36-10
* - 43
* - 511-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<number, ContributionAmount> = {
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-51
*/
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,

View File

@ -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[];

View File

@ -80,22 +80,27 @@ export class ContributionAccountRepository implements IContributionAccountReposi
sourceType: ContributionSourceType,
amount: ContributionAmount,
): Promise<void> {
const fieldMap: Record<ContributionSourceType, string> = {
[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<ContributionAccountAggregate[]> {
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<ContributionAmount> {
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<number> {
@ -187,7 +192,7 @@ export class ContributionAccountRepository implements IContributionAccountReposi
async countAccountsWithContribution(): Promise<number> {
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,

View File

@ -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;

View File

@ -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"

View File

@ -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")
}
// ==================== 挖矿统计 ====================
// 每分钟挖矿统计