package withdrawal import ( "context" "encoding/json" "net/url" "strings" "hfb_sys/backend/internal/model" ) // 转换为用户DTO func toDTO(w model.WithdrawalRequest) WithdrawalDTO { return WithdrawalDTO{ ID: w.ID, WithdrawNo: w.WithdrawNo, UserID: w.UserID, DistributionSiteID: w.DistributionSiteID, AmountCent: w.AmountCent, FeeCent: w.FeeCent, ActualAmountCent: w.ActualAmountCent, AccountType: w.AccountType, AccountName: w.AccountName, AccountNo: w.AccountNo, BankName: w.BankName, Status: w.Status, ReviewRemark: w.ReviewRemark, CreatedAt: w.CreatedAt, UpdatedAt: w.UpdatedAt, ReviewedAt: w.ReviewedAt, PaidAt: w.PaidAt, } } // toAdminListItems 批量装配提现分页列表依赖的用户和站点信息,避免每条记录分别查库。 func (r *Repository) toAdminListItems(ctx context.Context, withdrawals []model.WithdrawalRequest) ([]WithdrawalListItemDTO, error) { items := make([]WithdrawalListItemDTO, 0, len(withdrawals)) if len(withdrawals) == 0 { return items, nil } userIDs := make([]uint64, 0, len(withdrawals)) siteIDs := make([]uint64, 0, len(withdrawals)) seenUsers := make(map[uint64]struct{}, len(withdrawals)) seenSites := make(map[uint64]struct{}, len(withdrawals)) for _, withdrawal := range withdrawals { if _, ok := seenUsers[withdrawal.UserID]; !ok { seenUsers[withdrawal.UserID] = struct{}{} userIDs = append(userIDs, withdrawal.UserID) } if withdrawal.DistributionSiteID > 0 { if _, ok := seenSites[withdrawal.DistributionSiteID]; !ok { seenSites[withdrawal.DistributionSiteID] = struct{}{} siteIDs = append(siteIDs, withdrawal.DistributionSiteID) } } } usersByID := make(map[uint64]model.User, len(userIDs)) var users []model.User if err := r.db.WithContext(ctx).Select("id, nickname, phone").Where("id IN ?", userIDs).Find(&users).Error; err != nil { return nil, err } for _, user := range users { usersByID[user.ID] = user } sitesByID := make(map[uint64]model.DistributionSite, len(siteIDs)) if len(siteIDs) > 0 { var sites []model.DistributionSite if err := r.db.WithContext(ctx).Select("id, site_name").Where("id IN ?", siteIDs).Find(&sites).Error; err != nil { return nil, err } for _, site := range sites { sitesByID[site.ID] = site } } for _, withdrawal := range withdrawals { user := usersByID[withdrawal.UserID] site := sitesByID[withdrawal.DistributionSiteID] items = append(items, WithdrawalListItemDTO{ ID: withdrawal.ID, WithdrawNo: withdrawal.WithdrawNo, UserID: withdrawal.UserID, DistributionSiteID: withdrawal.DistributionSiteID, DistributionSiteName: site.SiteName, UserNickname: user.Nickname, UserPhone: user.Phone, AmountCent: withdrawal.AmountCent, FeeCent: withdrawal.FeeCent, ActualAmountCent: withdrawal.ActualAmountCent, AccountType: withdrawal.AccountType, AccountName: withdrawal.AccountName, AccountNo: withdrawal.AccountNo, BankName: withdrawal.BankName, Status: withdrawal.Status, CreatedAt: withdrawal.CreatedAt, }) } return items, nil } // 转换为管理员详细DTO // 转换为管理员详细DTO func (r *Repository) toDetailDTO(ctx context.Context, w model.WithdrawalRequest) (*WithdrawalDetailDTO, error) { db := r.db.WithContext(ctx) // 查询用户信息 var user model.User db.Select("nickname, phone").First(&user, w.UserID) // 查询审核人信息 var reviewedByName string if w.ReviewedBy != nil { var admin model.AdminUser if err := db.Select("nickname").First(&admin, *w.ReviewedBy).Error; err == nil { reviewedByName = admin.Nickname } } // 查询打款人信息 var paidByName string if w.PaidBy != nil { var admin model.AdminUser if err := db.Select("nickname").First(&admin, *w.PaidBy).Error; err == nil { paidByName = admin.Nickname } } // 获取完整账号和收款二维码(管理员可见) fullAccountNo := w.AccountNo var certificateURLs []string if w.PaymentAccountID != nil { var paymentAccount model.UserPaymentAccount if err := db.First(&paymentAccount, *w.PaymentAccountID).Error; err == nil { // 解密账号 decrypted, err := r.encryptor.Decrypt(paymentAccount.AccountNo) if err == nil { fullAccountNo = decrypted } // 解析收款二维码 if paymentAccount.CertificateURLs != nil { json.Unmarshal(paymentAccount.CertificateURLs, &certificateURLs) } certificateURLs = privateCertificateURLs(certificateURLs) } } var siteName, siteCode string if w.DistributionSiteID > 0 { var site model.DistributionSite if err := db.Select("site_name, site_code").First(&site, w.DistributionSiteID).Error; err == nil { siteName = site.SiteName siteCode = site.SiteCode } } return &WithdrawalDetailDTO{ ID: w.ID, WithdrawNo: w.WithdrawNo, UserID: w.UserID, DistributionSiteID: w.DistributionSiteID, DistributionSiteName: siteName, DistributionSiteCode: siteCode, UserNickname: user.Nickname, UserPhone: user.Phone, AmountCent: w.AmountCent, FeeCent: w.FeeCent, ActualAmountCent: w.ActualAmountCent, PaymentAccountID: w.PaymentAccountID, AccountType: w.AccountType, AccountName: w.AccountName, AccountNo: fullAccountNo, BankName: w.BankName, BankBranch: w.BankBranch, CertificateURLs: certificateURLs, Status: w.Status, ReviewedBy: w.ReviewedBy, ReviewedByName: reviewedByName, ReviewedAt: w.ReviewedAt, ReviewRemark: w.ReviewRemark, PaidBy: w.PaidBy, PaidByName: paidByName, PaidAt: w.PaidAt, PaymentProofURL: w.PaymentProofURL, PaymentRemark: w.PaymentRemark, CreatedAt: w.CreatedAt, UpdatedAt: w.UpdatedAt, }, nil } // privateCertificateURLs 兼容历史公开收款凭证 URL,统一经私有对象接口读取。 func privateCertificateURLs(values []string) []string { for index, value := range values { parsed, err := url.Parse(value) if err != nil || parsed.Path != "/api/public/files/object" { continue } if !strings.HasPrefix(parsed.Query().Get("key"), "payment-cert/") { continue } parsed.Path = "/api/files/object" values[index] = parsed.String() } return values }