feat(mining-wallet): add seed and auto-create province/city accounts

- Add prisma seed to initialize core system accounts (HQ, OP, FEE) and pool accounts
- Auto-create province/city system accounts on-demand during contribution distribution
- Province/city regions are also auto-created if not exist

This ensures:
1. Core accounts exist after deployment (via seed)
2. Province/city accounts are created dynamically as orders come in

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-13 23:36:31 -08:00
parent 6594845d4c
commit ef330a2687
3 changed files with 214 additions and 5 deletions

View File

@ -16,7 +16,8 @@
"prisma:generate": "prisma generate",
"prisma:migrate": "prisma migrate dev",
"prisma:migrate:prod": "prisma migrate deploy",
"prisma:studio": "prisma studio"
"prisma:studio": "prisma studio",
"prisma:seed": "ts-node prisma/seed.ts"
},
"dependencies": {
"@nestjs/common": "^10.3.0",

View File

@ -0,0 +1,92 @@
import { PrismaClient } from '@prisma/client';
import Decimal from 'decimal.js';
const prisma = new PrismaClient();
async function main() {
console.log('Seeding mining-wallet-service database...');
// 1. 初始化核心系统账户(总部、运营、手续费)
const systemAccounts = [
{ accountType: 'HEADQUARTERS', name: '总部账户', code: 'HQ' },
{ accountType: 'OPERATION', name: '运营账户', code: 'OP' },
{ accountType: 'FEE', name: '手续费账户', code: 'FEE' },
];
for (const account of systemAccounts) {
const existing = await prisma.systemAccount.findFirst({
where: { code: account.code },
});
if (!existing) {
await prisma.systemAccount.create({
data: {
accountType: account.accountType as any,
name: account.name,
code: account.code,
isActive: true,
},
});
console.log(`Created system account: ${account.code}`);
} else {
console.log(`System account already exists: ${account.code}`);
}
}
// 2. 初始化池账户(积分股池、黑洞池、流通池)
const poolAccounts = [
{
poolType: 'SHARE_POOL',
name: '积分股池',
balance: new Decimal('100000000'), // 1亿初始发行量
description: '挖矿奖励的来源池,总发行量',
},
{
poolType: 'BLACK_HOLE_POOL',
name: '黑洞池',
balance: new Decimal('0'),
targetBurn: new Decimal('50000000'), // 目标销毁5000万
description: '销毁的积分股,用于减少流通量',
},
{
poolType: 'CIRCULATION_POOL',
name: '流通池',
balance: new Decimal('0'),
description: '市场流通的积分股',
},
];
for (const pool of poolAccounts) {
const existing = await prisma.poolAccount.findFirst({
where: { poolType: pool.poolType as any },
});
if (!existing) {
await prisma.poolAccount.create({
data: {
poolType: pool.poolType as any,
name: pool.name,
balance: pool.balance,
targetBurn: pool.targetBurn,
remainingBurn: pool.targetBurn,
description: pool.description,
isActive: true,
},
});
console.log(`Created pool account: ${pool.poolType}`);
} else {
console.log(`Pool account already exists: ${pool.poolType}`);
}
}
console.log('Seeding completed!');
}
main()
.catch((e) => {
console.error('Seeding failed:', e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});

View File

@ -151,15 +151,25 @@ export class ContributionWalletService {
};
}
const systemAccount = await tx.systemAccount.findFirst({
let systemAccount = await tx.systemAccount.findFirst({
where: whereClause,
});
// 如果找不到,尝试自动创建省/市级系统账户
if (!systemAccount) {
this.logger.warn(
`System account not found: ${input.accountType}, province: ${input.provinceCode}, city: ${input.cityCode}`,
systemAccount = await this.createSystemAccountIfNeeded(
tx,
input.accountType,
input.provinceCode,
input.cityCode,
);
return;
if (!systemAccount) {
this.logger.warn(
`Failed to create system account: ${input.accountType}, province: ${input.provinceCode}, city: ${input.cityCode}`,
);
return;
}
}
const balanceBefore = new Decimal(
@ -281,4 +291,110 @@ export class ContributionWalletService {
};
return `${typeMap[input.contributionType]}, 来源认种: ${input.sourceAdoptionId}, 认种人: ${input.sourceAccountSequence}`;
}
/**
* /
* /
*/
private async createSystemAccountIfNeeded(
tx: any,
accountType: string,
provinceCode?: string,
cityCode?: string,
): Promise<any | null> {
// 只处理省/市级账户的自动创建
if (accountType === 'PROVINCE' && provinceCode) {
// 先找或创建省份
let province = await tx.province.findUnique({
where: { code: provinceCode },
});
if (!province) {
province = await tx.province.create({
data: {
code: provinceCode,
name: `${provinceCode}`, // 默认名称,后续可更新
status: 'ACTIVE',
},
});
this.logger.log(`Auto-created province: ${provinceCode}`);
}
// 创建省级系统账户
const account = await tx.systemAccount.create({
data: {
accountType: 'PROVINCE',
name: `${province.name}账户`,
code: `PROV-${provinceCode}`,
provinceId: province.id,
isActive: true,
},
});
this.logger.log(`Auto-created province system account: ${account.code}`);
return account;
}
if (accountType === 'CITY' && cityCode) {
// 先找城市
let city = await tx.city.findUnique({
where: { code: cityCode },
});
if (!city) {
// 城市不存在,需要先有省份
// 尝试从 cityCode 推断省份(如 GD-SZ 中的 GD
const parts = cityCode.split('-');
const inferredProvinceCode = parts.length > 1 ? parts[0] : null;
if (!inferredProvinceCode) {
this.logger.warn(`Cannot infer province from city code: ${cityCode}`);
return null;
}
// 找或创建省份
let province = await tx.province.findUnique({
where: { code: inferredProvinceCode },
});
if (!province) {
province = await tx.province.create({
data: {
code: inferredProvinceCode,
name: `${inferredProvinceCode}`,
status: 'ACTIVE',
},
});
this.logger.log(`Auto-created province: ${inferredProvinceCode}`);
}
// 创建城市
city = await tx.city.create({
data: {
code: cityCode,
name: `${cityCode}`, // 默认名称
provinceId: province.id,
status: 'ACTIVE',
},
});
this.logger.log(`Auto-created city: ${cityCode}`);
}
// 创建市级系统账户
const account = await tx.systemAccount.create({
data: {
accountType: 'CITY',
name: `${city.name}账户`,
code: `CITY-${cityCode}`,
provinceId: city.provinceId,
cityId: city.id,
isActive: true,
},
});
this.logger.log(`Auto-created city system account: ${account.code}`);
return account;
}
// 其他类型HEADQUARTERS, OPERATION, FEE不自动创建需要在 seed 中初始化
return null;
}
}