新功能: withdrawal_log 接口加 summary 字段 + admin 禁直接扣减 commission
接口侧: - /v1/public/user/withdrawal_log 响应新增 summary 字段, 包含: - commission_balance (当前余额) - locked_by_pending (待审批占用) - available_to_withdraw (可提现) - total_historical_amount (已通过提现总额) - total_refunded_amount (退款回扣总额) - total_income_amount (收入总额) - 前端可据此自洽展示账目对账, 用户能在一个接口里看清整笔账 代码守护: - updateUserBasicInfoLogic 拒绝任何 change<0 的 commission 修改 - 扣减必须走 approveWithdrawal 写 type=334 日志 - 防止未来 admin 误操作再次造成 user.commission 与 system_logs 失衡 时间戳修复: - queryWithdrawalLogLogic 和 queryCommissionReturnLogLogic 的时间戳 从 UnixMilli 改回 Unix (秒级), 符合项目"后端统一秒级"约定 测试: - 补 buildSummary 的 4 个 mock 查询期望 - 加 summary 字段值正确性断言
This commit is contained in:
@@ -78,17 +78,72 @@ func (l *QueryWithdrawalLogLogic) queryWithdrawalLogs(userID int64, page, size i
|
||||
Method: row.Method,
|
||||
Account: row.Account,
|
||||
QrCodeUrl: row.QrCodeUrl,
|
||||
CreatedAt: row.CreatedAt.UnixMilli(),
|
||||
UpdatedAt: row.UpdatedAt.UnixMilli(),
|
||||
CreatedAt: row.CreatedAt.Unix(),
|
||||
UpdatedAt: row.UpdatedAt.Unix(),
|
||||
})
|
||||
}
|
||||
|
||||
summary, err := l.buildSummary(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.QueryWithdrawalLogListResponse{
|
||||
List: list,
|
||||
Total: total,
|
||||
List: list,
|
||||
Total: total,
|
||||
Summary: summary,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// buildSummary 聚合用户佣金账目快照,供前端展示闭环对账
|
||||
//
|
||||
// commission_balance = user.commission(当前余额,由 system_logs 累计而来)
|
||||
// locked_by_pending = 待审批提现占用
|
||||
// available_to_withdraw = balance - locked
|
||||
// total_historical_amount = 已通过提现累计(status=1)
|
||||
// total_refunded_amount = 佣金回扣累计(type=333/337/338)
|
||||
// total_income_amount = 佣金收入累计(type=331/332)
|
||||
func (l *QueryWithdrawalLogLogic) buildSummary(userID int64) (*types.WithdrawalLogSummary, error) {
|
||||
db := l.svcCtx.DB.WithContext(l.ctx)
|
||||
var summary types.WithdrawalLogSummary
|
||||
|
||||
if err := db.Model(&user.User{}).Where("id = ?", userID).
|
||||
Select("COALESCE(commission, 0)").Scan(&summary.CommissionBalance).Error; err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "load commission balance failed: %v", err)
|
||||
}
|
||||
|
||||
if err := db.Model(&user.Withdrawal{}).
|
||||
Where("user_id = ? AND status = ?", userID, user.WithdrawalStatusPending).
|
||||
Select("COALESCE(SUM(amount), 0)").Scan(&summary.LockedByPending).Error; err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "sum pending withdrawals failed: %v", err)
|
||||
}
|
||||
|
||||
if err := db.Model(&user.Withdrawal{}).
|
||||
Where("user_id = ? AND status = ?", userID, user.WithdrawalStatusApproved).
|
||||
Select("COALESCE(SUM(amount), 0)").Scan(&summary.TotalHistoricalAmount).Error; err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "sum approved withdrawals failed: %v", err)
|
||||
}
|
||||
|
||||
row := db.Raw(`
|
||||
SELECT
|
||||
COALESCE(SUM(CASE WHEN CAST(JSON_EXTRACT(content,'$.type') AS UNSIGNED) IN (?,?)
|
||||
THEN CAST(JSON_EXTRACT(content,'$.amount') AS SIGNED) ELSE 0 END), 0) AS income,
|
||||
COALESCE(SUM(CASE WHEN CAST(JSON_EXTRACT(content,'$.type') AS UNSIGNED) IN (?,?,?)
|
||||
THEN CAST(JSON_EXTRACT(content,'$.amount') AS SIGNED) ELSE 0 END), 0) AS refund
|
||||
FROM system_logs
|
||||
WHERE type = ? AND object_id = ?`,
|
||||
log.CommissionTypePurchase, log.CommissionTypeRenewal,
|
||||
log.CommissionTypeRefund, log.CommissionTypeWithdrawReject, log.CommissionTypeWithdrawCancel,
|
||||
log.TypeCommission.Uint8(), userID,
|
||||
).Row()
|
||||
if err := row.Scan(&summary.TotalIncomeAmount, &summary.TotalRefundedAmount); err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "aggregate commission logs failed: %v", err)
|
||||
}
|
||||
|
||||
summary.AvailableToWithdraw = summary.CommissionBalance - summary.LockedByPending
|
||||
return &summary, nil
|
||||
}
|
||||
|
||||
func (l *QueryWithdrawalLogLogic) queryCommissionRefundLogs(userID int64, page, size int) (*types.QueryWithdrawalLogListResponse, error) {
|
||||
queryLogic := NewQueryCommissionReturnLogLogic(l.ctx, l.svcCtx)
|
||||
rows, total, err := queryLogic.queryCommissionReturnLogRecordsByEventTypes(userID, page, size, log.CommissionTypeRefund)
|
||||
|
||||
Reference in New Issue
Block a user