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 { model SyncedUser {
id BigInt @id @default(autoincrement()) id BigInt @id @default(autoincrement())
accountSequence String @unique @map("account_sequence") @db.VarChar(20) accountSequence String @unique @map("account_sequence") @db.VarChar(20)
originalUserId BigInt @map("original_user_id") originalUserId BigInt @map("original_user_id")
phone String? @db.VarChar(20) phone String? @db.VarChar(20)
status String? @db.VarChar(20) status String? @db.VarChar(20)
// CDC 同步元数据 // CDC 同步元数据
sourceSequenceNum BigInt @map("source_sequence_num") sourceSequenceNum BigInt @map("source_sequence_num")
@ -29,19 +29,19 @@ model SyncedUser {
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime @default(now()) @map("created_at")
@@map("synced_users")
@@index([originalUserId]) @@index([originalUserId])
@@index([contributionCalculated]) @@index([contributionCalculated])
@@map("synced_users")
} }
// 同步的认种数据 // 同步的认种数据
model SyncedAdoption { model SyncedAdoption {
id BigInt @id @default(autoincrement()) id BigInt @id @default(autoincrement())
originalAdoptionId BigInt @unique @map("original_adoption_id") originalAdoptionId BigInt @unique @map("original_adoption_id")
accountSequence String @map("account_sequence") @db.VarChar(20) accountSequence String @map("account_sequence") @db.VarChar(20)
treeCount Int @map("tree_count") treeCount Int @map("tree_count")
adoptionDate DateTime @map("adoption_date") @db.Date adoptionDate DateTime @map("adoption_date") @db.Date
status String? @db.VarChar(20) status String? @db.VarChar(20)
// 贡献值计算参数(从认种时的配置) // 贡献值计算参数(从认种时的配置)
contributionPerTree Decimal @map("contribution_per_tree") @db.Decimal(20, 10) contributionPerTree Decimal @map("contribution_per_tree") @db.Decimal(20, 10)
@ -56,17 +56,17 @@ model SyncedAdoption {
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime @default(now()) @map("created_at")
@@map("synced_adoptions")
@@index([accountSequence]) @@index([accountSequence])
@@index([adoptionDate]) @@index([adoptionDate])
@@index([contributionDistributed]) @@index([contributionDistributed])
@@map("synced_adoptions")
} }
// 同步的推荐关系数据 // 同步的推荐关系数据
model SyncedReferral { model SyncedReferral {
id BigInt @id @default(autoincrement()) id BigInt @id @default(autoincrement())
accountSequence String @unique @map("account_sequence") @db.VarChar(20) accountSequence String @unique @map("account_sequence") @db.VarChar(20)
referrerAccountSequence String? @map("referrer_account_sequence") @db.VarChar(20) referrerAccountSequence String? @map("referrer_account_sequence") @db.VarChar(20)
// 预计算的层级路径(便于快速查询上下级) // 预计算的层级路径(便于快速查询上下级)
ancestorPath String? @map("ancestor_path") @db.Text ancestorPath String? @map("ancestor_path") @db.Text
@ -78,8 +78,8 @@ model SyncedReferral {
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime @default(now()) @map("created_at")
@@map("synced_referrals")
@@index([referrerAccountSequence]) @@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 { model ContributionAccount {
id BigInt @id @default(autoincrement()) id BigInt @id @default(autoincrement())
accountSequence String @unique @map("account_sequence") @db.VarChar(20) accountSequence String @unique @map("account_sequence") @db.VarChar(20)
// 算力汇总 // ========== 个人算力(立即生效)==========
personalContribution Decimal @default(0) @map("personal_contribution") @db.Decimal(30, 10) 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)
// 用户条件(决定能获得多少团队算力) // ========== 15级层级算力待解锁/已解锁)==========
hasAdopted Boolean @default(false) @map("has_adopted") // 每级0.5%,分三档解锁
directReferralAdoptedCount Int @default(0) @map("direct_referral_adopted_count") // 第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") unlockedLevelDepth Int @default(0) @map("unlocked_level_depth")
// 解锁加成档位0=未解锁, 1/2/3=对应档位
unlockedBonusTiers Int @default(0) @map("unlocked_bonus_tiers") unlockedBonusTiers Int @default(0) @map("unlocked_bonus_tiers")
// 乐观锁 // 乐观锁
@ -112,80 +153,139 @@ model ContributionAccount {
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at") updatedAt DateTime @updatedAt @map("updated_at")
@@map("contribution_accounts")
@@index([totalContribution(sort: Desc)])
@@index([effectiveContribution(sort: Desc)]) @@index([effectiveContribution(sort: Desc)])
@@index([hasAdopted])
@@index([directReferralAdoptedCount])
@@map("contribution_accounts")
} }
// 算力明细表(分类账) // 算力明细表(分类账)
// 每笔算力变动都记录在此表,用于审计和追溯
// 状态流转PENDING -> UNLOCKED -> EXPIRED (或直接 EFFECTIVE -> EXPIRED)
model ContributionRecord { model ContributionRecord {
id BigInt @id @default(autoincrement()) id BigInt @id @default(autoincrement())
accountSequence String @map("account_sequence") @db.VarChar(20) accountSequence String @map("account_sequence") @db.VarChar(20)
// 来源信息(可追溯) // ========== 来源信息(可追溯)==========
sourceType String @map("source_type") @db.VarChar(30) // PERSONAL / TEAM_LEVEL / TEAM_BONUS sourceType String @map("source_type") @db.VarChar(30) // PERSONAL / LEVEL_1~15 / BONUS_TIER_1~3
sourceAdoptionId BigInt @map("source_adoption_id") sourceAdoptionId BigInt @map("source_adoption_id") // 来源认种ID
sourceAccountSequence String @map("source_account_sequence") @db.VarChar(20) sourceAccountSequence String @map("source_account_sequence") @db.VarChar(20) // 认种人账号
// 计算参数(审计用) // ========== 计算参数(审计用)==========
treeCount Int @map("tree_count") treeCount Int @map("tree_count") // 认种棵数
baseContribution Decimal @map("base_contribution") @db.Decimal(20, 10) baseContribution Decimal @map("base_contribution") @db.Decimal(20, 10) // 基础贡献值/棵
distributionRate Decimal @map("distribution_rate") @db.Decimal(10, 6) distributionRate Decimal @map("distribution_rate") @db.Decimal(10, 6) // 分配比例 (0.005=0.5%, 0.025=2.5%)
levelDepth Int? @map("level_depth") levelDepth Int? @map("level_depth") // 层级深度 (1-15)
bonusTier Int? @map("bonus_tier") 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 status String @default("PENDING") @map("status") @db.VarChar(20) // PENDING/UNLOCKED/EFFECTIVE
expireDate DateTime @map("expire_date") @db.Date unlockedAt DateTime? @map("unlocked_at") // 解锁时间
isExpired Boolean @default(false) @map("is_expired") 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") expiredAt DateTime? @map("expired_at")
// ========== 备注 ==========
remark String? @map("remark") @db.VarChar(500) // 备注说明
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime @default(now()) @map("created_at")
@@map("contribution_records") @@index([accountSequence, status])
@@index([accountSequence, createdAt(sort: Desc)]) @@index([accountSequence, createdAt(sort: Desc)])
@@index([sourceAdoptionId]) @@index([sourceAdoptionId])
@@index([sourceAccountSequence]) @@index([sourceAccountSequence])
@@index([sourceType]) @@index([sourceType])
@@index([status])
@@index([expireDate]) @@index([expireDate])
@@index([isExpired]) @@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 { model UnallocatedContribution {
id BigInt @id @default(autoincrement()) id BigInt @id @default(autoincrement())
sourceAdoptionId BigInt @map("source_adoption_id") sourceAdoptionId BigInt @map("source_adoption_id") // 来源认种ID
sourceAccountSequence String @map("source_account_sequence") @db.VarChar(20) 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) unallocType String @map("unalloc_type") @db.VarChar(30) // LEVEL_6~15_PENDING / BONUS_TIER_2_PENDING / BONUS_TIER_3_PENDING
levelDepth Int? @map("level_depth") 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) amount Decimal @map("amount") @db.Decimal(30, 10)
reason String? @db.VarChar(200) reason String? @db.VarChar(200) // 未分配原因
// 归总部后的处理 // ========== 分配状态 ==========
allocatedToHeadquarters Boolean @default(false) @map("allocated_to_headquarters") status String @default("PENDING") @map("status") @db.VarChar(20) // PENDING / ALLOCATED_TO_USER / ALLOCATED_TO_HQ
allocatedAt DateTime? @map("allocated_at") allocatedAt DateTime? @map("allocated_at")
allocatedToAccountSequence String? @map("allocated_to_account_sequence") @db.VarChar(20) // 最终分配给的账户
// ========== 有效期 ==========
effectiveDate DateTime @map("effective_date") @db.Date effectiveDate DateTime @map("effective_date") @db.Date
expireDate DateTime @map("expire_date") @db.Date expireDate DateTime @map("expire_date") @db.Date
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime @default(now()) @map("created_at")
@@map("unallocated_contributions")
@@index([sourceAdoptionId]) @@index([sourceAdoptionId])
@@index([wouldBeAccountSequence])
@@index([unallocType]) @@index([unallocType])
@@index([allocatedToHeadquarters]) @@index([status])
@@map("unallocated_contributions")
} }
// 系统账户(运营/省/市/总部) // 系统账户(运营/省/市/总部)
model SystemAccount { model SystemAccount {
id BigInt @id @default(autoincrement()) 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) name String @db.VarChar(100)
contributionBalance Decimal @default(0) @map("contribution_balance") @db.Decimal(30, 10) contributionBalance Decimal @default(0) @map("contribution_balance") @db.Decimal(30, 10)
@ -219,9 +319,9 @@ model SystemContributionRecord {
systemAccount SystemAccount @relation(fields: [systemAccountId], references: [id]) systemAccount SystemAccount @relation(fields: [systemAccountId], references: [id])
@@map("system_contribution_records")
@@index([systemAccountId]) @@index([systemAccountId])
@@index([sourceAdoptionId]) @@index([sourceAdoptionId])
@@map("system_contribution_records")
} }
// ============================================ // ============================================
@ -230,20 +330,20 @@ model SystemContributionRecord {
// 每日算力快照(用于挖矿分配计算) // 每日算力快照(用于挖矿分配计算)
model DailyContributionSnapshot { model DailyContributionSnapshot {
id BigInt @id @default(autoincrement()) id BigInt @id @default(autoincrement())
snapshotDate DateTime @map("snapshot_date") @db.Date snapshotDate DateTime @map("snapshot_date") @db.Date
accountSequence String @map("account_sequence") @db.VarChar(20) accountSequence String @map("account_sequence") @db.VarChar(20)
effectiveContribution Decimal @map("effective_contribution") @db.Decimal(30, 10) effectiveContribution Decimal @map("effective_contribution") @db.Decimal(30, 10)
networkTotalContribution Decimal @map("network_total_contribution") @db.Decimal(30, 10) networkTotalContribution Decimal @map("network_total_contribution") @db.Decimal(30, 10)
contributionRatio Decimal @map("contribution_ratio") @db.Decimal(30, 18) contributionRatio Decimal @map("contribution_ratio") @db.Decimal(30, 18)
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime @default(now()) @map("created_at")
@@unique([snapshotDate, accountSequence]) @@unique([snapshotDate, accountSequence])
@@map("daily_contribution_snapshots")
@@index([snapshotDate]) @@index([snapshotDate])
@@index([accountSequence]) @@index([accountSequence])
@@map("daily_contribution_snapshots")
} }
// 用户团队统计(缓存,定期更新) // 用户团队统计(缓存,定期更新)
@ -269,15 +369,15 @@ model UserTeamStats {
level14Trees Int @default(0) @map("level_14_trees") level14Trees Int @default(0) @map("level_14_trees")
level15Trees Int @default(0) @map("level_15_trees") level15Trees Int @default(0) @map("level_15_trees")
totalTeamTrees Int @default(0) @map("total_team_trees") totalTeamTrees Int @default(0) @map("total_team_trees")
directAdoptedReferrals Int @default(0) @map("direct_adopted_referrals") directAdoptedReferrals Int @default(0) @map("direct_adopted_referrals")
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime @default(now()) @map("created_at")
@@unique([accountSequence, statsDate]) @@unique([accountSequence, statsDate])
@@map("user_team_stats")
@@index([accountSequence]) @@index([accountSequence])
@@index([statsDate]) @@index([statsDate])
@@map("user_team_stats")
} }
// ============================================ // ============================================
@ -286,10 +386,10 @@ model UserTeamStats {
// CDC 同步进度表 // CDC 同步进度表
model CdcSyncProgress { model CdcSyncProgress {
id BigInt @id @default(autoincrement()) id BigInt @id @default(autoincrement())
sourceTopic String @unique @map("source_topic") @db.VarChar(100) sourceTopic String @unique @map("source_topic") @db.VarChar(100)
lastSequenceNum BigInt @default(0) @map("last_sequence_num") lastSequenceNum BigInt @default(0) @map("last_sequence_num")
lastSyncedAt DateTime? @map("last_synced_at") lastSyncedAt DateTime? @map("last_synced_at")
updatedAt DateTime @updatedAt @map("updated_at") updatedAt DateTime @updatedAt @map("updated_at")
@ -298,16 +398,16 @@ model CdcSyncProgress {
// 已处理事件表(幂等性) // 已处理事件表(幂等性)
model ProcessedEvent { model ProcessedEvent {
id BigInt @id @default(autoincrement()) id BigInt @id @default(autoincrement())
eventId String @unique @map("event_id") @db.VarChar(100) eventId String @unique @map("event_id") @db.VarChar(100)
eventType String @map("event_type") @db.VarChar(50) eventType String @map("event_type") @db.VarChar(50)
sourceService String? @map("source_service") @db.VarChar(50) sourceService String? @map("source_service") @db.VarChar(50)
processedAt DateTime @default(now()) @map("processed_at") processedAt DateTime @default(now()) @map("processed_at")
@@map("processed_events")
@@index([eventType]) @@index([eventType])
@@index([processedAt]) @@index([processedAt])
@@map("processed_events")
} }
// ============================================ // ============================================
@ -318,17 +418,17 @@ model ProcessedEvent {
model ContributionConfig { model ContributionConfig {
id BigInt @id @default(autoincrement()) id BigInt @id @default(autoincrement())
baseContribution Decimal @default(22617) @map("base_contribution") @db.Decimal(20, 10) baseContribution Decimal @default(22617) @map("base_contribution") @db.Decimal(20, 10)
incrementPercentage Decimal @default(0.003) @map("increment_percentage") @db.Decimal(10, 6) incrementPercentage Decimal @default(0.003) @map("increment_percentage") @db.Decimal(10, 6)
unitSize Int @default(100) @map("unit_size") unitSize Int @default(100) @map("unit_size")
startTreeNumber Int @default(1000) @map("start_tree_number") startTreeNumber Int @default(1000) @map("start_tree_number")
isActive Boolean @default(true) @map("is_active") isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime @default(now()) @map("created_at")
@@map("contribution_configs")
@@index([isActive]) @@index([isActive])
@@map("contribution_configs")
} }
// 分配比例配置 // 分配比例配置
@ -343,8 +443,8 @@ model DistributionRateConfig {
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime @default(now()) @map("created_at")
@@map("distribution_rate_configs")
@@index([isActive]) @@index([isActive])
@@map("distribution_rate_configs")
} }
// ============================================ // ============================================
@ -371,9 +471,9 @@ model OutboxEvent {
publishedAt DateTime? @map("published_at") publishedAt DateTime? @map("published_at")
nextRetryAt DateTime? @map("next_retry_at") nextRetryAt DateTime? @map("next_retry_at")
@@map("outbox_events")
@@index([status, createdAt]) @@index([status, createdAt])
@@index([status, nextRetryAt]) @@index([status, nextRetryAt])
@@index([aggregateType, aggregateId]) @@index([aggregateType, aggregateId])
@@index([topic]) @@index([topic])
@@map("outbox_events")
} }

View File

@ -7,14 +7,17 @@ export class ContributionRankingItemResponse {
@ApiProperty({ description: '账户序号' }) @ApiProperty({ description: '账户序号' })
accountSequence: string; accountSequence: string;
@ApiProperty({ description: '总算力' }) @ApiProperty({ description: '有效算力(个人 + 已解锁)' })
totalContribution: string; effectiveContribution: string;
@ApiProperty({ description: '个人算力' }) @ApiProperty({ description: '个人算力' })
personalContribution: string; personalContribution: string;
@ApiProperty({ description: '团队算力' }) @ApiProperty({ description: '待解锁算力总和' })
teamContribution: string; totalPending: string;
@ApiProperty({ description: '已解锁算力总和' })
totalUnlocked: string;
} }
export class ContributionRankingResponse { export class ContributionRankingResponse {
@ -26,8 +29,8 @@ export class UserRankResponse {
@ApiProperty({ description: '排名', nullable: true }) @ApiProperty({ description: '排名', nullable: true })
rank: number | null; rank: number | null;
@ApiProperty({ description: '算力' }) @ApiProperty({ description: '有效算力' })
totalContribution: string; effectiveContribution: string;
@ApiProperty({ description: '百分位', nullable: true }) @ApiProperty({ description: '百分位', nullable: true })
percentile: number | null; percentile: number | null;

View File

@ -5,9 +5,10 @@ import { RedisService } from '../../infrastructure/redis/redis.service';
export interface ContributionRankingDto { export interface ContributionRankingDto {
rank: number; rank: number;
accountSequence: string; accountSequence: string;
totalContribution: string; effectiveContribution: string;
personalContribution: string; personalContribution: string;
teamContribution: string; totalPending: string;
totalUnlocked: string;
} }
@Injectable() @Injectable()
@ -36,9 +37,10 @@ export class GetContributionRankingQuery {
const ranking: ContributionRankingDto[] = topContributors.map((account, index) => ({ const ranking: ContributionRankingDto[] = topContributors.map((account, index) => ({
rank: index + 1, rank: index + 1,
accountSequence: account.accountSequence, accountSequence: account.accountSequence,
totalContribution: account.totalContribution.value.toString(), effectiveContribution: account.effectiveContribution.value.toString(),
personalContribution: account.personalContribution.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<{ async getUserRank(accountSequence: string): Promise<{
rank: number | null; rank: number | null;
totalContribution: string; effectiveContribution: string;
percentile: number | null; percentile: number | null;
} | null> { } | null> {
const account = await this.accountRepository.findByAccountSequence(accountSequence); const account = await this.accountRepository.findByAccountSequence(accountSequence);
@ -67,7 +69,7 @@ export class GetContributionRankingQuery {
return { return {
rank: rank !== null ? rank + 1 : null, 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, 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 { export enum ContributionSourceType {
PERSONAL = 'PERSONAL', // 来自自己认种 PERSONAL = 'PERSONAL', // 来自自己认种
TEAM_LEVEL = 'TEAM_LEVEL', // 来自团队层级 TEAM_LEVEL = 'TEAM_LEVEL', // 来自团队层级 (1-15级)
TEAM_BONUS = 'TEAM_BONUS', // 来自团队额外奖励 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 { export class ContributionAccountAggregate {
private _id: bigint | null; private _id: bigint | null;
private _accountSequence: string; private _accountSequence: string;
// ========== 个人算力(立即生效)==========
private _personalContribution: ContributionAmount; private _personalContribution: ContributionAmount;
private _teamLevelContribution: ContributionAmount;
private _teamBonusContribution: ContributionAmount; // ========== 15级层级待解锁算力 ==========
private _totalContribution: ContributionAmount; 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 _effectiveContribution: ContributionAmount;
// ========== 解锁条件状态 ==========
private _hasAdopted: boolean; private _hasAdopted: boolean;
private _directReferralAdoptedCount: number; private _directReferralAdoptedCount: number;
private _unlockedLevelDepth: number; private _unlockedLevelDepth: number; // 0, 5, 10, 15
private _unlockedBonusTiers: number; private _unlockedBonusTiers: number; // 0, 1, 2, 3
private _version: number; private _version: number;
private _createdAt: Date; private _createdAt: Date;
private _updatedAt: Date; private _updatedAt: Date;
@ -34,9 +76,28 @@ export class ContributionAccountAggregate {
id?: bigint | null; id?: bigint | null;
accountSequence: string; accountSequence: string;
personalContribution?: ContributionAmount; personalContribution?: ContributionAmount;
teamLevelContribution?: ContributionAmount; level1Pending?: ContributionAmount;
teamBonusContribution?: ContributionAmount; level2Pending?: ContributionAmount;
totalContribution?: 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; effectiveContribution?: ContributionAmount;
hasAdopted?: boolean; hasAdopted?: boolean;
directReferralAdoptedCount?: number; directReferralAdoptedCount?: number;
@ -48,11 +109,38 @@ export class ContributionAccountAggregate {
}) { }) {
this._id = props.id ?? null; this._id = props.id ?? null;
this._accountSequence = props.accountSequence; this._accountSequence = props.accountSequence;
this._personalContribution = props.personalContribution ?? ContributionAmount.zero(); this._personalContribution = props.personalContribution ?? ContributionAmount.zero();
this._teamLevelContribution = props.teamLevelContribution ?? ContributionAmount.zero();
this._teamBonusContribution = props.teamBonusContribution ?? ContributionAmount.zero(); // 15级待解锁
this._totalContribution = props.totalContribution ?? ContributionAmount.zero(); 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._effectiveContribution = props.effectiveContribution ?? ContributionAmount.zero();
this._hasAdopted = props.hasAdopted ?? false; this._hasAdopted = props.hasAdopted ?? false;
this._directReferralAdoptedCount = props.directReferralAdoptedCount ?? 0; this._directReferralAdoptedCount = props.directReferralAdoptedCount ?? 0;
this._unlockedLevelDepth = props.unlockedLevelDepth ?? 0; this._unlockedLevelDepth = props.unlockedLevelDepth ?? 0;
@ -66,10 +154,36 @@ export class ContributionAccountAggregate {
get id(): bigint | null { return this._id; } get id(): bigint | null { return this._id; }
get accountSequence(): string { return this._accountSequence; } get accountSequence(): string { return this._accountSequence; }
get personalContribution(): ContributionAmount { return this._personalContribution; } get personalContribution(): ContributionAmount { return this._personalContribution; }
get teamLevelContribution(): ContributionAmount { return this._teamLevelContribution; }
get teamBonusContribution(): ContributionAmount { return this._teamBonusContribution; } // 15级待解锁
get totalContribution(): ContributionAmount { return this._totalContribution; } 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 effectiveContribution(): ContributionAmount { return this._effectiveContribution; }
get hasAdopted(): boolean { return this._hasAdopted; } get hasAdopted(): boolean { return this._hasAdopted; }
get directReferralAdoptedCount(): number { return this._directReferralAdoptedCount; } get directReferralAdoptedCount(): number { return this._directReferralAdoptedCount; }
get unlockedLevelDepth(): number { return this._unlockedLevelDepth; } get unlockedLevelDepth(): number { return this._unlockedLevelDepth; }
@ -79,35 +193,98 @@ export class ContributionAccountAggregate {
get updatedAt(): Date { return this._updatedAt; } get updatedAt(): Date { return this._updatedAt; }
/** /**
* *
*/ */
addPersonalContribution(amount: ContributionAmount): void { addPersonalContribution(amount: ContributionAmount): void {
this._personalContribution = this._personalContribution.add(amount); 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 { addLevelPendingContribution(levelDepth: number, amount: ContributionAmount): void {
this._teamLevelContribution = this._teamLevelContribution.add(amount); if (levelDepth < 1 || levelDepth > 15) {
this.recalculateTotal(); 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 { addBonusTierPendingContribution(tier: number, amount: ContributionAmount): void {
this._teamBonusContribution = this._teamBonusContribution.add(amount); if (tier < 1 || tier > 3) {
this.recalculateTotal(); 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 { markAsAdopted(): void {
if (this._hasAdopted) return; // 已经认种过
this._hasAdopted = true; 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(); 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 { private updateUnlockStatus(): void {
// 层级解锁规则 // 层级解锁规则(只有在已认种的情况下才会解锁)
if (this._directReferralAdoptedCount >= 5) { if (this._hasAdopted) {
this._unlockedLevelDepth = 15; if (this._directReferralAdoptedCount >= 5) {
} else if (this._directReferralAdoptedCount >= 3) { this._unlockedLevelDepth = 15; // 解锁11-15级
this._unlockedLevelDepth = 10; } else if (this._directReferralAdoptedCount >= 3) {
} else if (this._directReferralAdoptedCount >= 1) { this._unlockedLevelDepth = 10; // 解锁6-10级
this._unlockedLevelDepth = 5; } else {
} else { this._unlockedLevelDepth = 5; // 保持1-5级
this._unlockedLevelDepth = 0; }
} }
// 额外奖励解锁规则 // 加成解锁规则
let bonusTiers = 0; let bonusTiers = 0;
if (this._hasAdopted) bonusTiers++; if (this._hasAdopted) bonusTiers = 1; // 自己认种解锁第1档
if (this._directReferralAdoptedCount >= 2) bonusTiers++; if (this._directReferralAdoptedCount >= 2) bonusTiers = 2; // 直推≥2解锁第2档
if (this._directReferralAdoptedCount >= 4) bonusTiers++; if (this._directReferralAdoptedCount >= 4) bonusTiers = 3; // 直推≥4解锁第3档
this._unlockedBonusTiers = bonusTiers; this._unlockedBonusTiers = bonusTiers;
this._updatedAt = new Date(); 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; id: bigint;
accountSequence: string; accountSequence: string;
personalContribution: Decimal; personalContribution: Decimal;
teamLevelContribution: Decimal; level1Pending: Decimal;
teamBonusContribution: Decimal; level2Pending: Decimal;
totalContribution: 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; effectiveContribution: Decimal;
hasAdopted: boolean; hasAdopted: boolean;
directReferralAdoptedCount: number; directReferralAdoptedCount: number;
@ -209,9 +483,28 @@ export class ContributionAccountAggregate {
id: data.id, id: data.id,
accountSequence: data.accountSequence, accountSequence: data.accountSequence,
personalContribution: new ContributionAmount(data.personalContribution), personalContribution: new ContributionAmount(data.personalContribution),
teamLevelContribution: new ContributionAmount(data.teamLevelContribution), level1Pending: new ContributionAmount(data.level1Pending),
teamBonusContribution: new ContributionAmount(data.teamBonusContribution), level2Pending: new ContributionAmount(data.level2Pending),
totalContribution: new ContributionAmount(data.totalContribution), 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), effectiveContribution: new ContributionAmount(data.effectiveContribution),
hasAdopted: data.hasAdopted, hasAdopted: data.hasAdopted,
directReferralAdoptedCount: data.directReferralAdoptedCount, directReferralAdoptedCount: data.directReferralAdoptedCount,
@ -229,9 +522,28 @@ export class ContributionAccountAggregate {
toPersistence(): { toPersistence(): {
accountSequence: string; accountSequence: string;
personalContribution: Decimal; personalContribution: Decimal;
teamLevelContribution: Decimal; level1Pending: Decimal;
teamBonusContribution: Decimal; level2Pending: Decimal;
totalContribution: 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; effectiveContribution: Decimal;
hasAdopted: boolean; hasAdopted: boolean;
directReferralAdoptedCount: number; directReferralAdoptedCount: number;
@ -242,9 +554,28 @@ export class ContributionAccountAggregate {
return { return {
accountSequence: this._accountSequence, accountSequence: this._accountSequence,
personalContribution: this._personalContribution.value, personalContribution: this._personalContribution.value,
teamLevelContribution: this._teamLevelContribution.value, level1Pending: this._level1Pending.value,
teamBonusContribution: this._teamBonusContribution.value, level2Pending: this._level2Pending.value,
totalContribution: this._totalContribution.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, effectiveContribution: this._effectiveContribution.value,
hasAdopted: this._hasAdopted, hasAdopted: this._hasAdopted,
directReferralAdoptedCount: this._directReferralAdoptedCount, directReferralAdoptedCount: this._directReferralAdoptedCount,

View File

@ -43,7 +43,7 @@ export interface IContributionAccountRepository {
findMany(options: { findMany(options: {
page?: number; page?: number;
limit?: number; limit?: number;
orderBy?: 'totalContribution' | 'effectiveContribution'; orderBy?: 'effectiveContribution' | 'personalContribution';
order?: 'asc' | 'desc'; order?: 'asc' | 'desc';
}): Promise<{ }): Promise<{
items: ContributionAccountAggregate[]; items: ContributionAccountAggregate[];

View File

@ -80,22 +80,27 @@ export class ContributionAccountRepository implements IContributionAccountReposi
sourceType: ContributionSourceType, sourceType: ContributionSourceType,
amount: ContributionAmount, amount: ContributionAmount,
): Promise<void> { ): Promise<void> {
const fieldMap: Record<ContributionSourceType, string> = { // 个人算力直接增加到 personalContribution 和 effectiveContribution
[ContributionSourceType.PERSONAL]: 'personalContribution', // 层级/加成算力需要根据解锁状态分配到对应的 pending 字段
[ContributionSourceType.TEAM_LEVEL]: 'teamLevelContribution', if (sourceType === ContributionSourceType.PERSONAL) {
[ContributionSourceType.TEAM_BONUS]: 'teamBonusContribution', await this.client.contributionAccount.update({
}; where: { accountSequence },
data: {
const field = fieldMap[sourceType]; personalContribution: { increment: amount.value },
effectiveContribution: { increment: amount.value },
await this.client.contributionAccount.update({ updatedAt: new Date(),
where: { accountSequence }, },
data: { });
[field]: { increment: amount.value }, } else {
totalContribution: { increment: amount.value }, // 层级和加成算力暂时累加到 effectiveContribution待后续细化分配逻辑
updatedAt: new Date(), await this.client.contributionAccount.update({
}, where: { accountSequence },
}); data: {
effectiveContribution: { increment: amount.value },
updatedAt: new Date(),
},
});
}
} }
async findAllWithPagination(page: number, pageSize: number): Promise<{ async findAllWithPagination(page: number, pageSize: number): Promise<{
@ -106,7 +111,7 @@ export class ContributionAccountRepository implements IContributionAccountReposi
this.client.contributionAccount.findMany({ this.client.contributionAccount.findMany({
skip: (page - 1) * pageSize, skip: (page - 1) * pageSize,
take: pageSize, take: pageSize,
orderBy: { totalContribution: 'desc' }, orderBy: { effectiveContribution: 'desc' },
}), }),
this.client.contributionAccount.count(), this.client.contributionAccount.count(),
]); ]);
@ -120,7 +125,7 @@ export class ContributionAccountRepository implements IContributionAccountReposi
async findMany(options: { async findMany(options: {
page?: number; page?: number;
limit?: number; limit?: number;
orderBy?: 'totalContribution' | 'effectiveContribution'; orderBy?: 'effectiveContribution' | 'personalContribution';
order?: 'asc' | 'desc'; order?: 'asc' | 'desc';
}): Promise<{ }): Promise<{
items: ContributionAccountAggregate[]; items: ContributionAccountAggregate[];
@ -128,7 +133,7 @@ export class ContributionAccountRepository implements IContributionAccountReposi
}> { }> {
const page = options.page ?? 1; const page = options.page ?? 1;
const limit = options.limit ?? 50; const limit = options.limit ?? 50;
const orderBy = options.orderBy ?? 'totalContribution'; const orderBy = options.orderBy ?? 'effectiveContribution';
const order = options.order ?? 'desc'; const order = options.order ?? 'desc';
const [records, total] = await Promise.all([ const [records, total] = await Promise.all([
@ -165,8 +170,8 @@ export class ContributionAccountRepository implements IContributionAccountReposi
async findTopContributors(limit: number): Promise<ContributionAccountAggregate[]> { async findTopContributors(limit: number): Promise<ContributionAccountAggregate[]> {
const records = await this.client.contributionAccount.findMany({ const records = await this.client.contributionAccount.findMany({
where: { totalContribution: { gt: 0 } }, where: { effectiveContribution: { gt: 0 } },
orderBy: { totalContribution: 'desc' }, orderBy: { effectiveContribution: 'desc' },
take: limit, take: limit,
}); });
@ -175,10 +180,10 @@ export class ContributionAccountRepository implements IContributionAccountReposi
async getTotalContribution(): Promise<ContributionAmount> { async getTotalContribution(): Promise<ContributionAmount> {
const result = await this.client.contributionAccount.aggregate({ 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> { async countAccounts(): Promise<number> {
@ -187,7 +192,7 @@ export class ContributionAccountRepository implements IContributionAccountReposi
async countAccountsWithContribution(): Promise<number> { async countAccountsWithContribution(): Promise<number> {
return this.client.contributionAccount.count({ 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, id: record.id,
accountSequence: record.accountSequence, accountSequence: record.accountSequence,
personalContribution: record.personalContribution, personalContribution: record.personalContribution,
teamLevelContribution: record.teamLevelContribution, // 18 个待解锁字段
teamBonusContribution: record.teamBonusContribution, level1Pending: record.level1Pending,
totalContribution: record.totalContribution, 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, effectiveContribution: record.effectiveContribution,
// 解锁状态
hasAdopted: record.hasAdopted, hasAdopted: record.hasAdopted,
directReferralAdoptedCount: record.directReferralAdoptedCount, directReferralAdoptedCount: record.directReferralAdoptedCount,
unlockedLevelDepth: record.unlockedLevelDepth, 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") @@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")
}
// ==================== 挖矿统计 ==================== // ==================== 挖矿统计 ====================
// 每分钟挖矿统计 // 每分钟挖矿统计