fix(wallet): 系统账户划转前自动结算 settleableUsdt,解决全额划转余额不足

## 问题
省/市区域和团队账户收到的奖励进入 settleableUsdt,
但划转验证只检查 usdtAvailable,导致全额划转报"余额不足"。

## 修复
在 requestSystemWithdrawal 事务中,划转前自动执行结算:
1. 检测账户是否为区域/团队账户(6/7/8/9开头)
2. 若 settleableUsdt > 0,自动结算到 usdtAvailable
3. 记录 REWARD_SETTLED 流水明细(含 trigger=SYSTEM_WITHDRAWAL_AUTO_SETTLE)
4. 结算和划转在同一事务中,保证原子性

## 审计流水
每次自动结算会产生一条独立的 REWARD_SETTLED 流水:
- memo: "系统账户自动结算: 待结算 X 绿积分转入可用余额(划转前自动执行)"
- payloadJson: { settledAmount, previousAvailable, newAvailable, trigger }

## 其他
- 划转白名单扩展支持 6xxx(市团队)、7xxx(省团队)
- getSystemAccountName 添加省团队/市团队名称映射

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-02 06:30:34 -08:00
parent 5fad40cec1
commit d3969710be
1 changed files with 64 additions and 11 deletions

View File

@ -95,6 +95,16 @@ export class SystemWithdrawalApplicationService {
return `市区域(${accountSequence.substring(1)})`;
}
// 省团队账户: 7 + 省代码
if (accountSequence.startsWith('7') && accountSequence.length === 7) {
return `省团队(${accountSequence.substring(1)})`;
}
// 市团队账户: 6 + 市代码
if (accountSequence.startsWith('6') && accountSequence.length === 7) {
return `市团队(${accountSequence.substring(1)})`;
}
return `系统账户(${accountSequence})`;
}
@ -107,13 +117,8 @@ export class SystemWithdrawalApplicationService {
return true;
}
// 省区域账户: 9 + 省代码
if (accountSequence.startsWith('9') && accountSequence.length === 7) {
return true;
}
// 市区域账户: 8 + 市代码
if (accountSequence.startsWith('8') && accountSequence.length === 7) {
// 区域/团队账户: 6=市团队, 7=省团队, 8=市区域, 9=省区域
if (/^[6-9]\d{6}$/.test(accountSequence)) {
return true;
}
@ -166,12 +171,60 @@ export class SystemWithdrawalApplicationService {
throw new BadRequestException(`系统账户不存在: ${command.fromAccountSequence}`);
}
const currentBalance = new Decimal(systemWallet.usdtAvailable.toString());
let currentAvailable = new Decimal(systemWallet.usdtAvailable.toString());
const settleableAmount = new Decimal(systemWallet.settleableUsdt?.toString() || '0');
const withdrawAmount = new Decimal(command.amount);
if (currentBalance.lessThan(withdrawAmount)) {
// 6.1.1 如果是区域/团队账户且有待结算余额,先自动结算
// 6=市团队, 7=省团队, 8=市区域, 9=省区域 — 这些账户的奖励都进 settleableUsdt
const isRegionAccount = /^[6-9]/.test(command.fromAccountSequence);
if (isRegionAccount && settleableAmount.greaterThan(0)) {
this.logger.log(
`[SYSTEM_WITHDRAWAL] 区域账户 ${command.fromAccountSequence} 自动结算: ` +
`settleableUsdt=${settleableAmount.toFixed(2)} → usdtAvailable`
);
// 结算settleableUsdt → usdtAvailable
const newAvailable = currentAvailable.plus(settleableAmount);
await tx.walletAccount.update({
where: { id: systemWallet.id },
data: {
usdtAvailable: newAvailable,
settleableUsdt: new Decimal(0),
updatedAt: new Date(),
},
});
// 记录结算流水(审计用)
await tx.ledgerEntry.create({
data: {
accountSequence: command.fromAccountSequence,
userId: systemWallet.userId,
entryType: LedgerEntryType.REWARD_SETTLED,
amount: settleableAmount,
assetType: 'USDT',
balanceAfter: newAvailable,
memo: `系统账户自动结算: 待结算 ${settleableAmount.toFixed(2)} 绿积分转入可用余额(划转前自动执行)`,
payloadJson: {
settledAmount: settleableAmount.toFixed(2),
previousAvailable: currentAvailable.toFixed(2),
newAvailable: newAvailable.toFixed(2),
trigger: 'SYSTEM_WITHDRAWAL_AUTO_SETTLE',
},
},
});
// 更新本地变量
currentAvailable = newAvailable;
this.logger.log(
`[SYSTEM_WITHDRAWAL] 自动结算完成: 可用余额 ${currentAvailable.toFixed(2)} 绿积分`
);
}
// 6.1.2 验证可用余额是否足够
if (currentAvailable.lessThan(withdrawAmount)) {
throw new BadRequestException(
`余额不足: 当前 ${currentBalance.toFixed(2)} 绿积分, 需要 ${withdrawAmount.toFixed(2)} 绿积分`
`余额不足: 当前可用 ${currentAvailable.toFixed(2)} 绿积分, 需要 ${withdrawAmount.toFixed(2)} 绿积分`
);
}
@ -179,7 +232,7 @@ export class SystemWithdrawalApplicationService {
const orderNo = this.generateOrderNo();
// 6.3 扣减系统账户余额
const newBalance = currentBalance.minus(withdrawAmount);
const newBalance = currentAvailable.minus(withdrawAmount);
await tx.walletAccount.update({
where: { id: systemWallet.id },
data: {