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:
parent
6594845d4c
commit
ef330a2687
|
|
@ -16,7 +16,8 @@
|
||||||
"prisma:generate": "prisma generate",
|
"prisma:generate": "prisma generate",
|
||||||
"prisma:migrate": "prisma migrate dev",
|
"prisma:migrate": "prisma migrate dev",
|
||||||
"prisma:migrate:prod": "prisma migrate deploy",
|
"prisma:migrate:prod": "prisma migrate deploy",
|
||||||
"prisma:studio": "prisma studio"
|
"prisma:studio": "prisma studio",
|
||||||
|
"prisma:seed": "ts-node prisma/seed.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/common": "^10.3.0",
|
"@nestjs/common": "^10.3.0",
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
});
|
||||||
|
|
@ -151,15 +151,25 @@ export class ContributionWalletService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const systemAccount = await tx.systemAccount.findFirst({
|
let systemAccount = await tx.systemAccount.findFirst({
|
||||||
where: whereClause,
|
where: whereClause,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 如果找不到,尝试自动创建省/市级系统账户
|
||||||
if (!systemAccount) {
|
if (!systemAccount) {
|
||||||
this.logger.warn(
|
systemAccount = await this.createSystemAccountIfNeeded(
|
||||||
`System account not found: ${input.accountType}, province: ${input.provinceCode}, city: ${input.cityCode}`,
|
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(
|
const balanceBefore = new Decimal(
|
||||||
|
|
@ -281,4 +291,110 @@ export class ContributionWalletService {
|
||||||
};
|
};
|
||||||
return `${typeMap[input.contributionType]}, 来源认种: ${input.sourceAdoptionId}, 认种人: ${input.sourceAccountSequence}`;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue