refactor(system-accounts): 移除 baseType 字段,使用 accountType+regionCode 复合唯一键
## 主要变更 ### 数据模型简化 - 移除冗余的 baseType 字段,accountType 已包含类型信息 - 使用 accountType (OPERATION/PROVINCE/CITY/HEADQUARTERS) + regionCode (省市代码) 作为复合唯一键 - 所有查询改用 accountType+regionCode,100% 弃用数据库自增 ID ### contribution-service - SystemAccount 表移除 baseType,改用 accountType+regionCode 唯一约束 - 修改算力分配逻辑,省市账户使用对应 regionCode - 事件发布增加 regionCode 字段 ### mining-service - SystemMiningAccount 表使用 accountType+regionCode 唯一约束 - API 改为 /system-accounts/:accountType/records?regionCode=xxx 格式 - 挖矿分配逻辑支持按省市细分 ### mining-admin-service - SyncedSystemContribution 表使用 accountType+regionCode 唯一约束 - CDC 同步处理器适配新格式 - API 统一使用 accountType+regionCode 查询 ## API 示例 - 运营账户: GET /admin/system-accounts/OPERATION/records - 广东省: GET /admin/system-accounts/PROVINCE/records?regionCode=440000 - 广州市: GET /admin/system-accounts/CITY/records?regionCode=440100 - 总部: GET /admin/system-accounts/HEADQUARTERS/records Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
81b2e7a4c2
commit
9062346650
|
|
@ -222,13 +222,12 @@ CREATE INDEX "unallocated_contributions_unalloc_type_idx" ON "unallocated_contri
|
||||||
CREATE INDEX "unallocated_contributions_status_idx" ON "unallocated_contributions"("status");
|
CREATE INDEX "unallocated_contributions_status_idx" ON "unallocated_contributions"("status");
|
||||||
|
|
||||||
-- ============================================
|
-- ============================================
|
||||||
-- 6. 系统账户表 (支持按省市细分)
|
-- 6. 系统账户表
|
||||||
-- ============================================
|
-- ============================================
|
||||||
|
|
||||||
CREATE TABLE "system_accounts" (
|
CREATE TABLE "system_accounts" (
|
||||||
"id" BIGSERIAL NOT NULL,
|
"id" BIGSERIAL NOT NULL,
|
||||||
"account_type" TEXT NOT NULL,
|
"account_type" TEXT NOT NULL,
|
||||||
"base_type" TEXT NOT NULL,
|
|
||||||
"region_code" TEXT,
|
"region_code" TEXT,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
"contribution_balance" DECIMAL(30,10) NOT NULL DEFAULT 0,
|
"contribution_balance" DECIMAL(30,10) NOT NULL DEFAULT 0,
|
||||||
|
|
@ -240,8 +239,8 @@ CREATE TABLE "system_accounts" (
|
||||||
CONSTRAINT "system_accounts_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "system_accounts_pkey" PRIMARY KEY ("id")
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "system_accounts_account_type_key" ON "system_accounts"("account_type");
|
CREATE UNIQUE INDEX "system_accounts_account_type_region_code_key" ON "system_accounts"("account_type", "region_code");
|
||||||
CREATE INDEX "system_accounts_base_type_idx" ON "system_accounts"("base_type");
|
CREATE INDEX "system_accounts_account_type_idx" ON "system_accounts"("account_type");
|
||||||
CREATE INDEX "system_accounts_region_code_idx" ON "system_accounts"("region_code");
|
CREATE INDEX "system_accounts_region_code_idx" ON "system_accounts"("region_code");
|
||||||
|
|
||||||
CREATE TABLE "system_contribution_records" (
|
CREATE TABLE "system_contribution_records" (
|
||||||
|
|
|
||||||
|
|
@ -298,13 +298,10 @@ model UnallocatedContribution {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 系统账户(运营/省/市/总部)
|
// 系统账户(运营/省/市/总部)
|
||||||
// accountType 格式: OPERATION, PROVINCE, CITY, HEADQUARTERS (汇总账户)
|
|
||||||
// PROVINCE_440000, CITY_440100 等 (按省市细分的账户)
|
|
||||||
model SystemAccount {
|
model SystemAccount {
|
||||||
id BigInt @id @default(autoincrement())
|
id BigInt @id @default(autoincrement())
|
||||||
accountType String @unique @map("account_type") // 组合键: PROVINCE_440000, CITY_440100 等
|
accountType String @map("account_type") // OPERATION / PROVINCE / CITY / HEADQUARTERS
|
||||||
baseType String @map("base_type") // 基础类型: OPERATION / PROVINCE / CITY / HEADQUARTERS
|
regionCode String? @map("region_code") // 省/市代码,如 440000, 440100
|
||||||
regionCode String? @map("region_code") // 区域代码: 省/市代码,如 440000, 440100
|
|
||||||
name String
|
name String
|
||||||
|
|
||||||
contributionBalance Decimal @default(0) @map("contribution_balance") @db.Decimal(30, 10)
|
contributionBalance Decimal @default(0) @map("contribution_balance") @db.Decimal(30, 10)
|
||||||
|
|
@ -317,7 +314,8 @@ model SystemAccount {
|
||||||
|
|
||||||
records SystemContributionRecord[]
|
records SystemContributionRecord[]
|
||||||
|
|
||||||
@@index([baseType])
|
@@unique([accountType, regionCode])
|
||||||
|
@@index([accountType])
|
||||||
@@index([regionCode])
|
@@index([regionCode])
|
||||||
@@map("system_accounts")
|
@@map("system_accounts")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -438,8 +438,7 @@ export class AdminController {
|
||||||
const events = systemAccounts.map((account) => {
|
const events = systemAccounts.map((account) => {
|
||||||
const event = new SystemAccountSyncedEvent(
|
const event = new SystemAccountSyncedEvent(
|
||||||
account.accountType,
|
account.accountType,
|
||||||
account.baseType, // 基础类型
|
account.regionCode,
|
||||||
account.regionCode, // 区域代码
|
|
||||||
account.name,
|
account.name,
|
||||||
account.contributionBalance.toString(),
|
account.contributionBalance.toString(),
|
||||||
account.createdAt,
|
account.createdAt,
|
||||||
|
|
@ -447,7 +446,7 @@ export class AdminController {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
aggregateType: SystemAccountSyncedEvent.AGGREGATE_TYPE,
|
aggregateType: SystemAccountSyncedEvent.AGGREGATE_TYPE,
|
||||||
aggregateId: account.accountType,
|
aggregateId: `${account.accountType}:${account.regionCode || 'null'}`,
|
||||||
eventType: SystemAccountSyncedEvent.EVENT_TYPE,
|
eventType: SystemAccountSyncedEvent.EVENT_TYPE,
|
||||||
payload: event.toPayload(),
|
payload: event.toPayload(),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -332,44 +332,46 @@ export class ContributionCalculationService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 保存系统账户算力并发布同步事件(支持按省市细分)
|
// 5. 保存系统账户算力并发布同步事件
|
||||||
if (result.systemContributions.length > 0) {
|
if (result.systemContributions.length > 0) {
|
||||||
await this.systemAccountRepository.ensureSystemAccountsExist();
|
await this.systemAccountRepository.ensureSystemAccountsExist();
|
||||||
|
|
||||||
for (const sys of result.systemContributions) {
|
for (const sys of result.systemContributions) {
|
||||||
// 动态创建/更新系统账户(支持省市细分)
|
// 动态创建/更新系统账户
|
||||||
await this.systemAccountRepository.addContribution(
|
await this.systemAccountRepository.addContribution(
|
||||||
sys.accountType,
|
sys.accountType,
|
||||||
sys.baseType,
|
|
||||||
sys.regionCode,
|
sys.regionCode,
|
||||||
sys.amount,
|
sys.amount,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 保存算力明细记录并获取保存后的记录(带ID)
|
// 保存算力明细记录
|
||||||
const savedRecord = await this.systemAccountRepository.saveContributionRecord({
|
const savedRecord = await this.systemAccountRepository.saveContributionRecord({
|
||||||
systemAccountType: sys.accountType,
|
accountType: sys.accountType,
|
||||||
|
regionCode: sys.regionCode,
|
||||||
sourceAdoptionId,
|
sourceAdoptionId,
|
||||||
sourceAccountSequence,
|
sourceAccountSequence,
|
||||||
distributionRate: sys.rate.value.toNumber(),
|
distributionRate: sys.rate.value.toNumber(),
|
||||||
amount: sys.amount,
|
amount: sys.amount,
|
||||||
effectiveDate,
|
effectiveDate,
|
||||||
expireDate: null, // System account contributions never expire based on the schema's contributionNeverExpires field
|
expireDate: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 发布系统账户同步事件(用于 mining-service 同步系统账户算力)
|
// 发布系统账户同步事件(用于 mining-service 同步系统账户算力)
|
||||||
const systemAccount = await this.systemAccountRepository.findByType(sys.accountType);
|
const systemAccount = await this.systemAccountRepository.findByTypeAndRegion(
|
||||||
|
sys.accountType,
|
||||||
|
sys.regionCode,
|
||||||
|
);
|
||||||
if (systemAccount) {
|
if (systemAccount) {
|
||||||
const event = new SystemAccountSyncedEvent(
|
const event = new SystemAccountSyncedEvent(
|
||||||
sys.accountType,
|
sys.accountType,
|
||||||
sys.baseType, // 新增:基础类型
|
sys.regionCode,
|
||||||
sys.regionCode, // 新增:区域代码
|
|
||||||
systemAccount.name,
|
systemAccount.name,
|
||||||
systemAccount.contributionBalance.value.toString(),
|
systemAccount.contributionBalance.value.toString(),
|
||||||
systemAccount.createdAt,
|
systemAccount.createdAt,
|
||||||
);
|
);
|
||||||
await this.outboxRepository.save({
|
await this.outboxRepository.save({
|
||||||
aggregateType: SystemAccountSyncedEvent.AGGREGATE_TYPE,
|
aggregateType: SystemAccountSyncedEvent.AGGREGATE_TYPE,
|
||||||
aggregateId: sys.accountType,
|
aggregateId: `${sys.accountType}:${sys.regionCode || 'null'}`,
|
||||||
eventType: SystemAccountSyncedEvent.EVENT_TYPE,
|
eventType: SystemAccountSyncedEvent.EVENT_TYPE,
|
||||||
payload: event.toPayload(),
|
payload: event.toPayload(),
|
||||||
});
|
});
|
||||||
|
|
@ -383,7 +385,7 @@ export class ContributionCalculationService {
|
||||||
sys.rate.value.toNumber(),
|
sys.rate.value.toNumber(),
|
||||||
sys.amount.value.toString(),
|
sys.amount.value.toString(),
|
||||||
effectiveDate,
|
effectiveDate,
|
||||||
null, // System account contributions never expire
|
null,
|
||||||
savedRecord.createdAt,
|
savedRecord.createdAt,
|
||||||
);
|
);
|
||||||
await this.outboxRepository.save({
|
await this.outboxRepository.save({
|
||||||
|
|
|
||||||
|
|
@ -121,11 +121,15 @@ export class ContributionDistributionPublisherService {
|
||||||
return result.systemContributions.map((sys) => ({
|
return result.systemContributions.map((sys) => ({
|
||||||
accountType: sys.accountType,
|
accountType: sys.accountType,
|
||||||
amount: sys.amount.value.toString(),
|
amount: sys.amount.value.toString(),
|
||||||
|
// 直接使用 regionCode,没有就用传入的参数
|
||||||
provinceCode:
|
provinceCode:
|
||||||
sys.accountType === 'PROVINCE' || sys.accountType === 'CITY'
|
sys.accountType === 'PROVINCE'
|
||||||
? provinceCode
|
? sys.regionCode || provinceCode
|
||||||
: undefined,
|
: sys.accountType === 'CITY'
|
||||||
cityCode: sys.accountType === 'CITY' ? cityCode : undefined,
|
? sys.regionCode || cityCode
|
||||||
|
: undefined,
|
||||||
|
cityCode:
|
||||||
|
sys.accountType === 'CITY' ? sys.regionCode || cityCode : undefined,
|
||||||
neverExpires: sys.accountType === 'OPERATION', // 运营账户永不过期
|
neverExpires: sys.accountType === 'OPERATION', // 运营账户永不过期
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
/**
|
/**
|
||||||
* 系统账户算力同步事件
|
* 系统账户算力同步事件
|
||||||
* 用于将系统账户(运营、省、市、总部)的算力同步到 mining-service
|
* 用于将系统账户(运营、省、市、总部)的算力同步到 mining-service
|
||||||
* 支持按省市细分的账户(如 PROVINCE_440000, CITY_440100)
|
|
||||||
*/
|
*/
|
||||||
export class SystemAccountSyncedEvent {
|
export class SystemAccountSyncedEvent {
|
||||||
static readonly EVENT_TYPE = 'SystemAccountSynced';
|
static readonly EVENT_TYPE = 'SystemAccountSynced';
|
||||||
static readonly AGGREGATE_TYPE = 'SystemAccount';
|
static readonly AGGREGATE_TYPE = 'SystemAccount';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly accountType: string, // 组合键: OPERATION, PROVINCE_440000, CITY_440100 等
|
public readonly accountType: string, // OPERATION / PROVINCE / CITY / HEADQUARTERS
|
||||||
public readonly baseType: string, // 基础类型: OPERATION / PROVINCE / CITY / HEADQUARTERS
|
public readonly regionCode: string | null, // 省/市代码,如 440000, 440100
|
||||||
public readonly regionCode: string | null, // 区域代码: 省/市代码,如 440000, 440100
|
|
||||||
public readonly name: string,
|
public readonly name: string,
|
||||||
public readonly contributionBalance: string,
|
public readonly contributionBalance: string,
|
||||||
public readonly createdAt: Date,
|
public readonly createdAt: Date,
|
||||||
|
|
@ -20,7 +18,6 @@ export class SystemAccountSyncedEvent {
|
||||||
return {
|
return {
|
||||||
eventType: SystemAccountSyncedEvent.EVENT_TYPE,
|
eventType: SystemAccountSyncedEvent.EVENT_TYPE,
|
||||||
accountType: this.accountType,
|
accountType: this.accountType,
|
||||||
baseType: this.baseType,
|
|
||||||
regionCode: this.regionCode,
|
regionCode: this.regionCode,
|
||||||
name: this.name,
|
name: this.name,
|
||||||
contributionBalance: this.contributionBalance,
|
contributionBalance: this.contributionBalance,
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,12 @@ import { ContributionAccountAggregate, ContributionSourceType } from '../aggrega
|
||||||
import { ContributionRecordAggregate } from '../aggregates/contribution-record.aggregate';
|
import { ContributionRecordAggregate } from '../aggregates/contribution-record.aggregate';
|
||||||
import { SyncedAdoption, SyncedReferral } from '../repositories/synced-data.repository.interface';
|
import { SyncedAdoption, SyncedReferral } from '../repositories/synced-data.repository.interface';
|
||||||
|
|
||||||
// 基础类型枚举
|
|
||||||
export type SystemAccountBaseType = 'OPERATION' | 'PROVINCE' | 'CITY' | 'HEADQUARTERS';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统账户贡献值分配
|
* 系统账户贡献值分配
|
||||||
*/
|
*/
|
||||||
export interface SystemContributionAllocation {
|
export interface SystemContributionAllocation {
|
||||||
accountType: string; // 组合键: OPERATION, PROVINCE_440000, CITY_440100 等
|
accountType: 'OPERATION' | 'PROVINCE' | 'CITY' | 'HEADQUARTERS';
|
||||||
baseType: SystemAccountBaseType; // 基础类型
|
regionCode: string | null; // 省市代码,如 440000、440100
|
||||||
regionCode: string | null; // 区域代码
|
|
||||||
rate: DistributionRate;
|
rate: DistributionRate;
|
||||||
amount: ContributionAmount;
|
amount: ContributionAmount;
|
||||||
}
|
}
|
||||||
|
|
@ -98,7 +94,6 @@ export class ContributionCalculatorService {
|
||||||
// 运营账户(全国)- 12%
|
// 运营账户(全国)- 12%
|
||||||
result.systemContributions.push({
|
result.systemContributions.push({
|
||||||
accountType: 'OPERATION',
|
accountType: 'OPERATION',
|
||||||
baseType: 'OPERATION',
|
|
||||||
regionCode: null,
|
regionCode: null,
|
||||||
rate: DistributionRate.OPERATION,
|
rate: DistributionRate.OPERATION,
|
||||||
amount: totalContribution.multiply(DistributionRate.OPERATION.value),
|
amount: totalContribution.multiply(DistributionRate.OPERATION.value),
|
||||||
|
|
@ -106,47 +101,21 @@ export class ContributionCalculatorService {
|
||||||
|
|
||||||
// 省公司账户 - 1%(按认种选择的省份)
|
// 省公司账户 - 1%(按认种选择的省份)
|
||||||
const provinceCode = adoption.selectedProvince;
|
const provinceCode = adoption.selectedProvince;
|
||||||
if (provinceCode) {
|
result.systemContributions.push({
|
||||||
// 有省份时分配到具体省份账户
|
accountType: 'PROVINCE',
|
||||||
result.systemContributions.push({
|
regionCode: provinceCode || null,
|
||||||
accountType: `PROVINCE_${provinceCode}`,
|
rate: DistributionRate.PROVINCE,
|
||||||
baseType: 'PROVINCE',
|
amount: totalContribution.multiply(DistributionRate.PROVINCE.value),
|
||||||
regionCode: provinceCode,
|
});
|
||||||
rate: DistributionRate.PROVINCE,
|
|
||||||
amount: totalContribution.multiply(DistributionRate.PROVINCE.value),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 无省份时归汇总账户
|
|
||||||
result.systemContributions.push({
|
|
||||||
accountType: 'PROVINCE',
|
|
||||||
baseType: 'PROVINCE',
|
|
||||||
regionCode: null,
|
|
||||||
rate: DistributionRate.PROVINCE,
|
|
||||||
amount: totalContribution.multiply(DistributionRate.PROVINCE.value),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 市公司账户 - 2%(按认种选择的城市)
|
// 市公司账户 - 2%(按认种选择的城市)
|
||||||
const cityCode = adoption.selectedCity;
|
const cityCode = adoption.selectedCity;
|
||||||
if (cityCode) {
|
result.systemContributions.push({
|
||||||
// 有城市时分配到具体城市账户
|
accountType: 'CITY',
|
||||||
result.systemContributions.push({
|
regionCode: cityCode || null,
|
||||||
accountType: `CITY_${cityCode}`,
|
rate: DistributionRate.CITY,
|
||||||
baseType: 'CITY',
|
amount: totalContribution.multiply(DistributionRate.CITY.value),
|
||||||
regionCode: cityCode,
|
});
|
||||||
rate: DistributionRate.CITY,
|
|
||||||
amount: totalContribution.multiply(DistributionRate.CITY.value),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 无城市时归汇总账户
|
|
||||||
result.systemContributions.push({
|
|
||||||
accountType: 'CITY',
|
|
||||||
baseType: 'CITY',
|
|
||||||
regionCode: null,
|
|
||||||
rate: DistributionRate.CITY,
|
|
||||||
amount: totalContribution.multiply(DistributionRate.CITY.value),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 团队贡献值 (15%)
|
// 3. 团队贡献值 (15%)
|
||||||
this.distributeTeamContribution(
|
this.distributeTeamContribution(
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,12 @@ import { Injectable } from '@nestjs/common';
|
||||||
import { ContributionAmount } from '../../../domain/value-objects/contribution-amount.vo';
|
import { ContributionAmount } from '../../../domain/value-objects/contribution-amount.vo';
|
||||||
import { UnitOfWork, TransactionClient } from '../unit-of-work/unit-of-work';
|
import { UnitOfWork, TransactionClient } from '../unit-of-work/unit-of-work';
|
||||||
|
|
||||||
// 基础类型枚举
|
export type SystemAccountType = 'OPERATION' | 'PROVINCE' | 'CITY' | 'HEADQUARTERS';
|
||||||
export type SystemAccountBaseType = 'OPERATION' | 'PROVINCE' | 'CITY' | 'HEADQUARTERS';
|
|
||||||
|
|
||||||
export interface SystemAccount {
|
export interface SystemAccount {
|
||||||
id: bigint;
|
id: bigint;
|
||||||
accountType: string; // 组合键: PROVINCE_440000, CITY_440100 等
|
accountType: SystemAccountType;
|
||||||
baseType: SystemAccountBaseType; // 基础类型
|
regionCode: string | null; // 省/市代码
|
||||||
regionCode: string | null; // 区域代码
|
|
||||||
name: string;
|
name: string;
|
||||||
contributionBalance: ContributionAmount;
|
contributionBalance: ContributionAmount;
|
||||||
contributionNeverExpires: boolean;
|
contributionNeverExpires: boolean;
|
||||||
|
|
@ -40,11 +38,16 @@ export class SystemAccountRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据 accountType(组合键)查找系统账户
|
* 根据 accountType + regionCode 查找系统账户
|
||||||
*/
|
*/
|
||||||
async findByType(accountType: string): Promise<SystemAccount | null> {
|
async findByTypeAndRegion(
|
||||||
|
accountType: SystemAccountType,
|
||||||
|
regionCode: string | null,
|
||||||
|
): Promise<SystemAccount | null> {
|
||||||
const record = await this.client.systemAccount.findUnique({
|
const record = await this.client.systemAccount.findUnique({
|
||||||
where: { accountType },
|
where: {
|
||||||
|
accountType_regionCode: { accountType, regionCode },
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!record) {
|
if (!record) {
|
||||||
|
|
@ -55,12 +58,12 @@ export class SystemAccountRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据基础类型查找所有账户(如查找所有 CITY 类型账户)
|
* 根据类型查找所有账户(如查找所有 CITY 类型账户)
|
||||||
*/
|
*/
|
||||||
async findByBaseType(baseType: SystemAccountBaseType): Promise<SystemAccount[]> {
|
async findByType(accountType: SystemAccountType): Promise<SystemAccount[]> {
|
||||||
const records = await this.client.systemAccount.findMany({
|
const records = await this.client.systemAccount.findMany({
|
||||||
where: { baseType },
|
where: { accountType },
|
||||||
orderBy: { accountType: 'asc' },
|
orderBy: { regionCode: 'asc' },
|
||||||
});
|
});
|
||||||
|
|
||||||
return records.map((r) => this.toSystemAccount(r));
|
return records.map((r) => this.toSystemAccount(r));
|
||||||
|
|
@ -68,29 +71,28 @@ export class SystemAccountRepository {
|
||||||
|
|
||||||
async findAll(): Promise<SystemAccount[]> {
|
async findAll(): Promise<SystemAccount[]> {
|
||||||
const records = await this.client.systemAccount.findMany({
|
const records = await this.client.systemAccount.findMany({
|
||||||
orderBy: { accountType: 'asc' },
|
orderBy: [{ accountType: 'asc' }, { regionCode: 'asc' }],
|
||||||
});
|
});
|
||||||
|
|
||||||
return records.map((r) => this.toSystemAccount(r));
|
return records.map((r) => this.toSystemAccount(r));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 确保基础的汇总账户存在(向后兼容)
|
* 确保基础账户存在
|
||||||
*/
|
*/
|
||||||
async ensureSystemAccountsExist(): Promise<void> {
|
async ensureSystemAccountsExist(): Promise<void> {
|
||||||
const accounts: { accountType: string; baseType: SystemAccountBaseType; name: string }[] = [
|
const accounts: { accountType: SystemAccountType; name: string }[] = [
|
||||||
{ accountType: 'OPERATION', baseType: 'OPERATION', name: '运营账户' },
|
{ accountType: 'OPERATION', name: '运营账户' },
|
||||||
{ accountType: 'PROVINCE', baseType: 'PROVINCE', name: '省公司账户' },
|
{ accountType: 'HEADQUARTERS', name: '总部账户' },
|
||||||
{ accountType: 'CITY', baseType: 'CITY', name: '市公司账户' },
|
|
||||||
{ accountType: 'HEADQUARTERS', baseType: 'HEADQUARTERS', name: '总部账户' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const account of accounts) {
|
for (const account of accounts) {
|
||||||
await this.client.systemAccount.upsert({
|
await this.client.systemAccount.upsert({
|
||||||
where: { accountType: account.accountType },
|
where: {
|
||||||
|
accountType_regionCode: { accountType: account.accountType, regionCode: null },
|
||||||
|
},
|
||||||
create: {
|
create: {
|
||||||
accountType: account.accountType,
|
accountType: account.accountType,
|
||||||
baseType: account.baseType,
|
|
||||||
regionCode: null,
|
regionCode: null,
|
||||||
name: account.name,
|
name: account.name,
|
||||||
contributionBalance: 0,
|
contributionBalance: 0,
|
||||||
|
|
@ -103,24 +105,20 @@ export class SystemAccountRepository {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加算力到系统账户(动态创建账户)
|
* 添加算力到系统账户(动态创建账户)
|
||||||
* @param accountType 组合键,如 PROVINCE_440000, CITY_440100
|
|
||||||
* @param baseType 基础类型: OPERATION, PROVINCE, CITY, HEADQUARTERS
|
|
||||||
* @param regionCode 区域代码: 省/市代码,如 440000, 440100
|
|
||||||
* @param amount 算力金额
|
|
||||||
*/
|
*/
|
||||||
async addContribution(
|
async addContribution(
|
||||||
accountType: string,
|
accountType: SystemAccountType,
|
||||||
baseType: SystemAccountBaseType,
|
|
||||||
regionCode: string | null,
|
regionCode: string | null,
|
||||||
amount: ContributionAmount,
|
amount: ContributionAmount,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const name = this.getAccountName(baseType, regionCode);
|
const name = this.getAccountName(accountType, regionCode);
|
||||||
|
|
||||||
await this.client.systemAccount.upsert({
|
await this.client.systemAccount.upsert({
|
||||||
where: { accountType },
|
where: {
|
||||||
|
accountType_regionCode: { accountType, regionCode },
|
||||||
|
},
|
||||||
create: {
|
create: {
|
||||||
accountType,
|
accountType,
|
||||||
baseType,
|
|
||||||
regionCode,
|
regionCode,
|
||||||
name,
|
name,
|
||||||
contributionBalance: amount.value,
|
contributionBalance: amount.value,
|
||||||
|
|
@ -135,21 +133,22 @@ export class SystemAccountRepository {
|
||||||
/**
|
/**
|
||||||
* 生成账户名称
|
* 生成账户名称
|
||||||
*/
|
*/
|
||||||
private getAccountName(baseType: SystemAccountBaseType, regionCode: string | null): string {
|
private getAccountName(accountType: SystemAccountType, regionCode: string | null): string {
|
||||||
if (!regionCode) {
|
if (!regionCode) {
|
||||||
const names: Record<SystemAccountBaseType, string> = {
|
const names: Record<SystemAccountType, string> = {
|
||||||
OPERATION: '运营账户',
|
OPERATION: '运营账户',
|
||||||
PROVINCE: '省公司账户',
|
PROVINCE: '省公司账户',
|
||||||
CITY: '市公司账户',
|
CITY: '市公司账户',
|
||||||
HEADQUARTERS: '总部账户',
|
HEADQUARTERS: '总部账户',
|
||||||
};
|
};
|
||||||
return names[baseType] || baseType;
|
return names[accountType] || accountType;
|
||||||
}
|
}
|
||||||
return `${regionCode}账户`;
|
return `${regionCode}账户`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveContributionRecord(record: {
|
async saveContributionRecord(record: {
|
||||||
systemAccountType: string; // 改为 string 支持组合键
|
accountType: SystemAccountType;
|
||||||
|
regionCode: string | null;
|
||||||
sourceAdoptionId: bigint;
|
sourceAdoptionId: bigint;
|
||||||
sourceAccountSequence: string;
|
sourceAccountSequence: string;
|
||||||
distributionRate: number;
|
distributionRate: number;
|
||||||
|
|
@ -157,9 +156,9 @@ export class SystemAccountRepository {
|
||||||
effectiveDate: Date;
|
effectiveDate: Date;
|
||||||
expireDate?: Date | null;
|
expireDate?: Date | null;
|
||||||
}): Promise<SystemContributionRecord> {
|
}): Promise<SystemContributionRecord> {
|
||||||
const systemAccount = await this.findByType(record.systemAccountType);
|
const systemAccount = await this.findByTypeAndRegion(record.accountType, record.regionCode);
|
||||||
if (!systemAccount) {
|
if (!systemAccount) {
|
||||||
throw new Error(`System account ${record.systemAccountType} not found`);
|
throw new Error(`System account ${record.accountType}:${record.regionCode} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const created = await this.client.systemContributionRecord.create({
|
const created = await this.client.systemContributionRecord.create({
|
||||||
|
|
@ -177,42 +176,13 @@ export class SystemAccountRepository {
|
||||||
return this.toContributionRecord(created);
|
return this.toContributionRecord(created);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveContributionRecords(records: {
|
|
||||||
systemAccountType: string; // 改为 string 支持组合键
|
|
||||||
sourceAdoptionId: bigint;
|
|
||||||
sourceAccountSequence: string;
|
|
||||||
distributionRate: number;
|
|
||||||
amount: ContributionAmount;
|
|
||||||
effectiveDate: Date;
|
|
||||||
expireDate?: Date | null;
|
|
||||||
}[]): Promise<void> {
|
|
||||||
if (records.length === 0) return;
|
|
||||||
|
|
||||||
const systemAccounts = await this.findAll();
|
|
||||||
const accountMap = new Map<string, bigint>();
|
|
||||||
for (const account of systemAccounts) {
|
|
||||||
accountMap.set(account.accountType, account.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.client.systemContributionRecord.createMany({
|
|
||||||
data: records.map((r) => ({
|
|
||||||
systemAccountId: accountMap.get(r.systemAccountType)!,
|
|
||||||
sourceAdoptionId: r.sourceAdoptionId,
|
|
||||||
sourceAccountSequence: r.sourceAccountSequence,
|
|
||||||
distributionRate: r.distributionRate,
|
|
||||||
amount: r.amount.value,
|
|
||||||
effectiveDate: r.effectiveDate,
|
|
||||||
expireDate: r.expireDate ?? null,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async findContributionRecords(
|
async findContributionRecords(
|
||||||
systemAccountType: string, // 改为 string 支持组合键
|
accountType: SystemAccountType,
|
||||||
|
regionCode: string | null,
|
||||||
page: number,
|
page: number,
|
||||||
pageSize: number,
|
pageSize: number,
|
||||||
): Promise<{ data: SystemContributionRecord[]; total: number }> {
|
): Promise<{ data: SystemContributionRecord[]; total: number }> {
|
||||||
const systemAccount = await this.findByType(systemAccountType);
|
const systemAccount = await this.findByTypeAndRegion(accountType, regionCode);
|
||||||
if (!systemAccount) {
|
if (!systemAccount) {
|
||||||
return { data: [], total: 0 };
|
return { data: [], total: 0 };
|
||||||
}
|
}
|
||||||
|
|
@ -238,8 +208,7 @@ export class SystemAccountRepository {
|
||||||
private toSystemAccount(record: any): SystemAccount {
|
private toSystemAccount(record: any): SystemAccount {
|
||||||
return {
|
return {
|
||||||
id: record.id,
|
id: record.id,
|
||||||
accountType: record.accountType,
|
accountType: record.accountType as SystemAccountType,
|
||||||
baseType: record.baseType as SystemAccountBaseType,
|
|
||||||
regionCode: record.regionCode,
|
regionCode: record.regionCode,
|
||||||
name: record.name,
|
name: record.name,
|
||||||
contributionBalance: new ContributionAmount(record.contributionBalance),
|
contributionBalance: new ContributionAmount(record.contributionBalance),
|
||||||
|
|
|
||||||
|
|
@ -302,11 +302,10 @@ CREATE TABLE "synced_circulation_pools" (
|
||||||
CONSTRAINT "synced_circulation_pools_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "synced_circulation_pools_pkey" PRIMARY KEY ("id")
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable: 系统账户算力 (from contribution-service)
|
||||||
CREATE TABLE "synced_system_contributions" (
|
CREATE TABLE "synced_system_contributions" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"accountType" TEXT NOT NULL,
|
"accountType" TEXT NOT NULL,
|
||||||
"base_type" TEXT NOT NULL DEFAULT '',
|
|
||||||
"region_code" TEXT,
|
"region_code" TEXT,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
"contributionBalance" DECIMAL(30,8) NOT NULL DEFAULT 0,
|
"contributionBalance" DECIMAL(30,8) NOT NULL DEFAULT 0,
|
||||||
|
|
@ -689,11 +688,10 @@ CREATE UNIQUE INDEX "synced_daily_mining_stats_statDate_key" ON "synced_daily_mi
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "synced_day_klines_klineDate_key" ON "synced_day_klines"("klineDate");
|
CREATE UNIQUE INDEX "synced_day_klines_klineDate_key" ON "synced_day_klines"("klineDate");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex: synced_system_contributions
|
||||||
CREATE UNIQUE INDEX "synced_system_contributions_accountType_key" ON "synced_system_contributions"("accountType");
|
-- 使用 accountType + region_code 复合唯一键
|
||||||
|
CREATE UNIQUE INDEX "synced_system_contributions_accountType_region_code_key" ON "synced_system_contributions"("accountType", "region_code");
|
||||||
-- CreateIndex (base_type 和 region_code 索引)
|
CREATE INDEX "synced_system_contributions_accountType_idx" ON "synced_system_contributions"("accountType");
|
||||||
CREATE INDEX "synced_system_contributions_base_type_idx" ON "synced_system_contributions"("base_type");
|
|
||||||
CREATE INDEX "synced_system_contributions_region_code_idx" ON "synced_system_contributions"("region_code");
|
CREATE INDEX "synced_system_contributions_region_code_idx" ON "synced_system_contributions"("region_code");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
|
|
@ -869,11 +867,12 @@ ALTER TABLE "audit_logs" ADD CONSTRAINT "audit_logs_adminId_fkey" FOREIGN KEY ("
|
||||||
-- 用于存储从 contribution-service 同步的系统账户算力来源明细
|
-- 用于存储从 contribution-service 同步的系统账户算力来源明细
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable: 系统账户算力明细 (from contribution-service)
|
||||||
CREATE TABLE "synced_system_contribution_records" (
|
CREATE TABLE "synced_system_contribution_records" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"original_record_id" BIGINT NOT NULL,
|
"original_record_id" BIGINT NOT NULL,
|
||||||
"system_account_type" TEXT NOT NULL,
|
"account_type" TEXT NOT NULL,
|
||||||
|
"region_code" TEXT,
|
||||||
"source_adoption_id" BIGINT NOT NULL,
|
"source_adoption_id" BIGINT NOT NULL,
|
||||||
"source_account_sequence" TEXT NOT NULL,
|
"source_account_sequence" TEXT NOT NULL,
|
||||||
"distribution_rate" DECIMAL(10,6) NOT NULL,
|
"distribution_rate" DECIMAL(10,6) NOT NULL,
|
||||||
|
|
@ -890,7 +889,7 @@ CREATE TABLE "synced_system_contribution_records" (
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "synced_system_contribution_records_original_record_id_key" ON "synced_system_contribution_records"("original_record_id");
|
CREATE UNIQUE INDEX "synced_system_contribution_records_original_record_id_key" ON "synced_system_contribution_records"("original_record_id");
|
||||||
CREATE INDEX "synced_system_contribution_records_system_account_type_idx" ON "synced_system_contribution_records"("system_account_type");
|
CREATE INDEX "synced_system_contribution_records_account_type_region_code_idx" ON "synced_system_contribution_records"("account_type", "region_code");
|
||||||
CREATE INDEX "synced_system_contribution_records_source_adoption_id_idx" ON "synced_system_contribution_records"("source_adoption_id");
|
CREATE INDEX "synced_system_contribution_records_source_adoption_id_idx" ON "synced_system_contribution_records"("source_adoption_id");
|
||||||
CREATE INDEX "synced_system_contribution_records_source_account_sequence_idx" ON "synced_system_contribution_records"("source_account_sequence");
|
CREATE INDEX "synced_system_contribution_records_source_account_sequence_idx" ON "synced_system_contribution_records"("source_account_sequence");
|
||||||
CREATE INDEX "synced_system_contribution_records_created_at_idx" ON "synced_system_contribution_records"("created_at" DESC);
|
CREATE INDEX "synced_system_contribution_records_created_at_idx" ON "synced_system_contribution_records"("created_at" DESC);
|
||||||
|
|
|
||||||
|
|
@ -422,19 +422,16 @@ model SyncedCirculationPool {
|
||||||
|
|
||||||
model SyncedSystemContribution {
|
model SyncedSystemContribution {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
accountType String @unique // 组合键: OPERATION, PROVINCE_440000, CITY_440100 等
|
accountType String // OPERATION / PROVINCE / CITY / HEADQUARTERS
|
||||||
baseType String @default("") @map("base_type") // 基础类型: OPERATION / PROVINCE / CITY / HEADQUARTERS
|
regionCode String? @map("region_code") // 省/市代码,如 440000, 440100
|
||||||
regionCode String? @map("region_code") // 区域代码: 省/市代码,如 440000, 440100
|
|
||||||
name String
|
name String
|
||||||
contributionBalance Decimal @db.Decimal(30, 8) @default(0)
|
contributionBalance Decimal @db.Decimal(30, 8) @default(0)
|
||||||
contributionNeverExpires Boolean @default(false)
|
contributionNeverExpires Boolean @default(false)
|
||||||
syncedAt DateTime @default(now())
|
syncedAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
// 关联算力明细记录
|
@@unique([accountType, regionCode])
|
||||||
records SyncedSystemContributionRecord[]
|
@@index([accountType])
|
||||||
|
|
||||||
@@index([baseType])
|
|
||||||
@@index([regionCode])
|
@@index([regionCode])
|
||||||
@@map("synced_system_contributions")
|
@@map("synced_system_contributions")
|
||||||
}
|
}
|
||||||
|
|
@ -446,7 +443,10 @@ model SyncedSystemContribution {
|
||||||
model SyncedSystemContributionRecord {
|
model SyncedSystemContributionRecord {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
originalRecordId BigInt @unique @map("original_record_id") // contribution-service 中的原始 ID
|
originalRecordId BigInt @unique @map("original_record_id") // contribution-service 中的原始 ID
|
||||||
systemAccountType String @map("system_account_type") // 关联的系统账户类型 (组合键)
|
|
||||||
|
// 系统账户信息(冗余存储,便于查询)
|
||||||
|
accountType String @map("account_type") // OPERATION / PROVINCE / CITY / HEADQUARTERS
|
||||||
|
regionCode String? @map("region_code") // 省/市代码
|
||||||
|
|
||||||
// 来源信息
|
// 来源信息
|
||||||
sourceAdoptionId BigInt @map("source_adoption_id") // 来源认种ID
|
sourceAdoptionId BigInt @map("source_adoption_id") // 来源认种ID
|
||||||
|
|
@ -465,10 +465,7 @@ model SyncedSystemContributionRecord {
|
||||||
syncedAt DateTime @default(now())
|
syncedAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
// 关联系统账户
|
@@index([accountType, regionCode])
|
||||||
systemContribution SyncedSystemContribution @relation(fields: [systemAccountType], references: [accountType])
|
|
||||||
|
|
||||||
@@index([systemAccountType])
|
|
||||||
@@index([sourceAdoptionId])
|
@@index([sourceAdoptionId])
|
||||||
@@index([sourceAccountSequence])
|
@@index([sourceAccountSequence])
|
||||||
@@index([createdAt(sort: Desc)])
|
@@index([createdAt(sort: Desc)])
|
||||||
|
|
|
||||||
|
|
@ -22,16 +22,19 @@ export class SystemAccountsController {
|
||||||
|
|
||||||
@Get(':accountType/records')
|
@Get(':accountType/records')
|
||||||
@ApiOperation({ summary: '获取系统账户挖矿记录' })
|
@ApiOperation({ summary: '获取系统账户挖矿记录' })
|
||||||
@ApiParam({ name: 'accountType', type: String, description: '系统账户类型' })
|
@ApiParam({ name: 'accountType', type: String, description: '系统账户类型(OPERATION/PROVINCE/CITY/HEADQUARTERS)' })
|
||||||
|
@ApiQuery({ name: 'regionCode', required: false, type: String, description: '区域代码(省/市代码)' })
|
||||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||||
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
||||||
async getSystemAccountMiningRecords(
|
async getSystemAccountMiningRecords(
|
||||||
@Param('accountType') accountType: string,
|
@Param('accountType') accountType: string,
|
||||||
|
@Query('regionCode') regionCode?: string,
|
||||||
@Query('page') page?: number,
|
@Query('page') page?: number,
|
||||||
@Query('pageSize') pageSize?: number,
|
@Query('pageSize') pageSize?: number,
|
||||||
) {
|
) {
|
||||||
return this.systemAccountsService.getSystemAccountMiningRecords(
|
return this.systemAccountsService.getSystemAccountMiningRecords(
|
||||||
accountType,
|
accountType,
|
||||||
|
regionCode || null,
|
||||||
page ?? 1,
|
page ?? 1,
|
||||||
pageSize ?? 20,
|
pageSize ?? 20,
|
||||||
);
|
);
|
||||||
|
|
@ -39,16 +42,19 @@ export class SystemAccountsController {
|
||||||
|
|
||||||
@Get(':accountType/transactions')
|
@Get(':accountType/transactions')
|
||||||
@ApiOperation({ summary: '获取系统账户交易记录' })
|
@ApiOperation({ summary: '获取系统账户交易记录' })
|
||||||
@ApiParam({ name: 'accountType', type: String, description: '系统账户类型' })
|
@ApiParam({ name: 'accountType', type: String, description: '系统账户类型(OPERATION/PROVINCE/CITY/HEADQUARTERS)' })
|
||||||
|
@ApiQuery({ name: 'regionCode', required: false, type: String, description: '区域代码(省/市代码)' })
|
||||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||||
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
||||||
async getSystemAccountTransactions(
|
async getSystemAccountTransactions(
|
||||||
@Param('accountType') accountType: string,
|
@Param('accountType') accountType: string,
|
||||||
|
@Query('regionCode') regionCode?: string,
|
||||||
@Query('page') page?: number,
|
@Query('page') page?: number,
|
||||||
@Query('pageSize') pageSize?: number,
|
@Query('pageSize') pageSize?: number,
|
||||||
) {
|
) {
|
||||||
return this.systemAccountsService.getSystemAccountTransactions(
|
return this.systemAccountsService.getSystemAccountTransactions(
|
||||||
accountType,
|
accountType,
|
||||||
|
regionCode || null,
|
||||||
page ?? 1,
|
page ?? 1,
|
||||||
pageSize ?? 20,
|
pageSize ?? 20,
|
||||||
);
|
);
|
||||||
|
|
@ -57,22 +63,25 @@ export class SystemAccountsController {
|
||||||
@Get(':accountType/contributions')
|
@Get(':accountType/contributions')
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: '获取系统账户算力来源明细',
|
summary: '获取系统账户算力来源明细',
|
||||||
description: '显示该账户的每笔算力来自哪个认种订单,支持按省市细分的账户类型(如 CITY_440100, PROVINCE_440000)',
|
description: '显示该账户的每笔算力来自哪个认种订单',
|
||||||
})
|
})
|
||||||
@ApiParam({
|
@ApiParam({
|
||||||
name: 'accountType',
|
name: 'accountType',
|
||||||
type: String,
|
type: String,
|
||||||
description: '系统账户类型(组合键),如 OPERATION, PROVINCE_440000, CITY_440100',
|
description: '系统账户类型(OPERATION/PROVINCE/CITY/HEADQUARTERS)',
|
||||||
})
|
})
|
||||||
|
@ApiQuery({ name: 'regionCode', required: false, type: String, description: '区域代码(省/市代码)' })
|
||||||
@ApiQuery({ name: 'page', required: false, type: Number, description: '页码,默认1' })
|
@ApiQuery({ name: 'page', required: false, type: Number, description: '页码,默认1' })
|
||||||
@ApiQuery({ name: 'pageSize', required: false, type: Number, description: '每页数量,默认20' })
|
@ApiQuery({ name: 'pageSize', required: false, type: Number, description: '每页数量,默认20' })
|
||||||
async getSystemAccountContributionRecords(
|
async getSystemAccountContributionRecords(
|
||||||
@Param('accountType') accountType: string,
|
@Param('accountType') accountType: string,
|
||||||
|
@Query('regionCode') regionCode?: string,
|
||||||
@Query('page') page?: number,
|
@Query('page') page?: number,
|
||||||
@Query('pageSize') pageSize?: number,
|
@Query('pageSize') pageSize?: number,
|
||||||
) {
|
) {
|
||||||
return this.systemAccountsService.getSystemAccountContributionRecords(
|
return this.systemAccountsService.getSystemAccountContributionRecords(
|
||||||
accountType,
|
accountType,
|
||||||
|
regionCode || null,
|
||||||
page ?? 1,
|
page ?? 1,
|
||||||
pageSize ?? 20,
|
pageSize ?? 20,
|
||||||
);
|
);
|
||||||
|
|
@ -86,11 +95,13 @@ export class SystemAccountsController {
|
||||||
@ApiParam({
|
@ApiParam({
|
||||||
name: 'accountType',
|
name: 'accountType',
|
||||||
type: String,
|
type: String,
|
||||||
description: '系统账户类型(组合键),如 OPERATION, PROVINCE_440000, CITY_440100',
|
description: '系统账户类型(OPERATION/PROVINCE/CITY/HEADQUARTERS)',
|
||||||
})
|
})
|
||||||
|
@ApiQuery({ name: 'regionCode', required: false, type: String, description: '区域代码(省/市代码)' })
|
||||||
async getSystemAccountContributionStats(
|
async getSystemAccountContributionStats(
|
||||||
@Param('accountType') accountType: string,
|
@Param('accountType') accountType: string,
|
||||||
|
@Query('regionCode') regionCode?: string,
|
||||||
) {
|
) {
|
||||||
return this.systemAccountsService.getSystemAccountContributionStats(accountType);
|
return this.systemAccountsService.getSystemAccountContributionStats(accountType, regionCode || null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ import { firstValueFrom } from 'rxjs';
|
||||||
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
|
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
|
||||||
|
|
||||||
interface MiningServiceSystemAccount {
|
interface MiningServiceSystemAccount {
|
||||||
accountType: string; // 组合键: OPERATION, PROVINCE_440000, CITY_440100 等
|
id: string;
|
||||||
baseType: string; // 基础类型: OPERATION / PROVINCE / CITY / HEADQUARTERS
|
accountType: string; // OPERATION / PROVINCE / CITY / HEADQUARTERS
|
||||||
regionCode: string | null; // 区域代码: 省/市代码,如 440000, 440100
|
regionCode: string | null; // 省/市代码,如 440000, 440100
|
||||||
name: string;
|
name: string;
|
||||||
totalMined: string;
|
totalMined: string;
|
||||||
availableBalance: string;
|
availableBalance: string;
|
||||||
|
|
@ -48,7 +48,11 @@ export class SystemAccountsService {
|
||||||
|
|
||||||
const miningDataMap = new Map<string, MiningServiceSystemAccount>();
|
const miningDataMap = new Map<string, MiningServiceSystemAccount>();
|
||||||
for (const account of response.data.accounts) {
|
for (const account of response.data.accounts) {
|
||||||
miningDataMap.set(account.accountType, account);
|
// 使用 accountType:regionCode 作为 key,与 contribution 表一致
|
||||||
|
const key = account.regionCode
|
||||||
|
? `${account.accountType}:${account.regionCode}`
|
||||||
|
: account.accountType;
|
||||||
|
miningDataMap.set(key, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
return miningDataMap;
|
return miningDataMap;
|
||||||
|
|
@ -82,12 +86,13 @@ export class SystemAccountsService {
|
||||||
// 从 mining-service 获取挖矿数据
|
// 从 mining-service 获取挖矿数据
|
||||||
const miningDataMap = await this.fetchMiningServiceSystemAccounts();
|
const miningDataMap = await this.fetchMiningServiceSystemAccounts();
|
||||||
|
|
||||||
// 构建算力数据映射 - 支持两种匹配方式
|
// 构建算力数据映射 - 使用 accountType:regionCode 格式
|
||||||
// 1. 直接用 accountType 匹配(如 OPERATION, HEADQUARTERS)
|
|
||||||
// 2. 用组合键匹配(如 CITY_440100, PROVINCE_440000)
|
|
||||||
const contributionMap = new Map<string, any>();
|
const contributionMap = new Map<string, any>();
|
||||||
for (const contrib of syncedContributions) {
|
for (const contrib of syncedContributions) {
|
||||||
contributionMap.set(contrib.accountType, contrib);
|
const key = contrib.regionCode
|
||||||
|
? `${contrib.accountType}:${contrib.regionCode}`
|
||||||
|
: contrib.accountType;
|
||||||
|
contributionMap.set(key, contrib);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建返回数据
|
// 构建返回数据
|
||||||
|
|
@ -96,19 +101,19 @@ export class SystemAccountsService {
|
||||||
let contrib = null;
|
let contrib = null;
|
||||||
let miningData = null;
|
let miningData = null;
|
||||||
|
|
||||||
// 1. 尝试用 regionCode 匹配(针对省市账户)
|
// 1. 尝试用 accountType:regionCode 匹配(针对省市账户)
|
||||||
if (account.code) {
|
if (account.code) {
|
||||||
// 从 code 提取区域代码(如 "CITY-440100" -> "440100")
|
// 从 code 提取区域代码(如 "CITY-440100" -> "440100")
|
||||||
const regionCode = this.extractRegionCodeFromCode(account.code);
|
const regionCode = this.extractRegionCodeFromCode(account.code);
|
||||||
if (regionCode) {
|
if (regionCode) {
|
||||||
// 构建组合键(如 CITY_440100)
|
// 使用 accountType:regionCode 格式(如 CITY:440100)
|
||||||
const accountTypeKey = `${account.accountType}_${regionCode}`;
|
const matchKey = `${account.accountType}:${regionCode}`;
|
||||||
contrib = contributionMap.get(accountTypeKey);
|
contrib = contributionMap.get(matchKey);
|
||||||
miningData = miningDataMap.get(accountTypeKey);
|
miningData = miningDataMap.get(matchKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 回退到直接 accountType 匹配(汇总账户)
|
// 2. 回退到直接 accountType 匹配(汇总账户,如 OPERATION, HEADQUARTERS)
|
||||||
if (!contrib) {
|
if (!contrib) {
|
||||||
contrib = contributionMap.get(account.accountType);
|
contrib = contributionMap.get(account.accountType);
|
||||||
}
|
}
|
||||||
|
|
@ -241,9 +246,14 @@ export class SystemAccountsService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取系统账户挖矿记录
|
* 获取系统账户挖矿记录
|
||||||
|
* @param accountType 系统账户类型(OPERATION/PROVINCE/CITY/HEADQUARTERS)
|
||||||
|
* @param regionCode 区域代码(省/市代码,如 440000, 440100)
|
||||||
|
* @param page 页码
|
||||||
|
* @param pageSize 每页数量
|
||||||
*/
|
*/
|
||||||
async getSystemAccountMiningRecords(
|
async getSystemAccountMiningRecords(
|
||||||
accountType: string,
|
accountType: string,
|
||||||
|
regionCode: string | null,
|
||||||
page: number = 1,
|
page: number = 1,
|
||||||
pageSize: number = 20,
|
pageSize: number = 20,
|
||||||
) {
|
) {
|
||||||
|
|
@ -253,12 +263,15 @@ export class SystemAccountsService {
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const params: Record<string, any> = { page, pageSize };
|
||||||
|
if (regionCode) {
|
||||||
|
params.regionCode = regionCode;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await firstValueFrom(
|
const response = await firstValueFrom(
|
||||||
this.httpService.get(
|
this.httpService.get(
|
||||||
`${miningServiceUrl}/admin/system-accounts/${accountType}/records`,
|
`${miningServiceUrl}/admin/system-accounts/${accountType}/records`,
|
||||||
{
|
{ params },
|
||||||
params: { page, pageSize },
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -267,15 +280,20 @@ export class SystemAccountsService {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`Failed to fetch system account mining records: ${error.message}`,
|
`Failed to fetch system account mining records: ${error.message}`,
|
||||||
);
|
);
|
||||||
return { records: [], total: 0, page, pageSize };
|
return { records: [], total: 0, page, pageSize, accountType, regionCode };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取系统账户交易记录
|
* 获取系统账户交易记录
|
||||||
|
* @param accountType 系统账户类型(OPERATION/PROVINCE/CITY/HEADQUARTERS)
|
||||||
|
* @param regionCode 区域代码(省/市代码,如 440000, 440100)
|
||||||
|
* @param page 页码
|
||||||
|
* @param pageSize 每页数量
|
||||||
*/
|
*/
|
||||||
async getSystemAccountTransactions(
|
async getSystemAccountTransactions(
|
||||||
accountType: string,
|
accountType: string,
|
||||||
|
regionCode: string | null,
|
||||||
page: number = 1,
|
page: number = 1,
|
||||||
pageSize: number = 20,
|
pageSize: number = 20,
|
||||||
) {
|
) {
|
||||||
|
|
@ -285,12 +303,15 @@ export class SystemAccountsService {
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const params: Record<string, any> = { page, pageSize };
|
||||||
|
if (regionCode) {
|
||||||
|
params.regionCode = regionCode;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await firstValueFrom(
|
const response = await firstValueFrom(
|
||||||
this.httpService.get(
|
this.httpService.get(
|
||||||
`${miningServiceUrl}/admin/system-accounts/${accountType}/transactions`,
|
`${miningServiceUrl}/admin/system-accounts/${accountType}/transactions`,
|
||||||
{
|
{ params },
|
||||||
params: { page, pageSize },
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -299,7 +320,7 @@ export class SystemAccountsService {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`Failed to fetch system account transactions: ${error.message}`,
|
`Failed to fetch system account transactions: ${error.message}`,
|
||||||
);
|
);
|
||||||
return { transactions: [], total: 0, page, pageSize };
|
return { transactions: [], total: 0, page, pageSize, accountType, regionCode };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -307,32 +328,38 @@ export class SystemAccountsService {
|
||||||
* 获取系统账户算力来源明细
|
* 获取系统账户算力来源明细
|
||||||
* 显示该账户的每笔算力来自哪个认种订单
|
* 显示该账户的每笔算力来自哪个认种订单
|
||||||
*
|
*
|
||||||
* @param accountType 系统账户类型(组合键),如 CITY_440100, PROVINCE_440000, OPERATION
|
* @param accountType 系统账户类型(OPERATION/PROVINCE/CITY/HEADQUARTERS)
|
||||||
|
* @param regionCode 区域代码(省/市代码,如 440000, 440100)
|
||||||
* @param page 页码
|
* @param page 页码
|
||||||
* @param pageSize 每页数量
|
* @param pageSize 每页数量
|
||||||
*/
|
*/
|
||||||
async getSystemAccountContributionRecords(
|
async getSystemAccountContributionRecords(
|
||||||
accountType: string,
|
accountType: string,
|
||||||
|
regionCode: string | null,
|
||||||
page: number = 1,
|
page: number = 1,
|
||||||
pageSize: number = 20,
|
pageSize: number = 20,
|
||||||
) {
|
) {
|
||||||
|
const whereClause = regionCode
|
||||||
|
? { accountType, regionCode }
|
||||||
|
: { accountType, regionCode: null };
|
||||||
|
|
||||||
const [records, total] = await Promise.all([
|
const [records, total] = await Promise.all([
|
||||||
this.prisma.syncedSystemContributionRecord.findMany({
|
this.prisma.syncedSystemContributionRecord.findMany({
|
||||||
where: { systemAccountType: accountType },
|
where: whereClause,
|
||||||
skip: (page - 1) * pageSize,
|
skip: (page - 1) * pageSize,
|
||||||
take: pageSize,
|
take: pageSize,
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
}),
|
}),
|
||||||
this.prisma.syncedSystemContributionRecord.count({
|
this.prisma.syncedSystemContributionRecord.count({
|
||||||
where: { systemAccountType: accountType },
|
where: whereClause,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
records: records.map((record) => ({
|
records: records.map((record) => ({
|
||||||
id: record.id,
|
|
||||||
originalRecordId: record.originalRecordId.toString(),
|
originalRecordId: record.originalRecordId.toString(),
|
||||||
systemAccountType: record.systemAccountType,
|
accountType: record.accountType,
|
||||||
|
regionCode: record.regionCode,
|
||||||
sourceAdoptionId: record.sourceAdoptionId.toString(),
|
sourceAdoptionId: record.sourceAdoptionId.toString(),
|
||||||
sourceAccountSequence: record.sourceAccountSequence,
|
sourceAccountSequence: record.sourceAccountSequence,
|
||||||
distributionRate: record.distributionRate.toString(),
|
distributionRate: record.distributionRate.toString(),
|
||||||
|
|
@ -354,15 +381,19 @@ export class SystemAccountsService {
|
||||||
* 获取系统账户算力明细统计
|
* 获取系统账户算力明细统计
|
||||||
* 用于显示算力来源的汇总信息
|
* 用于显示算力来源的汇总信息
|
||||||
*/
|
*/
|
||||||
async getSystemAccountContributionStats(accountType: string) {
|
async getSystemAccountContributionStats(accountType: string, regionCode: string | null) {
|
||||||
// 获取算力账户信息
|
// 获取算力账户信息
|
||||||
const contribution = await this.prisma.syncedSystemContribution.findUnique({
|
const contribution = await this.prisma.syncedSystemContribution.findUnique({
|
||||||
where: { accountType },
|
where: { accountType_regionCode: { accountType, regionCode } },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const whereClause = regionCode
|
||||||
|
? { accountType, regionCode }
|
||||||
|
: { accountType, regionCode: null };
|
||||||
|
|
||||||
// 获取明细记录统计
|
// 获取明细记录统计
|
||||||
const recordStats = await this.prisma.syncedSystemContributionRecord.aggregate({
|
const recordStats = await this.prisma.syncedSystemContributionRecord.aggregate({
|
||||||
where: { systemAccountType: accountType },
|
where: whereClause,
|
||||||
_count: true,
|
_count: true,
|
||||||
_sum: { amount: true },
|
_sum: { amount: true },
|
||||||
});
|
});
|
||||||
|
|
@ -370,20 +401,19 @@ export class SystemAccountsService {
|
||||||
// 获取来源认种订单数量(去重)
|
// 获取来源认种订单数量(去重)
|
||||||
const uniqueAdoptions = await this.prisma.syncedSystemContributionRecord.groupBy({
|
const uniqueAdoptions = await this.prisma.syncedSystemContributionRecord.groupBy({
|
||||||
by: ['sourceAdoptionId'],
|
by: ['sourceAdoptionId'],
|
||||||
where: { systemAccountType: accountType },
|
where: whereClause,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取来源用户数量(去重)
|
// 获取来源用户数量(去重)
|
||||||
const uniqueUsers = await this.prisma.syncedSystemContributionRecord.groupBy({
|
const uniqueUsers = await this.prisma.syncedSystemContributionRecord.groupBy({
|
||||||
by: ['sourceAccountSequence'],
|
by: ['sourceAccountSequence'],
|
||||||
where: { systemAccountType: accountType },
|
where: whereClause,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accountType,
|
accountType,
|
||||||
|
regionCode,
|
||||||
name: contribution?.name || accountType,
|
name: contribution?.name || accountType,
|
||||||
baseType: contribution?.baseType || this.extractBaseTypeFromAccountType(accountType),
|
|
||||||
regionCode: contribution?.regionCode || null,
|
|
||||||
totalContribution: contribution?.contributionBalance?.toString() || '0',
|
totalContribution: contribution?.contributionBalance?.toString() || '0',
|
||||||
recordCount: recordStats._count,
|
recordCount: recordStats._count,
|
||||||
sumFromRecords: recordStats._sum?.amount?.toString() || '0',
|
sumFromRecords: recordStats._sum?.amount?.toString() || '0',
|
||||||
|
|
@ -391,13 +421,4 @@ export class SystemAccountsService {
|
||||||
uniqueUserCount: uniqueUsers.length,
|
uniqueUserCount: uniqueUsers.length,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 从 accountType 提取 baseType
|
|
||||||
*/
|
|
||||||
private extractBaseTypeFromAccountType(accountType: string): string {
|
|
||||||
if (accountType.startsWith('PROVINCE_')) return 'PROVINCE';
|
|
||||||
if (accountType.startsWith('CITY_')) return 'CITY';
|
|
||||||
return accountType;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -559,44 +559,33 @@ export class CdcSyncService implements OnModuleInit {
|
||||||
/**
|
/**
|
||||||
* 处理 SystemAccountSynced 事件 - 同步系统账户算力
|
* 处理 SystemAccountSynced 事件 - 同步系统账户算力
|
||||||
* 来自 contribution-service 的系统账户(运营、省、市、总部)算力同步
|
* 来自 contribution-service 的系统账户(运营、省、市、总部)算力同步
|
||||||
* 支持组合键账户类型(如 PROVINCE_440000, CITY_440100)
|
* accountType: OPERATION / PROVINCE / CITY / HEADQUARTERS
|
||||||
|
* regionCode: 省/市代码,如 440000, 440100
|
||||||
*/
|
*/
|
||||||
private async handleSystemAccountSynced(event: ServiceEvent, tx: TransactionClient): Promise<void> {
|
private async handleSystemAccountSynced(event: ServiceEvent, tx: TransactionClient): Promise<void> {
|
||||||
const { payload } = event;
|
const { payload } = event;
|
||||||
// 从 accountType 提取 baseType(向后兼容)
|
const accountType = payload.accountType; // OPERATION / PROVINCE / CITY / HEADQUARTERS
|
||||||
const baseType = payload.baseType || this.extractBaseType(payload.accountType);
|
|
||||||
const regionCode = payload.regionCode || null;
|
const regionCode = payload.regionCode || null;
|
||||||
|
|
||||||
|
// 使用 accountType + regionCode 作为复合唯一键
|
||||||
await tx.syncedSystemContribution.upsert({
|
await tx.syncedSystemContribution.upsert({
|
||||||
where: { accountType: payload.accountType },
|
where: {
|
||||||
|
accountType_regionCode: { accountType, regionCode },
|
||||||
|
},
|
||||||
create: {
|
create: {
|
||||||
accountType: payload.accountType,
|
accountType,
|
||||||
baseType,
|
|
||||||
regionCode,
|
regionCode,
|
||||||
name: payload.name,
|
name: payload.name,
|
||||||
contributionBalance: payload.contributionBalance || 0,
|
contributionBalance: payload.contributionBalance || 0,
|
||||||
contributionNeverExpires: true, // 系统账户算力永不过期
|
contributionNeverExpires: true, // 系统账户算力永不过期
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
baseType,
|
|
||||||
regionCode,
|
|
||||||
name: payload.name,
|
name: payload.name,
|
||||||
contributionBalance: payload.contributionBalance,
|
contributionBalance: payload.contributionBalance,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 从 accountType 提取基础类型(向后兼容)
|
|
||||||
* 例如: PROVINCE_440000 -> PROVINCE, CITY_440100 -> CITY
|
|
||||||
*/
|
|
||||||
private extractBaseType(accountType: string): string {
|
|
||||||
if (accountType.startsWith('PROVINCE_')) return 'PROVINCE';
|
|
||||||
if (accountType.startsWith('CITY_')) return 'CITY';
|
|
||||||
// 如果没有下划线,则 accountType 本身就是基础类型
|
|
||||||
return accountType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理 SystemContributionRecordCreated 事件 - 同步系统账户算力明细
|
* 处理 SystemContributionRecordCreated 事件 - 同步系统账户算力明细
|
||||||
* 来自 contribution-service,记录每笔算力的来源信息
|
* 来自 contribution-service,记录每笔算力的来源信息
|
||||||
|
|
@ -604,11 +593,15 @@ export class CdcSyncService implements OnModuleInit {
|
||||||
private async handleSystemContributionRecordCreated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
|
private async handleSystemContributionRecordCreated(event: ServiceEvent, tx: TransactionClient): Promise<void> {
|
||||||
const { payload } = event;
|
const { payload } = event;
|
||||||
|
|
||||||
|
const accountType = payload.accountType;
|
||||||
|
const regionCode = payload.regionCode || null;
|
||||||
|
|
||||||
await tx.syncedSystemContributionRecord.upsert({
|
await tx.syncedSystemContributionRecord.upsert({
|
||||||
where: { originalRecordId: BigInt(payload.recordId) },
|
where: { originalRecordId: BigInt(payload.recordId) },
|
||||||
create: {
|
create: {
|
||||||
originalRecordId: BigInt(payload.recordId),
|
originalRecordId: BigInt(payload.recordId),
|
||||||
systemAccountType: payload.systemAccountType,
|
accountType,
|
||||||
|
regionCode,
|
||||||
sourceAdoptionId: BigInt(payload.sourceAdoptionId),
|
sourceAdoptionId: BigInt(payload.sourceAdoptionId),
|
||||||
sourceAccountSequence: payload.sourceAccountSequence,
|
sourceAccountSequence: payload.sourceAccountSequence,
|
||||||
distributionRate: payload.distributionRate,
|
distributionRate: payload.distributionRate,
|
||||||
|
|
@ -619,7 +612,8 @@ export class CdcSyncService implements OnModuleInit {
|
||||||
createdAt: new Date(payload.createdAt),
|
createdAt: new Date(payload.createdAt),
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
systemAccountType: payload.systemAccountType,
|
accountType,
|
||||||
|
regionCode,
|
||||||
sourceAdoptionId: BigInt(payload.sourceAdoptionId),
|
sourceAdoptionId: BigInt(payload.sourceAdoptionId),
|
||||||
sourceAccountSequence: payload.sourceAccountSequence,
|
sourceAccountSequence: payload.sourceAccountSequence,
|
||||||
distributionRate: payload.distributionRate,
|
distributionRate: payload.distributionRate,
|
||||||
|
|
@ -630,7 +624,7 @@ export class CdcSyncService implements OnModuleInit {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`Synced system contribution record: recordId=${payload.recordId}, account=${payload.systemAccountType}, amount=${payload.amount}`,
|
`Synced system contribution record: recordId=${payload.recordId}, account=${accountType}:${regionCode}, amount=${payload.amount}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,11 +99,10 @@ CREATE TABLE "mining_transactions" (
|
||||||
CONSTRAINT "mining_transactions_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "mining_transactions_pkey" PRIMARY KEY ("id")
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable: 系统挖矿账户 (直接使用 TEXT 组合键)
|
-- CreateTable: 系统挖矿账户
|
||||||
CREATE TABLE "system_mining_accounts" (
|
CREATE TABLE "system_mining_accounts" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"account_type" TEXT NOT NULL,
|
"account_type" TEXT NOT NULL,
|
||||||
"base_type" TEXT NOT NULL,
|
|
||||||
"region_code" TEXT,
|
"region_code" TEXT,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
"totalMined" DECIMAL(30, 8) NOT NULL DEFAULT 0,
|
"totalMined" DECIMAL(30, 8) NOT NULL DEFAULT 0,
|
||||||
|
|
@ -119,7 +118,7 @@ CREATE TABLE "system_mining_accounts" (
|
||||||
-- CreateTable: 系统账户挖矿记录
|
-- CreateTable: 系统账户挖矿记录
|
||||||
CREATE TABLE "system_mining_records" (
|
CREATE TABLE "system_mining_records" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"account_type" TEXT NOT NULL,
|
"system_account_id" TEXT NOT NULL,
|
||||||
"mining_minute" TIMESTAMP(3) NOT NULL,
|
"mining_minute" TIMESTAMP(3) NOT NULL,
|
||||||
"contribution_ratio" DECIMAL(30, 18) NOT NULL,
|
"contribution_ratio" DECIMAL(30, 18) NOT NULL,
|
||||||
"total_contribution" DECIMAL(30, 8) NOT NULL,
|
"total_contribution" DECIMAL(30, 8) NOT NULL,
|
||||||
|
|
@ -133,7 +132,7 @@ CREATE TABLE "system_mining_records" (
|
||||||
-- CreateTable: 系统账户交易流水
|
-- CreateTable: 系统账户交易流水
|
||||||
CREATE TABLE "system_mining_transactions" (
|
CREATE TABLE "system_mining_transactions" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"account_type" TEXT NOT NULL,
|
"system_account_id" TEXT NOT NULL,
|
||||||
"type" TEXT NOT NULL,
|
"type" TEXT NOT NULL,
|
||||||
"amount" DECIMAL(30, 8) NOT NULL,
|
"amount" DECIMAL(30, 8) NOT NULL,
|
||||||
"balance_before" DECIMAL(30, 8) NOT NULL,
|
"balance_before" DECIMAL(30, 8) NOT NULL,
|
||||||
|
|
@ -407,17 +406,17 @@ CREATE INDEX "mining_transactions_counterparty_account_seq_idx" ON "mining_trans
|
||||||
CREATE INDEX "mining_transactions_counterparty_user_id_idx" ON "mining_transactions"("counterparty_user_id");
|
CREATE INDEX "mining_transactions_counterparty_user_id_idx" ON "mining_transactions"("counterparty_user_id");
|
||||||
|
|
||||||
-- CreateIndex: system_mining_accounts
|
-- CreateIndex: system_mining_accounts
|
||||||
CREATE UNIQUE INDEX "system_mining_accounts_account_type_key" ON "system_mining_accounts"("account_type");
|
CREATE UNIQUE INDEX "system_mining_accounts_account_type_region_code_key" ON "system_mining_accounts"("account_type", "region_code");
|
||||||
CREATE INDEX "system_mining_accounts_totalContribution_idx" ON "system_mining_accounts"("totalContribution" DESC);
|
CREATE INDEX "system_mining_accounts_totalContribution_idx" ON "system_mining_accounts"("totalContribution" DESC);
|
||||||
CREATE INDEX "system_mining_accounts_base_type_idx" ON "system_mining_accounts"("base_type");
|
CREATE INDEX "system_mining_accounts_account_type_idx" ON "system_mining_accounts"("account_type");
|
||||||
CREATE INDEX "system_mining_accounts_region_code_idx" ON "system_mining_accounts"("region_code");
|
CREATE INDEX "system_mining_accounts_region_code_idx" ON "system_mining_accounts"("region_code");
|
||||||
|
|
||||||
-- CreateIndex: system_mining_records
|
-- CreateIndex: system_mining_records
|
||||||
CREATE UNIQUE INDEX "system_mining_records_account_type_mining_minute_key" ON "system_mining_records"("account_type", "mining_minute");
|
CREATE UNIQUE INDEX "system_mining_records_system_account_id_mining_minute_key" ON "system_mining_records"("system_account_id", "mining_minute");
|
||||||
CREATE INDEX "system_mining_records_mining_minute_idx" ON "system_mining_records"("mining_minute");
|
CREATE INDEX "system_mining_records_mining_minute_idx" ON "system_mining_records"("mining_minute");
|
||||||
|
|
||||||
-- CreateIndex: system_mining_transactions
|
-- CreateIndex: system_mining_transactions
|
||||||
CREATE INDEX "system_mining_transactions_account_type_created_at_idx" ON "system_mining_transactions"("account_type", "created_at" DESC);
|
CREATE INDEX "system_mining_transactions_system_account_id_created_at_idx" ON "system_mining_transactions"("system_account_id", "created_at" DESC);
|
||||||
|
|
||||||
-- CreateIndex: pending_contribution_mining
|
-- CreateIndex: pending_contribution_mining
|
||||||
CREATE UNIQUE INDEX "pending_contribution_mining_source_adoption_id_would_be_acco_key"
|
CREATE UNIQUE INDEX "pending_contribution_mining_source_adoption_id_would_be_acco_key"
|
||||||
|
|
@ -533,12 +532,12 @@ ALTER TABLE "mining_records" ADD CONSTRAINT "mining_records_accountSequence_fkey
|
||||||
ALTER TABLE "mining_transactions" ADD CONSTRAINT "mining_transactions_accountSequence_fkey" FOREIGN KEY ("accountSequence") REFERENCES "mining_accounts"("accountSequence") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "mining_transactions" ADD CONSTRAINT "mining_transactions_accountSequence_fkey" FOREIGN KEY ("accountSequence") REFERENCES "mining_accounts"("accountSequence") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey: system_mining_records
|
-- AddForeignKey: system_mining_records
|
||||||
ALTER TABLE "system_mining_records" ADD CONSTRAINT "system_mining_records_account_type_fkey"
|
ALTER TABLE "system_mining_records" ADD CONSTRAINT "system_mining_records_system_account_id_fkey"
|
||||||
FOREIGN KEY ("account_type") REFERENCES "system_mining_accounts"("account_type") ON DELETE RESTRICT ON UPDATE CASCADE;
|
FOREIGN KEY ("system_account_id") REFERENCES "system_mining_accounts"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey: system_mining_transactions
|
-- AddForeignKey: system_mining_transactions
|
||||||
ALTER TABLE "system_mining_transactions" ADD CONSTRAINT "system_mining_transactions_account_type_fkey"
|
ALTER TABLE "system_mining_transactions" ADD CONSTRAINT "system_mining_transactions_system_account_id_fkey"
|
||||||
FOREIGN KEY ("account_type") REFERENCES "system_mining_accounts"("account_type") ON DELETE RESTRICT ON UPDATE CASCADE;
|
FOREIGN KEY ("system_account_id") REFERENCES "system_mining_accounts"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey: pending_mining_records
|
-- AddForeignKey: pending_mining_records
|
||||||
ALTER TABLE "pending_mining_records" ADD CONSTRAINT "pending_mining_records_pending_contribution_id_fkey"
|
ALTER TABLE "pending_mining_records" ADD CONSTRAINT "pending_mining_records_pending_contribution_id_fkey"
|
||||||
|
|
@ -550,11 +549,9 @@ ALTER TABLE "burn_records" ADD CONSTRAINT "burn_records_blackHoleId_fkey" FOREIG
|
||||||
-- AddForeignKey
|
-- 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;
|
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;
|
||||||
|
|
||||||
-- 初始化系统账户
|
-- 初始化系统账户 (无 regionCode 的汇总账户)
|
||||||
INSERT INTO "system_mining_accounts" ("id", "account_type", "base_type", "name", "totalMined", "availableBalance", "totalContribution", "updated_at")
|
INSERT INTO "system_mining_accounts" ("id", "account_type", "region_code", "name", "totalMined", "availableBalance", "totalContribution", "updated_at")
|
||||||
VALUES
|
VALUES
|
||||||
(gen_random_uuid(), 'OPERATION', 'OPERATION', '运营账户', 0, 0, 0, NOW()),
|
(gen_random_uuid(), 'OPERATION', NULL, '运营账户', 0, 0, 0, NOW()),
|
||||||
(gen_random_uuid(), 'PROVINCE', 'PROVINCE', '省公司账户', 0, 0, 0, NOW()),
|
(gen_random_uuid(), 'HEADQUARTERS', NULL, '总部账户', 0, 0, 0, NOW())
|
||||||
(gen_random_uuid(), 'CITY', 'CITY', '市公司账户', 0, 0, 0, NOW()),
|
ON CONFLICT ("account_type", "region_code") DO NOTHING;
|
||||||
(gen_random_uuid(), 'HEADQUARTERS', 'HEADQUARTERS', '总部账户', 0, 0, 0, NOW())
|
|
||||||
ON CONFLICT ("account_type") DO NOTHING;
|
|
||||||
|
|
|
||||||
|
|
@ -53,13 +53,10 @@ model MiningEra {
|
||||||
// ==================== 系统账户(运营/省/市/总部)====================
|
// ==================== 系统账户(运营/省/市/总部)====================
|
||||||
|
|
||||||
// 系统挖矿账户
|
// 系统挖矿账户
|
||||||
// accountType 格式: OPERATION, PROVINCE, CITY, HEADQUARTERS (汇总账户)
|
|
||||||
// PROVINCE_440000, CITY_440100 等 (按省市细分的账户)
|
|
||||||
model SystemMiningAccount {
|
model SystemMiningAccount {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
accountType String @unique @map("account_type") // 组合键
|
accountType String @map("account_type") // OPERATION/PROVINCE/CITY/HEADQUARTERS
|
||||||
baseType String @map("base_type") // 基础类型: OPERATION/PROVINCE/CITY/HEADQUARTERS
|
regionCode String? @map("region_code") // 省市代码,如 440000、440100
|
||||||
regionCode String? @map("region_code") // 区域代码
|
|
||||||
name String
|
name String
|
||||||
totalMined Decimal @default(0) @db.Decimal(30, 8) // 总挖到的积分股
|
totalMined Decimal @default(0) @db.Decimal(30, 8) // 总挖到的积分股
|
||||||
availableBalance Decimal @default(0) @db.Decimal(30, 8) // 可用余额
|
availableBalance Decimal @default(0) @db.Decimal(30, 8) // 可用余额
|
||||||
|
|
@ -71,7 +68,8 @@ model SystemMiningAccount {
|
||||||
records SystemMiningRecord[]
|
records SystemMiningRecord[]
|
||||||
transactions SystemMiningTransaction[]
|
transactions SystemMiningTransaction[]
|
||||||
|
|
||||||
@@index([baseType])
|
@@unique([accountType, regionCode])
|
||||||
|
@@index([accountType])
|
||||||
@@index([regionCode])
|
@@index([regionCode])
|
||||||
@@index([totalContribution(sort: Desc)])
|
@@index([totalContribution(sort: Desc)])
|
||||||
@@map("system_mining_accounts")
|
@@map("system_mining_accounts")
|
||||||
|
|
@ -80,7 +78,7 @@ model SystemMiningAccount {
|
||||||
// 系统账户挖矿记录(分钟级别汇总)
|
// 系统账户挖矿记录(分钟级别汇总)
|
||||||
model SystemMiningRecord {
|
model SystemMiningRecord {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
accountType String @map("account_type") // 组合键
|
systemAccountId String @map("system_account_id")
|
||||||
miningMinute DateTime @map("mining_minute")
|
miningMinute DateTime @map("mining_minute")
|
||||||
contributionRatio Decimal @db.Decimal(30, 18) @map("contribution_ratio")
|
contributionRatio Decimal @db.Decimal(30, 18) @map("contribution_ratio")
|
||||||
totalContribution Decimal @db.Decimal(30, 8) @map("total_contribution")
|
totalContribution Decimal @db.Decimal(30, 8) @map("total_contribution")
|
||||||
|
|
@ -88,29 +86,29 @@ model SystemMiningRecord {
|
||||||
minedAmount Decimal @db.Decimal(30, 18) @map("mined_amount")
|
minedAmount Decimal @db.Decimal(30, 18) @map("mined_amount")
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
|
|
||||||
account SystemMiningAccount @relation(fields: [accountType], references: [accountType])
|
account SystemMiningAccount @relation(fields: [systemAccountId], references: [id])
|
||||||
|
|
||||||
@@unique([accountType, miningMinute])
|
@@unique([systemAccountId, miningMinute])
|
||||||
@@index([miningMinute])
|
@@index([miningMinute])
|
||||||
@@map("system_mining_records")
|
@@map("system_mining_records")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 系统账户交易流水
|
// 系统账户交易流水
|
||||||
model SystemMiningTransaction {
|
model SystemMiningTransaction {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
accountType String @map("account_type") // 组合键
|
systemAccountId String @map("system_account_id")
|
||||||
type String // MINE, TRANSFER_OUT, ADJUSTMENT
|
type String // MINE, TRANSFER_OUT, ADJUSTMENT
|
||||||
amount Decimal @db.Decimal(30, 8)
|
amount Decimal @db.Decimal(30, 8)
|
||||||
balanceBefore Decimal @db.Decimal(30, 8) @map("balance_before")
|
balanceBefore Decimal @db.Decimal(30, 8) @map("balance_before")
|
||||||
balanceAfter Decimal @db.Decimal(30, 8) @map("balance_after")
|
balanceAfter Decimal @db.Decimal(30, 8) @map("balance_after")
|
||||||
referenceId String? @map("reference_id")
|
referenceId String? @map("reference_id")
|
||||||
referenceType String? @map("reference_type")
|
referenceType String? @map("reference_type")
|
||||||
memo String? @db.Text
|
memo String? @db.Text
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
|
|
||||||
account SystemMiningAccount @relation(fields: [accountType], references: [accountType])
|
account SystemMiningAccount @relation(fields: [systemAccountId], references: [id])
|
||||||
|
|
||||||
@@index([accountType, createdAt(sort: Desc)])
|
@@index([systemAccountId, createdAt(sort: Desc)])
|
||||||
@@map("system_mining_transactions")
|
@@map("system_mining_transactions")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -163,14 +163,14 @@ export class AdminController {
|
||||||
@ApiOperation({ summary: '获取系统账户挖矿状态' })
|
@ApiOperation({ summary: '获取系统账户挖矿状态' })
|
||||||
async getSystemAccounts() {
|
async getSystemAccounts() {
|
||||||
const accounts = await this.prisma.systemMiningAccount.findMany({
|
const accounts = await this.prisma.systemMiningAccount.findMany({
|
||||||
orderBy: { accountType: 'asc' },
|
orderBy: [{ accountType: 'asc' }, { regionCode: 'asc' }],
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts: accounts.map((acc) => ({
|
accounts: accounts.map((acc) => ({
|
||||||
|
id: acc.id,
|
||||||
accountType: acc.accountType,
|
accountType: acc.accountType,
|
||||||
baseType: acc.baseType, // 新增:基础类型
|
regionCode: acc.regionCode,
|
||||||
regionCode: acc.regionCode, // 新增:区域代码
|
|
||||||
name: acc.name,
|
name: acc.name,
|
||||||
totalMined: acc.totalMined.toString(),
|
totalMined: acc.totalMined.toString(),
|
||||||
availableBalance: acc.availableBalance.toString(),
|
availableBalance: acc.availableBalance.toString(),
|
||||||
|
|
@ -184,35 +184,58 @@ export class AdminController {
|
||||||
@Get('system-accounts/:accountType/records')
|
@Get('system-accounts/:accountType/records')
|
||||||
@Public()
|
@Public()
|
||||||
@ApiOperation({ summary: '获取系统账户挖矿记录' })
|
@ApiOperation({ summary: '获取系统账户挖矿记录' })
|
||||||
@ApiParam({ name: 'accountType', type: String, description: '系统账户类型 (OPERATION, PROVINCE_440000, CITY_440100, HEADQUARTERS 等)' })
|
@ApiParam({ name: 'accountType', type: String, description: '系统账户类型(OPERATION/PROVINCE/CITY/HEADQUARTERS)' })
|
||||||
|
@ApiQuery({ name: 'regionCode', required: false, type: String, description: '区域代码(省/市代码)' })
|
||||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||||
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
||||||
async getSystemAccountMiningRecords(
|
async getSystemAccountMiningRecords(
|
||||||
@Param('accountType') accountType: string,
|
@Param('accountType') accountType: string,
|
||||||
|
@Query('regionCode') regionCode?: string,
|
||||||
@Query('page') page?: number,
|
@Query('page') page?: number,
|
||||||
@Query('pageSize') pageSize?: number,
|
@Query('pageSize') pageSize?: number,
|
||||||
) {
|
) {
|
||||||
const pageNum = page ?? 1;
|
const pageNum = page ?? 1;
|
||||||
const pageSizeNum = pageSize ?? 20;
|
const pageSizeNum = pageSize ?? 20;
|
||||||
const skip = (pageNum - 1) * pageSizeNum;
|
const skip = (pageNum - 1) * pageSizeNum;
|
||||||
// accountType 现在是字符串类型,支持组合键(如 PROVINCE_440000, CITY_440100)
|
|
||||||
|
// 先通过 accountType + regionCode 查找系统账户
|
||||||
|
const account = await this.prisma.systemMiningAccount.findUnique({
|
||||||
|
where: {
|
||||||
|
accountType_regionCode: {
|
||||||
|
accountType,
|
||||||
|
regionCode: regionCode || null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
return {
|
||||||
|
records: [],
|
||||||
|
total: 0,
|
||||||
|
page: pageNum,
|
||||||
|
pageSize: pageSizeNum,
|
||||||
|
accountType,
|
||||||
|
regionCode: regionCode || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const [records, total] = await Promise.all([
|
const [records, total] = await Promise.all([
|
||||||
this.prisma.systemMiningRecord.findMany({
|
this.prisma.systemMiningRecord.findMany({
|
||||||
where: { accountType },
|
where: { systemAccountId: account.id },
|
||||||
orderBy: { miningMinute: 'desc' },
|
orderBy: { miningMinute: 'desc' },
|
||||||
skip,
|
skip,
|
||||||
take: pageSizeNum,
|
take: pageSizeNum,
|
||||||
}),
|
}),
|
||||||
this.prisma.systemMiningRecord.count({
|
this.prisma.systemMiningRecord.count({
|
||||||
where: { accountType },
|
where: { systemAccountId: account.id },
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
records: records.map((record) => ({
|
records: records.map((record) => ({
|
||||||
id: record.id,
|
id: record.id,
|
||||||
accountType: record.accountType,
|
accountType,
|
||||||
|
regionCode: regionCode || null,
|
||||||
miningMinute: record.miningMinute,
|
miningMinute: record.miningMinute,
|
||||||
contributionRatio: record.contributionRatio.toString(),
|
contributionRatio: record.contributionRatio.toString(),
|
||||||
totalContribution: record.totalContribution.toString(),
|
totalContribution: record.totalContribution.toString(),
|
||||||
|
|
@ -223,41 +246,66 @@ export class AdminController {
|
||||||
total,
|
total,
|
||||||
page: pageNum,
|
page: pageNum,
|
||||||
pageSize: pageSizeNum,
|
pageSize: pageSizeNum,
|
||||||
|
accountType,
|
||||||
|
regionCode: regionCode || null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('system-accounts/:accountType/transactions')
|
@Get('system-accounts/:accountType/transactions')
|
||||||
@Public()
|
@Public()
|
||||||
@ApiOperation({ summary: '获取系统账户交易记录' })
|
@ApiOperation({ summary: '获取系统账户交易记录' })
|
||||||
@ApiParam({ name: 'accountType', type: String, description: '系统账户类型 (OPERATION, PROVINCE_440000, CITY_440100, HEADQUARTERS 等)' })
|
@ApiParam({ name: 'accountType', type: String, description: '系统账户类型(OPERATION/PROVINCE/CITY/HEADQUARTERS)' })
|
||||||
|
@ApiQuery({ name: 'regionCode', required: false, type: String, description: '区域代码(省/市代码)' })
|
||||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||||
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
||||||
async getSystemAccountTransactions(
|
async getSystemAccountTransactions(
|
||||||
@Param('accountType') accountType: string,
|
@Param('accountType') accountType: string,
|
||||||
|
@Query('regionCode') regionCode?: string,
|
||||||
@Query('page') page?: number,
|
@Query('page') page?: number,
|
||||||
@Query('pageSize') pageSize?: number,
|
@Query('pageSize') pageSize?: number,
|
||||||
) {
|
) {
|
||||||
const pageNum = page ?? 1;
|
const pageNum = page ?? 1;
|
||||||
const pageSizeNum = pageSize ?? 20;
|
const pageSizeNum = pageSize ?? 20;
|
||||||
const skip = (pageNum - 1) * pageSizeNum;
|
const skip = (pageNum - 1) * pageSizeNum;
|
||||||
// accountType 现在是字符串类型,支持组合键(如 PROVINCE_440000, CITY_440100)
|
|
||||||
|
// 先通过 accountType + regionCode 查找系统账户
|
||||||
|
const account = await this.prisma.systemMiningAccount.findUnique({
|
||||||
|
where: {
|
||||||
|
accountType_regionCode: {
|
||||||
|
accountType,
|
||||||
|
regionCode: regionCode || null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
return {
|
||||||
|
transactions: [],
|
||||||
|
total: 0,
|
||||||
|
page: pageNum,
|
||||||
|
pageSize: pageSizeNum,
|
||||||
|
accountType,
|
||||||
|
regionCode: regionCode || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const [transactions, total] = await Promise.all([
|
const [transactions, total] = await Promise.all([
|
||||||
this.prisma.systemMiningTransaction.findMany({
|
this.prisma.systemMiningTransaction.findMany({
|
||||||
where: { accountType },
|
where: { systemAccountId: account.id },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
skip,
|
skip,
|
||||||
take: pageSizeNum,
|
take: pageSizeNum,
|
||||||
}),
|
}),
|
||||||
this.prisma.systemMiningTransaction.count({
|
this.prisma.systemMiningTransaction.count({
|
||||||
where: { accountType },
|
where: { systemAccountId: account.id },
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
transactions: transactions.map((tx) => ({
|
transactions: transactions.map((tx) => ({
|
||||||
id: tx.id,
|
id: tx.id,
|
||||||
accountType: tx.accountType,
|
accountType,
|
||||||
|
regionCode: regionCode || null,
|
||||||
type: tx.type,
|
type: tx.type,
|
||||||
amount: tx.amount.toString(),
|
amount: tx.amount.toString(),
|
||||||
balanceBefore: tx.balanceBefore.toString(),
|
balanceBefore: tx.balanceBefore.toString(),
|
||||||
|
|
@ -270,6 +318,8 @@ export class AdminController {
|
||||||
total,
|
total,
|
||||||
page: pageNum,
|
page: pageNum,
|
||||||
pageSize: pageSizeNum,
|
pageSize: pageSizeNum,
|
||||||
|
accountType,
|
||||||
|
regionCode: regionCode || null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,12 +89,11 @@ export class ContributionEventHandler implements OnModuleInit {
|
||||||
});
|
});
|
||||||
} else if (eventType === 'SystemAccountSynced') {
|
} else if (eventType === 'SystemAccountSynced') {
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`Received SystemAccountSynced for ${eventPayload.accountType} (baseType=${eventPayload.baseType}, regionCode=${eventPayload.regionCode})`,
|
`Received SystemAccountSynced for ${eventPayload.accountType} (regionCode=${eventPayload.regionCode})`,
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.networkSyncService.handleSystemAccountSynced({
|
await this.networkSyncService.handleSystemAccountSynced({
|
||||||
accountType: eventPayload.accountType,
|
accountType: eventPayload.accountType,
|
||||||
baseType: eventPayload.baseType || eventPayload.accountType, // 向后兼容
|
|
||||||
regionCode: eventPayload.regionCode || null,
|
regionCode: eventPayload.regionCode || null,
|
||||||
name: eventPayload.name,
|
name: eventPayload.name,
|
||||||
contributionBalance: eventPayload.contributionBalance,
|
contributionBalance: eventPayload.contributionBalance,
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ import { MiningCalculatorService } from '../../domain/services/mining-calculator
|
||||||
import { ShareAmount } from '../../domain/value-objects/share-amount.vo';
|
import { ShareAmount } from '../../domain/value-objects/share-amount.vo';
|
||||||
import Decimal from 'decimal.js';
|
import Decimal from 'decimal.js';
|
||||||
|
|
||||||
// 系统账户基础类型常量(替代 Prisma 枚举)
|
// 系统账户类型常量
|
||||||
const HEADQUARTERS_BASE_TYPE = 'HEADQUARTERS';
|
const HEADQUARTERS_ACCOUNT_TYPE = 'HEADQUARTERS';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 挖矿分配服务
|
* 挖矿分配服务
|
||||||
|
|
@ -188,6 +188,7 @@ export class MiningDistributionService {
|
||||||
for (const data of result.systemRedisData) {
|
for (const data of result.systemRedisData) {
|
||||||
await this.accumulateSystemMinuteData(
|
await this.accumulateSystemMinuteData(
|
||||||
data.accountType,
|
data.accountType,
|
||||||
|
data.regionCode,
|
||||||
currentMinute,
|
currentMinute,
|
||||||
data.reward,
|
data.reward,
|
||||||
data.contribution,
|
data.contribution,
|
||||||
|
|
@ -207,7 +208,8 @@ export class MiningDistributionService {
|
||||||
new ShareAmount(0),
|
new ShareAmount(0),
|
||||||
);
|
);
|
||||||
await this.accumulateSystemMinuteData(
|
await this.accumulateSystemMinuteData(
|
||||||
HEADQUARTERS_BASE_TYPE,
|
HEADQUARTERS_ACCOUNT_TYPE,
|
||||||
|
null, // HEADQUARTERS 没有 regionCode
|
||||||
currentMinute,
|
currentMinute,
|
||||||
headquartersTotalReward,
|
headquartersTotalReward,
|
||||||
headquartersTotalContribution,
|
headquartersTotalContribution,
|
||||||
|
|
@ -351,7 +353,8 @@ export class MiningDistributionService {
|
||||||
|
|
||||||
// 计算所有系统账户的挖矿奖励
|
// 计算所有系统账户的挖矿奖励
|
||||||
const systemRewards: Array<{
|
const systemRewards: Array<{
|
||||||
accountType: string; // 改为字符串支持组合键
|
accountType: string;
|
||||||
|
regionCode: string | null;
|
||||||
reward: ShareAmount;
|
reward: ShareAmount;
|
||||||
contribution: ShareAmount;
|
contribution: ShareAmount;
|
||||||
memo: string;
|
memo: string;
|
||||||
|
|
@ -359,7 +362,7 @@ export class MiningDistributionService {
|
||||||
|
|
||||||
for (const systemAccount of systemAccounts) {
|
for (const systemAccount of systemAccounts) {
|
||||||
// 总部账户不直接参与挖矿,它只接收未解锁算力的收益
|
// 总部账户不直接参与挖矿,它只接收未解锁算力的收益
|
||||||
if (systemAccount.baseType === HEADQUARTERS_BASE_TYPE) {
|
if (systemAccount.accountType === HEADQUARTERS_ACCOUNT_TYPE) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -376,6 +379,7 @@ export class MiningDistributionService {
|
||||||
if (!reward.isZero()) {
|
if (!reward.isZero()) {
|
||||||
systemRewards.push({
|
systemRewards.push({
|
||||||
accountType: systemAccount.accountType,
|
accountType: systemAccount.accountType,
|
||||||
|
regionCode: systemAccount.regionCode,
|
||||||
reward,
|
reward,
|
||||||
contribution: systemAccount.totalContribution,
|
contribution: systemAccount.totalContribution,
|
||||||
memo: `秒挖矿 ${currentSecond.getTime()}`,
|
memo: `秒挖矿 ${currentSecond.getTime()}`,
|
||||||
|
|
@ -417,8 +421,8 @@ export class MiningDistributionService {
|
||||||
// 在单个事务中执行所有系统账户和待解锁算力的挖矿
|
// 在单个事务中执行所有系统账户和待解锁算力的挖矿
|
||||||
await this.prisma.$transaction(async (tx) => {
|
await this.prisma.$transaction(async (tx) => {
|
||||||
// 处理系统账户挖矿
|
// 处理系统账户挖矿
|
||||||
for (const { accountType, reward, memo } of systemRewards) {
|
for (const { accountType, regionCode, reward, memo } of systemRewards) {
|
||||||
await this.systemMiningAccountRepository.mine(accountType, reward, memo, tx);
|
await this.systemMiningAccountRepository.mine(accountType as any, regionCode, reward, memo, tx);
|
||||||
systemDistributed = systemDistributed.add(reward);
|
systemDistributed = systemDistributed.add(reward);
|
||||||
systemParticipantCount++;
|
systemParticipantCount++;
|
||||||
}
|
}
|
||||||
|
|
@ -438,7 +442,8 @@ export class MiningDistributionService {
|
||||||
// 一次性更新总部账户(而不是每个待解锁算力单独更新)
|
// 一次性更新总部账户(而不是每个待解锁算力单独更新)
|
||||||
if (!headquartersTotal.isZero()) {
|
if (!headquartersTotal.isZero()) {
|
||||||
await this.systemMiningAccountRepository.mine(
|
await this.systemMiningAccountRepository.mine(
|
||||||
HEADQUARTERS_BASE_TYPE,
|
HEADQUARTERS_ACCOUNT_TYPE as any,
|
||||||
|
null, // HEADQUARTERS 没有 regionCode
|
||||||
headquartersTotal,
|
headquartersTotal,
|
||||||
`秒挖矿 ${currentSecond.getTime()} - 待解锁算力汇总 (${pendingRewards.length}笔)`,
|
`秒挖矿 ${currentSecond.getTime()} - 待解锁算力汇总 (${pendingRewards.length}笔)`,
|
||||||
tx,
|
tx,
|
||||||
|
|
@ -447,9 +452,10 @@ export class MiningDistributionService {
|
||||||
});
|
});
|
||||||
|
|
||||||
// 事务成功后,累积 Redis 数据(Redis 操作不需要在事务内)
|
// 事务成功后,累积 Redis 数据(Redis 操作不需要在事务内)
|
||||||
for (const { accountType, reward, contribution } of systemRewards) {
|
for (const { accountType, regionCode, reward, contribution } of systemRewards) {
|
||||||
await this.accumulateSystemMinuteData(
|
await this.accumulateSystemMinuteData(
|
||||||
accountType,
|
accountType,
|
||||||
|
regionCode,
|
||||||
currentMinute,
|
currentMinute,
|
||||||
reward,
|
reward,
|
||||||
contribution,
|
contribution,
|
||||||
|
|
@ -469,7 +475,8 @@ export class MiningDistributionService {
|
||||||
new ShareAmount(0),
|
new ShareAmount(0),
|
||||||
);
|
);
|
||||||
await this.accumulateSystemMinuteData(
|
await this.accumulateSystemMinuteData(
|
||||||
HEADQUARTERS_BASE_TYPE,
|
HEADQUARTERS_ACCOUNT_TYPE,
|
||||||
|
null, // HEADQUARTERS 没有 regionCode
|
||||||
currentMinute,
|
currentMinute,
|
||||||
headquartersTotalReward,
|
headquartersTotalReward,
|
||||||
headquartersTotalContribution,
|
headquartersTotalContribution,
|
||||||
|
|
@ -508,7 +515,8 @@ export class MiningDistributionService {
|
||||||
systemParticipantCount: number;
|
systemParticipantCount: number;
|
||||||
pendingParticipantCount: number;
|
pendingParticipantCount: number;
|
||||||
systemRedisData: Array<{
|
systemRedisData: Array<{
|
||||||
accountType: string; // 改为字符串支持组合键
|
accountType: string;
|
||||||
|
regionCode: string | null;
|
||||||
reward: ShareAmount;
|
reward: ShareAmount;
|
||||||
contribution: ShareAmount;
|
contribution: ShareAmount;
|
||||||
}>;
|
}>;
|
||||||
|
|
@ -541,7 +549,8 @@ export class MiningDistributionService {
|
||||||
|
|
||||||
// 计算所有系统账户的挖矿奖励
|
// 计算所有系统账户的挖矿奖励
|
||||||
const systemRewards: Array<{
|
const systemRewards: Array<{
|
||||||
accountType: string; // 改为字符串支持组合键
|
accountType: string;
|
||||||
|
regionCode: string | null;
|
||||||
reward: ShareAmount;
|
reward: ShareAmount;
|
||||||
contribution: ShareAmount;
|
contribution: ShareAmount;
|
||||||
memo: string;
|
memo: string;
|
||||||
|
|
@ -549,7 +558,7 @@ export class MiningDistributionService {
|
||||||
|
|
||||||
for (const systemAccount of systemAccounts) {
|
for (const systemAccount of systemAccounts) {
|
||||||
// 总部账户不直接参与挖矿,它只接收未解锁算力的收益
|
// 总部账户不直接参与挖矿,它只接收未解锁算力的收益
|
||||||
if (systemAccount.baseType === HEADQUARTERS_BASE_TYPE) {
|
if (systemAccount.accountType === HEADQUARTERS_ACCOUNT_TYPE) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -566,6 +575,7 @@ export class MiningDistributionService {
|
||||||
if (!reward.isZero()) {
|
if (!reward.isZero()) {
|
||||||
systemRewards.push({
|
systemRewards.push({
|
||||||
accountType: systemAccount.accountType,
|
accountType: systemAccount.accountType,
|
||||||
|
regionCode: systemAccount.regionCode,
|
||||||
reward,
|
reward,
|
||||||
contribution: systemAccount.totalContribution,
|
contribution: systemAccount.totalContribution,
|
||||||
memo: `秒挖矿 ${currentSecond.getTime()}`,
|
memo: `秒挖矿 ${currentSecond.getTime()}`,
|
||||||
|
|
@ -612,11 +622,11 @@ export class MiningDistributionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理系统账户挖矿(复用外部事务)
|
// 处理系统账户挖矿(复用外部事务)
|
||||||
for (const { accountType, reward, contribution, memo } of systemRewards) {
|
for (const { accountType, regionCode, reward, contribution, memo } of systemRewards) {
|
||||||
await this.systemMiningAccountRepository.mine(accountType, reward, memo, tx);
|
await this.systemMiningAccountRepository.mine(accountType as any, regionCode, reward, memo, tx);
|
||||||
systemDistributed = systemDistributed.add(reward);
|
systemDistributed = systemDistributed.add(reward);
|
||||||
systemParticipantCount++;
|
systemParticipantCount++;
|
||||||
systemRedisData.push({ accountType, reward, contribution });
|
systemRedisData.push({ accountType, regionCode, reward, contribution });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理待解锁算力挖矿(归总部账户)
|
// 处理待解锁算力挖矿(归总部账户)
|
||||||
|
|
@ -633,7 +643,8 @@ export class MiningDistributionService {
|
||||||
// 一次性更新总部账户(而不是每个待解锁算力单独更新)
|
// 一次性更新总部账户(而不是每个待解锁算力单独更新)
|
||||||
if (!headquartersTotal.isZero()) {
|
if (!headquartersTotal.isZero()) {
|
||||||
await this.systemMiningAccountRepository.mine(
|
await this.systemMiningAccountRepository.mine(
|
||||||
HEADQUARTERS_BASE_TYPE,
|
HEADQUARTERS_ACCOUNT_TYPE as any,
|
||||||
|
null, // HEADQUARTERS 没有 regionCode
|
||||||
headquartersTotal,
|
headquartersTotal,
|
||||||
`秒挖矿 ${currentSecond.getTime()} - 待解锁算力汇总 (${pendingRewards.length}笔)`,
|
`秒挖矿 ${currentSecond.getTime()} - 待解锁算力汇总 (${pendingRewards.length}笔)`,
|
||||||
tx,
|
tx,
|
||||||
|
|
@ -696,20 +707,26 @@ export class MiningDistributionService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 累积系统账户每分钟的挖矿数据到Redis
|
* 累积系统账户每分钟的挖矿数据到Redis
|
||||||
* @param accountType 账户类型(组合键,如 PROVINCE_440000, CITY_440100)
|
* @param accountType 账户类型(OPERATION/PROVINCE/CITY/HEADQUARTERS)
|
||||||
|
* @param regionCode 区域代码(省市代码,如 440000, 440100)
|
||||||
*/
|
*/
|
||||||
private async accumulateSystemMinuteData(
|
private async accumulateSystemMinuteData(
|
||||||
accountType: string,
|
accountType: string,
|
||||||
|
regionCode: string | null,
|
||||||
minuteTime: Date,
|
minuteTime: Date,
|
||||||
reward: ShareAmount,
|
reward: ShareAmount,
|
||||||
accountContribution: ShareAmount,
|
accountContribution: ShareAmount,
|
||||||
totalContribution: ShareAmount,
|
totalContribution: ShareAmount,
|
||||||
secondDistribution: ShareAmount,
|
secondDistribution: ShareAmount,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const key = `mining:system:minute:${minuteTime.getTime()}:${accountType}`;
|
// Redis key 使用 accountType:regionCode 格式区分不同的省市账户
|
||||||
|
const keyId = regionCode ? `${accountType}:${regionCode}` : accountType;
|
||||||
|
const key = `mining:system:minute:${minuteTime.getTime()}:${keyId}`;
|
||||||
const existing = await this.redis.get(key);
|
const existing = await this.redis.get(key);
|
||||||
|
|
||||||
let accumulated: {
|
let accumulated: {
|
||||||
|
accountType: string;
|
||||||
|
regionCode: string | null;
|
||||||
minedAmount: string;
|
minedAmount: string;
|
||||||
contributionRatio: string;
|
contributionRatio: string;
|
||||||
totalContribution: string;
|
totalContribution: string;
|
||||||
|
|
@ -728,6 +745,8 @@ export class MiningDistributionService {
|
||||||
accumulated.accountContribution = accountContribution.value.toString();
|
accumulated.accountContribution = accountContribution.value.toString();
|
||||||
} else {
|
} else {
|
||||||
accumulated = {
|
accumulated = {
|
||||||
|
accountType,
|
||||||
|
regionCode,
|
||||||
minedAmount: reward.value.toString(),
|
minedAmount: reward.value.toString(),
|
||||||
contributionRatio: accountContribution.value.dividedBy(totalContribution.value).toString(),
|
contributionRatio: accountContribution.value.dividedBy(totalContribution.value).toString(),
|
||||||
totalContribution: totalContribution.value.toString(),
|
totalContribution: totalContribution.value.toString(),
|
||||||
|
|
@ -795,7 +814,8 @@ export class MiningDistributionService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 写入系统账户每分钟汇总的挖矿记录
|
* 写入系统账户每分钟汇总的挖矿记录
|
||||||
* 支持组合键账户类型(如 PROVINCE_440000, CITY_440100)
|
* accountType: OPERATION/PROVINCE/CITY/HEADQUARTERS
|
||||||
|
* regionCode: 区域代码(省市代码)
|
||||||
*/
|
*/
|
||||||
private async writeSystemMinuteRecords(minuteTime: Date): Promise<void> {
|
private async writeSystemMinuteRecords(minuteTime: Date): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
|
@ -807,12 +827,14 @@ export class MiningDistributionService {
|
||||||
if (!data) continue;
|
if (!data) continue;
|
||||||
|
|
||||||
const accumulated = JSON.parse(data);
|
const accumulated = JSON.parse(data);
|
||||||
// accountType 现在是字符串类型,支持组合键
|
// 从 accumulated 中获取 accountType 和 regionCode
|
||||||
const accountType = key.split(':').pop()!;
|
const accountType = accumulated.accountType;
|
||||||
|
const regionCode = accumulated.regionCode;
|
||||||
|
|
||||||
// 使用 repository 的 saveMinuteRecord 方法(支持 upsert)
|
// 使用 repository 的 saveMinuteRecord 方法(支持 upsert)
|
||||||
await this.systemMiningAccountRepository.saveMinuteRecord(
|
await this.systemMiningAccountRepository.saveMinuteRecord(
|
||||||
accountType,
|
accountType as any,
|
||||||
|
regionCode,
|
||||||
minuteTime,
|
minuteTime,
|
||||||
new ShareAmount(accumulated.contributionRatio),
|
new ShareAmount(accumulated.contributionRatio),
|
||||||
new ShareAmount(accumulated.totalContribution),
|
new ShareAmount(accumulated.totalContribution),
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,14 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
|
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
|
||||||
import {
|
import {
|
||||||
SystemMiningAccountRepository,
|
SystemMiningAccountRepository,
|
||||||
SystemAccountBaseType,
|
SystemAccountType,
|
||||||
} from '../../infrastructure/persistence/repositories/system-mining-account.repository';
|
} from '../../infrastructure/persistence/repositories/system-mining-account.repository';
|
||||||
import { ShareAmount } from '../../domain/value-objects/share-amount.vo';
|
import { ShareAmount } from '../../domain/value-objects/share-amount.vo';
|
||||||
import Decimal from 'decimal.js';
|
import Decimal from 'decimal.js';
|
||||||
|
|
||||||
interface SystemAccountSyncedData {
|
interface SystemAccountSyncedData {
|
||||||
accountType: string; // 组合键: OPERATION, PROVINCE_440000, CITY_440100 等
|
accountType: string; // OPERATION / PROVINCE / CITY / HEADQUARTERS
|
||||||
baseType: string; // 基础类型: OPERATION / PROVINCE / CITY / HEADQUARTERS
|
regionCode: string | null; // 省/市代码,如 440000, 440100
|
||||||
regionCode: string | null; // 区域代码: 省/市代码,如 440000, 440100
|
|
||||||
name: string;
|
name: string;
|
||||||
contributionBalance: string;
|
contributionBalance: string;
|
||||||
}
|
}
|
||||||
|
|
@ -51,16 +50,17 @@ export class NetworkSyncService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理系统账户同步事件
|
* 处理系统账户同步事件
|
||||||
* 支持组合键账户类型(如 PROVINCE_440000, CITY_440100)
|
* accountType: OPERATION / PROVINCE / CITY / HEADQUARTERS
|
||||||
|
* regionCode: 省/市代码,如 440000, 440100
|
||||||
*/
|
*/
|
||||||
async handleSystemAccountSynced(data: SystemAccountSyncedData): Promise<void> {
|
async handleSystemAccountSynced(data: SystemAccountSyncedData): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// 验证 baseType 是否有效
|
// 验证 accountType 是否有效
|
||||||
const validBaseTypes: SystemAccountBaseType[] = ['OPERATION', 'PROVINCE', 'CITY', 'HEADQUARTERS'];
|
const validTypes: SystemAccountType[] = ['OPERATION', 'PROVINCE', 'CITY', 'HEADQUARTERS'];
|
||||||
const baseType = (data.baseType || this.extractBaseType(data.accountType)) as SystemAccountBaseType;
|
const accountType = data.accountType as SystemAccountType;
|
||||||
|
|
||||||
if (!validBaseTypes.includes(baseType)) {
|
if (!validTypes.includes(accountType)) {
|
||||||
this.logger.warn(`Unknown system account base type: ${baseType} for ${data.accountType}`);
|
this.logger.warn(`Unknown system account type: ${accountType}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,15 +68,14 @@ export class NetworkSyncService {
|
||||||
|
|
||||||
// 使用 upsert 动态创建或更新账户
|
// 使用 upsert 动态创建或更新账户
|
||||||
await this.systemAccountRepository.updateContribution(
|
await this.systemAccountRepository.updateContribution(
|
||||||
data.accountType,
|
accountType,
|
||||||
baseType,
|
|
||||||
data.regionCode,
|
data.regionCode,
|
||||||
data.name,
|
data.name,
|
||||||
contribution,
|
contribution,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`Synced system account ${data.accountType} (baseType=${baseType}, regionCode=${data.regionCode}): contribution=${data.contributionBalance}`,
|
`Synced system account ${accountType} (regionCode=${data.regionCode}): contribution=${data.contributionBalance}`,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Failed to sync system account ${data.accountType}`, error);
|
this.logger.error(`Failed to sync system account ${data.accountType}`, error);
|
||||||
|
|
@ -84,17 +83,6 @@ export class NetworkSyncService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 从 accountType 提取基础类型(向后兼容)
|
|
||||||
* 例如: PROVINCE_440000 -> PROVINCE, CITY_440100 -> CITY
|
|
||||||
*/
|
|
||||||
private extractBaseType(accountType: string): string {
|
|
||||||
if (accountType.startsWith('PROVINCE_')) return 'PROVINCE';
|
|
||||||
if (accountType.startsWith('CITY_')) return 'CITY';
|
|
||||||
// 如果没有下划线,则 accountType 本身就是基础类型
|
|
||||||
return accountType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理全网进度更新事件
|
* 处理全网进度更新事件
|
||||||
*/
|
*/
|
||||||
|
|
@ -159,7 +147,6 @@ export class NetworkSyncService {
|
||||||
for (const account of systemAccounts) {
|
for (const account of systemAccounts) {
|
||||||
await this.handleSystemAccountSynced({
|
await this.handleSystemAccountSynced({
|
||||||
accountType: account.accountType,
|
accountType: account.accountType,
|
||||||
baseType: account.baseType || account.accountType, // 向后兼容
|
|
||||||
regionCode: account.regionCode || null,
|
regionCode: account.regionCode || null,
|
||||||
name: account.name,
|
name: account.name,
|
||||||
contributionBalance: account.contributionBalance,
|
contributionBalance: account.contributionBalance,
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,12 @@ import { PrismaService } from '../prisma/prisma.service';
|
||||||
import { ShareAmount } from '../../../domain/value-objects/share-amount.vo';
|
import { ShareAmount } from '../../../domain/value-objects/share-amount.vo';
|
||||||
import { TransactionClient } from '../unit-of-work/unit-of-work';
|
import { TransactionClient } from '../unit-of-work/unit-of-work';
|
||||||
|
|
||||||
// 基础类型定义(不再使用 Prisma 枚举)
|
export type SystemAccountType = 'OPERATION' | 'PROVINCE' | 'CITY' | 'HEADQUARTERS';
|
||||||
export type SystemAccountBaseType = 'OPERATION' | 'PROVINCE' | 'CITY' | 'HEADQUARTERS';
|
|
||||||
|
|
||||||
export interface SystemMiningAccountSnapshot {
|
export interface SystemMiningAccountSnapshot {
|
||||||
accountType: string; // 组合键: OPERATION, PROVINCE_440000, CITY_440100 等
|
id: string;
|
||||||
baseType: SystemAccountBaseType; // 基础类型
|
accountType: SystemAccountType;
|
||||||
regionCode: string | null; // 区域代码
|
regionCode: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
totalMined: ShareAmount;
|
totalMined: ShareAmount;
|
||||||
availableBalance: ShareAmount;
|
availableBalance: ShareAmount;
|
||||||
|
|
@ -21,9 +20,14 @@ export interface SystemMiningAccountSnapshot {
|
||||||
export class SystemMiningAccountRepository {
|
export class SystemMiningAccountRepository {
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
async findByType(accountType: string): Promise<SystemMiningAccountSnapshot | null> {
|
async findByTypeAndRegion(
|
||||||
|
accountType: SystemAccountType,
|
||||||
|
regionCode: string | null,
|
||||||
|
): Promise<SystemMiningAccountSnapshot | null> {
|
||||||
const record = await this.prisma.systemMiningAccount.findUnique({
|
const record = await this.prisma.systemMiningAccount.findUnique({
|
||||||
where: { accountType },
|
where: {
|
||||||
|
accountType_regionCode: { accountType, regionCode },
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!record) {
|
if (!record) {
|
||||||
|
|
@ -34,12 +38,12 @@ export class SystemMiningAccountRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据基础类型查找所有账户
|
* 根据类型查找所有账户
|
||||||
*/
|
*/
|
||||||
async findByBaseType(baseType: SystemAccountBaseType): Promise<SystemMiningAccountSnapshot[]> {
|
async findByType(accountType: SystemAccountType): Promise<SystemMiningAccountSnapshot[]> {
|
||||||
const records = await this.prisma.systemMiningAccount.findMany({
|
const records = await this.prisma.systemMiningAccount.findMany({
|
||||||
where: { baseType },
|
where: { accountType },
|
||||||
orderBy: { accountType: 'asc' },
|
orderBy: { regionCode: 'asc' },
|
||||||
});
|
});
|
||||||
|
|
||||||
return records.map((r) => this.toSnapshot(r));
|
return records.map((r) => this.toSnapshot(r));
|
||||||
|
|
@ -47,29 +51,28 @@ export class SystemMiningAccountRepository {
|
||||||
|
|
||||||
async findAll(): Promise<SystemMiningAccountSnapshot[]> {
|
async findAll(): Promise<SystemMiningAccountSnapshot[]> {
|
||||||
const records = await this.prisma.systemMiningAccount.findMany({
|
const records = await this.prisma.systemMiningAccount.findMany({
|
||||||
orderBy: { accountType: 'asc' },
|
orderBy: [{ accountType: 'asc' }, { regionCode: 'asc' }],
|
||||||
});
|
});
|
||||||
|
|
||||||
return records.map((r) => this.toSnapshot(r));
|
return records.map((r) => this.toSnapshot(r));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 确保基础汇总账户存在
|
* 确保基础账户存在
|
||||||
*/
|
*/
|
||||||
async ensureSystemAccountsExist(): Promise<void> {
|
async ensureSystemAccountsExist(): Promise<void> {
|
||||||
const accounts: { accountType: string; baseType: SystemAccountBaseType; name: string }[] = [
|
const accounts: { accountType: SystemAccountType; name: string }[] = [
|
||||||
{ accountType: 'OPERATION', baseType: 'OPERATION', name: '运营账户' },
|
{ accountType: 'OPERATION', name: '运营账户' },
|
||||||
{ accountType: 'PROVINCE', baseType: 'PROVINCE', name: '省公司账户' },
|
{ accountType: 'HEADQUARTERS', name: '总部账户' },
|
||||||
{ accountType: 'CITY', baseType: 'CITY', name: '市公司账户' },
|
|
||||||
{ accountType: 'HEADQUARTERS', baseType: 'HEADQUARTERS', name: '总部账户' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const account of accounts) {
|
for (const account of accounts) {
|
||||||
await this.prisma.systemMiningAccount.upsert({
|
await this.prisma.systemMiningAccount.upsert({
|
||||||
where: { accountType: account.accountType },
|
where: {
|
||||||
|
accountType_regionCode: { accountType: account.accountType, regionCode: null },
|
||||||
|
},
|
||||||
create: {
|
create: {
|
||||||
accountType: account.accountType,
|
accountType: account.accountType,
|
||||||
baseType: account.baseType,
|
|
||||||
regionCode: null,
|
regionCode: null,
|
||||||
name: account.name,
|
name: account.name,
|
||||||
totalMined: 0,
|
totalMined: 0,
|
||||||
|
|
@ -82,20 +85,20 @@ export class SystemMiningAccountRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新系统账户算力(支持动态创建省市账户)
|
* 更新系统账户算力(动态创建省市账户)
|
||||||
*/
|
*/
|
||||||
async updateContribution(
|
async updateContribution(
|
||||||
accountType: string,
|
accountType: SystemAccountType,
|
||||||
baseType: SystemAccountBaseType,
|
|
||||||
regionCode: string | null,
|
regionCode: string | null,
|
||||||
name: string,
|
name: string,
|
||||||
contribution: ShareAmount,
|
contribution: ShareAmount,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.prisma.systemMiningAccount.upsert({
|
await this.prisma.systemMiningAccount.upsert({
|
||||||
where: { accountType },
|
where: {
|
||||||
|
accountType_regionCode: { accountType, regionCode },
|
||||||
|
},
|
||||||
create: {
|
create: {
|
||||||
accountType,
|
accountType,
|
||||||
baseType,
|
|
||||||
regionCode,
|
regionCode,
|
||||||
name,
|
name,
|
||||||
totalContribution: contribution.value,
|
totalContribution: contribution.value,
|
||||||
|
|
@ -117,25 +120,24 @@ export class SystemMiningAccountRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行系统账户挖矿(带外部事务支持)
|
* 执行系统账户挖矿
|
||||||
* @param accountType 账户类型(组合键)
|
|
||||||
* @param amount 挖矿数量
|
|
||||||
* @param memo 备注
|
|
||||||
* @param tx 可选的外部事务客户端,如果不传则自动创建事务
|
|
||||||
*/
|
*/
|
||||||
async mine(
|
async mine(
|
||||||
accountType: string,
|
accountType: SystemAccountType,
|
||||||
|
regionCode: string | null,
|
||||||
amount: ShareAmount,
|
amount: ShareAmount,
|
||||||
memo: string,
|
memo: string,
|
||||||
tx?: TransactionClient,
|
tx?: TransactionClient,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const executeInTx = async (client: TransactionClient) => {
|
const executeInTx = async (client: TransactionClient) => {
|
||||||
const account = await client.systemMiningAccount.findUnique({
|
const account = await client.systemMiningAccount.findUnique({
|
||||||
where: { accountType },
|
where: {
|
||||||
|
accountType_regionCode: { accountType, regionCode },
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
throw new Error(`System account ${accountType} not found`);
|
throw new Error(`System account ${accountType}:${regionCode} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const balanceBefore = account.availableBalance;
|
const balanceBefore = account.availableBalance;
|
||||||
|
|
@ -143,7 +145,7 @@ export class SystemMiningAccountRepository {
|
||||||
const totalMined = account.totalMined.plus(amount.value);
|
const totalMined = account.totalMined.plus(amount.value);
|
||||||
|
|
||||||
await client.systemMiningAccount.update({
|
await client.systemMiningAccount.update({
|
||||||
where: { accountType },
|
where: { id: account.id },
|
||||||
data: {
|
data: {
|
||||||
totalMined,
|
totalMined,
|
||||||
availableBalance: balanceAfter,
|
availableBalance: balanceAfter,
|
||||||
|
|
@ -152,7 +154,7 @@ export class SystemMiningAccountRepository {
|
||||||
|
|
||||||
await client.systemMiningTransaction.create({
|
await client.systemMiningTransaction.create({
|
||||||
data: {
|
data: {
|
||||||
accountType,
|
systemAccountId: account.id,
|
||||||
type: 'MINE',
|
type: 'MINE',
|
||||||
amount: amount.value,
|
amount: amount.value,
|
||||||
balanceBefore,
|
balanceBefore,
|
||||||
|
|
@ -163,31 +165,40 @@ export class SystemMiningAccountRepository {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (tx) {
|
if (tx) {
|
||||||
// 使用外部事务
|
|
||||||
await executeInTx(tx);
|
await executeInTx(tx);
|
||||||
} else {
|
} else {
|
||||||
// 自动创建事务(向后兼容)
|
|
||||||
await this.prisma.$transaction(executeInTx);
|
await this.prisma.$transaction(executeInTx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveMinuteRecord(
|
async saveMinuteRecord(
|
||||||
accountType: string,
|
accountType: SystemAccountType,
|
||||||
|
regionCode: string | null,
|
||||||
miningMinute: Date,
|
miningMinute: Date,
|
||||||
contributionRatio: ShareAmount,
|
contributionRatio: ShareAmount,
|
||||||
totalContribution: ShareAmount,
|
totalContribution: ShareAmount,
|
||||||
secondDistribution: ShareAmount,
|
secondDistribution: ShareAmount,
|
||||||
minedAmount: ShareAmount,
|
minedAmount: ShareAmount,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const account = await this.prisma.systemMiningAccount.findUnique({
|
||||||
|
where: {
|
||||||
|
accountType_regionCode: { accountType, regionCode },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
throw new Error(`System account ${accountType}:${regionCode} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
await this.prisma.systemMiningRecord.upsert({
|
await this.prisma.systemMiningRecord.upsert({
|
||||||
where: {
|
where: {
|
||||||
accountType_miningMinute: {
|
systemAccountId_miningMinute: {
|
||||||
accountType,
|
systemAccountId: account.id,
|
||||||
miningMinute,
|
miningMinute,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
accountType,
|
systemAccountId: account.id,
|
||||||
miningMinute,
|
miningMinute,
|
||||||
contributionRatio: contributionRatio.value,
|
contributionRatio: contributionRatio.value,
|
||||||
totalContribution: totalContribution.value,
|
totalContribution: totalContribution.value,
|
||||||
|
|
@ -202,8 +213,8 @@ export class SystemMiningAccountRepository {
|
||||||
|
|
||||||
private toSnapshot(record: any): SystemMiningAccountSnapshot {
|
private toSnapshot(record: any): SystemMiningAccountSnapshot {
|
||||||
return {
|
return {
|
||||||
accountType: record.accountType,
|
id: record.id,
|
||||||
baseType: record.baseType as SystemAccountBaseType,
|
accountType: record.accountType as SystemAccountType,
|
||||||
regionCode: record.regionCode,
|
regionCode: record.regionCode,
|
||||||
name: record.name,
|
name: record.name,
|
||||||
totalMined: new ShareAmount(record.totalMined),
|
totalMined: new ShareAmount(record.totalMined),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue