feat(ledger): 流水明细显示来源用户ID + 统计兼容历史批量转换数据

- wallet_service.dart: LedgerEntry 新增 sourceAccountFromMemo 从 memo 提取来源用户
- ledger_detail_page.dart: 流水列表项显示"来自 Dxxx"金色文字
- ledger_detail_page.dart: 权益详情弹窗添加来源用户行和备注行
- wallet-application.service.ts: 统计/趋势保留 memo 兼容历史批量转换记录

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-01 10:41:11 -08:00
parent d876dd1591
commit e4a2a0e37a
3 changed files with 30 additions and 3 deletions

View File

@ -2697,7 +2697,11 @@ export class WalletApplicationService {
const amount = Number(entry.amount); const amount = Number(entry.amount);
// 只计算非临时性流水的收支统计 // 只计算非临时性流水的收支统计
if (!excludeFromStats.has(entry.entryType)) { // 兼容历史数据:老的批量转换记录 entry_type 仍为 REWARD_TO_SETTLEABLE
// 通过 memo 中 "pending rewards settled" 识别并排除
const isExcluded = excludeFromStats.has(entry.entryType) ||
(entry.entryType === 'REWARD_TO_SETTLEABLE' && entry.memo?.includes('pending rewards settled'));
if (!isExcluded) {
if (amount > 0) { if (amount > 0) {
totalIncome += amount; totalIncome += amount;
} else { } else {
@ -2791,8 +2795,10 @@ export class WalletApplicationService {
const amount = Number(entry.amount); const amount = Number(entry.amount);
const existing = dailyMap.get(dateStr) || { income: 0, expense: 0, count: 0 }; const existing = dailyMap.get(dateStr) || { income: 0, expense: 0, count: 0 };
// 只计算非临时性流水的收支统计 // 只计算非临时性流水的收支统计(兼容历史批量转换数据)
if (!excludeFromStats.has(entry.entryType)) { const isExcluded = excludeFromStats.has(entry.entryType) ||
(entry.entryType === 'REWARD_TO_SETTLEABLE' && entry.memo?.includes('pending rewards settled'));
if (!isExcluded) {
if (amount > 0) { if (amount > 0) {
existing.income += amount; existing.income += amount;
periodIncome += amount; periodIncome += amount;

View File

@ -1522,6 +1522,13 @@ class LedgerEntry {
return baseName; return baseName;
} }
/// memo ID"来源: D26022600016"
String? get sourceAccountFromMemo {
if (memo == null) return null;
final match = RegExp(r'来源:\s*(D\d+)').firstMatch(memo!);
return match?.group(1);
}
/// ///
bool get isIncome => amount > 0; bool get isIncome => amount > 0;
} }

View File

@ -1002,6 +1002,16 @@ class _LedgerDetailPageState extends ConsumerState<LedgerDetailPage>
color: Color(0xFF5D4037), color: Color(0xFF5D4037),
), ),
), ),
if (entry.sourceAccountFromMemo != null) ...[
const SizedBox(height: 2),
Text(
'来自 ${entry.sourceAccountFromMemo}',
style: const TextStyle(
fontSize: 11,
color: Color(0xFFD4AF37),
),
),
],
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
_formatDate(entry.createdAt), _formatDate(entry.createdAt),
@ -1715,11 +1725,15 @@ class _RewardDetailSheet extends StatelessWidget {
), ),
_buildDetailRow('权益类型', entry.displayName), _buildDetailRow('权益类型', entry.displayName),
_buildDetailRow('流水类型', entry.entryTypeName), _buildDetailRow('流水类型', entry.entryTypeName),
if (entry.sourceAccountFromMemo != null)
_buildDetailRow('来源用户', entry.sourceAccountFromMemo!),
if (entry.balanceAfter != null) if (entry.balanceAfter != null)
_buildDetailRow('入账后余额', '${_formatAmount(entry.balanceAfter!)} 绿积分'), _buildDetailRow('入账后余额', '${_formatAmount(entry.balanceAfter!)} 绿积分'),
_buildDetailRow('入账时间', _formatDateTime(entry.createdAt)), _buildDetailRow('入账时间', _formatDateTime(entry.createdAt)),
if (entry.refOrderId != null && entry.refOrderId!.isNotEmpty) if (entry.refOrderId != null && entry.refOrderId!.isNotEmpty)
_buildDetailRow('关联订单', entry.refOrderId!), _buildDetailRow('关联订单', entry.refOrderId!),
if (entry.memo != null && entry.memo!.isNotEmpty)
_buildDetailRow('备注', entry.memo!),
], ],
), ),
), ),