diff --git a/.claude/settings.local.json b/.claude/settings.local.json index cfb29539..a7120bcf 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -632,7 +632,11 @@ "Bash(where psql:*)", "Bash(git commit -m \"$\\(cat <<''EOF''\nfix\\(reporting-service\\): 修复面对面结算数据解包问题\n\nwallet-service 返回 { success, data, timestamp } 包装格式,\ngetOfflineSettlementSummary 需要用 response.data.data 解包才能获取真正的数据。\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n\\)\")", "Bash(git commit -m \"$\\(cat <<''EOF''\nfix\\(wallet/reporting\\): 修复手续费归集统计 API 的数据库表名和响应解包问题\n\n- wallet-service: 修复 getFeeCollectionSummary 中原生 SQL 使用错误表名\n - 将 ledger_entries 改为 wallet_ledger_entries(Prisma 映射表名)\n- reporting-service: 修复 getFeeCollectionSummary/Entries 响应解包\n - wallet-service 返回 { success, data, timestamp } 格式需要解包 data\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n\\)\")", - "Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(wallet-service\\): 添加手续费归集统计的历史数据兼容\n\n当 FEE_COLLECTION 流水为空时,自动从提现订单表查询历史手续费:\n- getFeeCollectionSummary: 从 withdrawal_orders 和 fiat_withdrawal_orders 聚合统计\n- getFeeCollectionEntries: 从两个订单表查询明细列表,支持分页和类型筛选\n- 按月统计使用 UNION ALL 合并两种提现订单数据\n- 明细记录添加备注说明区分来源(区块链/法币)\n\n回滚方式:删除 fallback 代码块和两个私有方法\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n\\)\")" + "Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(wallet-service\\): 添加手续费归集统计的历史数据兼容\n\n当 FEE_COLLECTION 流水为空时,自动从提现订单表查询历史手续费:\n- getFeeCollectionSummary: 从 withdrawal_orders 和 fiat_withdrawal_orders 聚合统计\n- getFeeCollectionEntries: 从两个订单表查询明细列表,支持分页和类型筛选\n- 按月统计使用 UNION ALL 合并两种提现订单数据\n- 明细记录添加备注说明区分来源(区块链/法币)\n\n回滚方式:删除 fallback 代码块和两个私有方法\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n\\)\")", + "Bash(dir /s /b *.yml)", + "Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(mobile-app\\): 添加联系客服功能\n\n在个人中心设置菜单中添加\"联系客服\"入口,点击后显示弹窗,\n用户可以查看客服的QQ号和微信号,并支持一键复制到剪贴板。\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n\\)\")", + "Bash(git commit -m \"$\\(cat <<''EOF''\nfix\\(wallet-service, admin-web\\): 修复系统账户划转金额类型问题\n\n- wallet-service: 支持 amount 为字符串或数字类型,添加类型转换\n- admin-web: 改进错误处理,正确提取 Axios 错误消息\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n\\)\")", + "Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(mobile-app\\): 更新客服联系方式\n\n- 客服微信1: liulianhuanghou1\n- 客服微信2: liulianhuanghou2\n- 客服QQ1: 1502109619\n- 客服QQ2: 2171447109\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n\\)\")" ], "deny": [], "ask": [] diff --git a/backend/services/wallet-service/src/application/services/system-withdrawal-application.service.ts b/backend/services/wallet-service/src/application/services/system-withdrawal-application.service.ts index 3c780f7f..bffa1824 100644 --- a/backend/services/wallet-service/src/application/services/system-withdrawal-application.service.ts +++ b/backend/services/wallet-service/src/application/services/system-withdrawal-application.service.ts @@ -17,10 +17,10 @@ import Decimal from 'decimal.js'; // 系统账户名称映射 const SYSTEM_ACCOUNT_NAMES: Record = { - 'S0000000001': '总部账户', - 'S0000000002': '成本账户', - 'S0000000003': '运营账户', - 'S0000000004': 'RWAD底池', + 'S0000000001': '总部储蓄', + 'S0000000002': '运营1', + 'S0000000003': '运营2', + 'S0000000004': '积分股池', 'S0000000005': '分享权益池', 'S0000000006': '手续费归集', }; @@ -28,7 +28,9 @@ const SYSTEM_ACCOUNT_NAMES: Record = { // 允许转出的系统账户白名单 const ALLOWED_WITHDRAWAL_ACCOUNTS = new Set([ 'S0000000001', // 总部账户 - 'S0000000003', // 运营账户 + 'S0000000002', // 运营1 + 'S0000000003', // 运营2 + 'S0000000004', // 积分股池 'S0000000005', // 分享权益池 'S0000000006', // 手续费归集 ]); @@ -480,6 +482,7 @@ export class SystemWithdrawalApplicationService { /** * 获取可转出的系统账户列表 + * [2026-01-07] 更新:S0000000006 手续费归集账户余额为0时,从提现订单表统计历史手续费 */ async getWithdrawableSystemAccounts(): Promise<{ accountSequence: string; @@ -488,7 +491,9 @@ export class SystemWithdrawalApplicationService { }[]> { const accounts: string[] = [ 'S0000000001', // 总部账户 - 'S0000000003', // 运营账户 + 'S0000000002', // 运营1 + 'S0000000003', // 运营2 + 'S0000000004', // 积分股池 'S0000000005', // 分享权益池 'S0000000006', // 手续费归集 ]; @@ -512,11 +517,71 @@ export class SystemWithdrawalApplicationService { const allWallets = [...wallets, ...regionWallets]; - return allWallets.map((w) => ({ - accountSequence: w.accountSequence, - accountName: this.getSystemAccountName(w.accountSequence), - balance: w.usdtAvailable.toString(), - })); + // [2026-01-07] 兼容:检查 S0000000006 手续费归集账户 + // 如果账户不存在或余额为0,从提现订单表统计历史手续费 + const feeAccountSequence = 'S0000000006'; + const feeWallet = allWallets.find(w => w.accountSequence === feeAccountSequence); + const feeBalance = feeWallet ? new Decimal(feeWallet.usdtAvailable.toString()) : new Decimal(0); + + let adjustedFeeBalance = feeBalance; + if (feeBalance.isZero()) { + this.logger.log('[getWithdrawableSystemAccounts] S0000000006 余额为0,从提现订单表统计历史手续费'); + const fallbackBalance = await this.calculateFeeCollectionFromOrders(); + adjustedFeeBalance = new Decimal(fallbackBalance); + this.logger.log(`[getWithdrawableSystemAccounts] 历史手续费总额: ${adjustedFeeBalance.toFixed(2)}`); + } + + // 构建返回结果 + const result = allWallets.map((w) => { + // 对于手续费归集账户,使用调整后的余额 + if (w.accountSequence === feeAccountSequence) { + return { + accountSequence: w.accountSequence, + accountName: this.getSystemAccountName(w.accountSequence), + balance: adjustedFeeBalance.toString(), + }; + } + return { + accountSequence: w.accountSequence, + accountName: this.getSystemAccountName(w.accountSequence), + balance: w.usdtAvailable.toString(), + }; + }); + + // [2026-01-07] 兼容:如果 S0000000006 账户不存在于数据库,但有历史手续费,也要显示 + if (!feeWallet && !adjustedFeeBalance.isZero()) { + result.push({ + accountSequence: feeAccountSequence, + accountName: this.getSystemAccountName(feeAccountSequence), + balance: adjustedFeeBalance.toString(), + }); + } + + return result; + } + + /** + * 从提现订单表统计历史手续费总额 + * [2026-01-07] 新增:用于 S0000000006 账户余额为0时的兼容 + * 回滚方式:删除此方法 + */ + private async calculateFeeCollectionFromOrders(): Promise { + // 1. 统计区块链提现订单手续费 (CONFIRMED 状态) + const withdrawalStats = await this.prisma.withdrawalOrder.aggregate({ + where: { status: 'CONFIRMED' }, + _sum: { fee: true }, + }); + + // 2. 统计法币提现订单手续费 (COMPLETED 状态) + const fiatStats = await this.prisma.fiatWithdrawalOrder.aggregate({ + where: { status: 'COMPLETED' }, + _sum: { fee: true }, + }); + + const withdrawalFeeAmount = Number(withdrawalStats._sum.fee) || 0; + const fiatFeeAmount = Number(fiatStats._sum.fee) || 0; + + return withdrawalFeeAmount + fiatFeeAmount; } /** diff --git a/backend/services/wallet-service/src/application/services/wallet-application.service.ts b/backend/services/wallet-service/src/application/services/wallet-application.service.ts index 88528c8a..f47c3416 100644 --- a/backend/services/wallet-service/src/application/services/wallet-application.service.ts +++ b/backend/services/wallet-service/src/application/services/wallet-application.service.ts @@ -2339,6 +2339,7 @@ export class WalletApplicationService { * 处理过期奖励 * 定时任务调用,将已过期的 PENDING 奖励标记为 EXPIRED * 过期金额转入总部社区账户 (S0000000001) + * [2026-01-07] 更新:按分配类型分别记录流水,便于明细查看 */ async processExpiredRewards(batchSize = 100): Promise<{ processedCount: number; @@ -2374,15 +2375,38 @@ export class WalletApplicationService { hqWallet.addAvailableBalance(Money.USDT(totalExpiredUsdt)); await this.walletRepo.save(hqWallet); - // 记录流水 - const ledgerEntry = LedgerEntry.create({ - accountSequence: headquartersAccountSequence, - userId: hqWallet.userId, - entryType: LedgerEntryType.SYSTEM_ALLOCATION, - amount: Money.USDT(totalExpiredUsdt), - memo: `Expired rewards from ${expiredRewards.length} pending entries`, - }); - await this.ledgerRepo.save(ledgerEntry); + // [2026-01-07] 更新:按分配类型分别记录流水,便于明细查看过期分享权益等 + // 按 allocationType 分组 + const groupedByType = new Map(); + for (const reward of expiredRewards) { + const type = reward.allocationType; + const existing = groupedByType.get(type) || { amount: 0, count: 0, orderIds: [] }; + existing.amount += reward.usdtAmount.value; + existing.count += 1; + existing.orderIds.push(reward.sourceOrderId); + groupedByType.set(type, existing); + } + + // 为每种类型创建单独的流水记录 + for (const [allocationType, data] of groupedByType) { + const ledgerEntry = LedgerEntry.create({ + accountSequence: headquartersAccountSequence, + userId: hqWallet.userId, + entryType: LedgerEntryType.SYSTEM_ALLOCATION, + amount: Money.USDT(data.amount), + memo: `过期${this.getAllocationTypeLabel(allocationType)}收入 (${data.count}笔)`, + payloadJson: { + allocationType, + expiredCount: data.count, + sourceOrderIds: data.orderIds.slice(0, 10), // 只保留前10个订单号 + }, + }); + await this.ledgerRepo.save(ledgerEntry); + + this.logger.log( + `[processExpiredRewards] Recorded ${allocationType}: ${data.amount} USDT from ${data.count} expired rewards`, + ); + } this.logger.log( `[processExpiredRewards] Transferred ${totalExpiredUsdt} USDT to headquarters from ${expiredRewards.length} expired rewards`, @@ -2396,6 +2420,22 @@ export class WalletApplicationService { }; } + /** + * 获取分配类型的中文标签 + * [2026-01-07] 新增:用于过期奖励流水备注 + */ + private getAllocationTypeLabel(allocationType: string): string { + const labels: Record = { + SHARE_RIGHT: '分享权益', + PROVINCE_TEAM_RIGHT: '省团队权益', + CITY_TEAM_RIGHT: '市团队权益', + PROVINCE_AREA_RIGHT: '省区域权益', + CITY_AREA_RIGHT: '市区域权益', + COMMUNITY_RIGHT: '社区权益', + }; + return labels[allocationType] || allocationType; + } + // =============== Ledger Statistics =============== /**