package shop_commission import ( "context" "encoding/json" "fmt" "time" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/model/dto" "github.com/break/junhong_cmp_fiber/internal/store" "github.com/break/junhong_cmp_fiber/internal/store/postgres" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" ) type Service struct { shopStore *postgres.ShopStore accountStore *postgres.AccountStore walletStore *postgres.WalletStore commissionWithdrawalReqStore *postgres.CommissionWithdrawalRequestStore commissionRecordStore *postgres.CommissionRecordStore } func New( shopStore *postgres.ShopStore, accountStore *postgres.AccountStore, walletStore *postgres.WalletStore, commissionWithdrawalReqStore *postgres.CommissionWithdrawalRequestStore, commissionRecordStore *postgres.CommissionRecordStore, ) *Service { return &Service{ shopStore: shopStore, accountStore: accountStore, walletStore: walletStore, commissionWithdrawalReqStore: commissionWithdrawalReqStore, commissionRecordStore: commissionRecordStore, } } func (s *Service) ListShopCommissionSummary(ctx context.Context, req *dto.ShopCommissionSummaryListReq) (*dto.ShopCommissionSummaryPageResult, error) { opts := &store.QueryOptions{ Page: req.Page, PageSize: req.PageSize, OrderBy: "created_at DESC", } if opts.Page == 0 { opts.Page = 1 } if opts.PageSize == 0 { opts.PageSize = constants.DefaultPageSize } filters := make(map[string]interface{}) if req.ShopName != "" { filters["shop_name"] = req.ShopName } shops, total, err := s.shopStore.List(ctx, opts, filters) if err != nil { return nil, fmt.Errorf("查询店铺列表失败: %w", err) } if len(shops) == 0 { return &dto.ShopCommissionSummaryPageResult{ Items: []dto.ShopCommissionSummaryItem{}, Total: 0, Page: opts.Page, Size: opts.PageSize, }, nil } shopIDs := make([]uint, 0, len(shops)) for _, shop := range shops { shopIDs = append(shopIDs, shop.ID) } walletSummaries, err := s.walletStore.GetShopCommissionSummaryBatch(ctx, shopIDs) if err != nil { return nil, fmt.Errorf("查询店铺钱包汇总失败: %w", err) } withdrawnAmounts, err := s.commissionWithdrawalReqStore.SumAmountByShopIDsAndStatus(ctx, shopIDs, constants.WithdrawalStatusApproved) if err != nil { return nil, fmt.Errorf("查询已提现金额失败: %w", err) } withdrawingAmounts, err := s.commissionWithdrawalReqStore.SumAmountByShopIDsAndStatus(ctx, shopIDs, constants.WithdrawalStatusPending) if err != nil { return nil, fmt.Errorf("查询提现中金额失败: %w", err) } primaryAccounts, err := s.accountStore.GetPrimaryAccountsByShopIDs(ctx, shopIDs) if err != nil { return nil, fmt.Errorf("查询主账号失败: %w", err) } accountMap := make(map[uint]*model.Account) for _, acc := range primaryAccounts { if acc.ShopID != nil { accountMap[*acc.ShopID] = acc } } items := make([]dto.ShopCommissionSummaryItem, 0, len(shops)) for _, shop := range shops { if req.Username != "" { acc := accountMap[shop.ID] if acc == nil || !containsSubstring(acc.Username, req.Username) { total-- continue } } item := s.buildCommissionSummaryItem(shop, walletSummaries[shop.ID], withdrawnAmounts[shop.ID], withdrawingAmounts[shop.ID], accountMap[shop.ID]) items = append(items, item) } return &dto.ShopCommissionSummaryPageResult{ Items: items, Total: total, Page: opts.Page, Size: opts.PageSize, }, nil } func (s *Service) buildCommissionSummaryItem(shop *model.Shop, walletSummary *postgres.ShopCommissionSummary, withdrawnAmount, withdrawingAmount int64, account *model.Account) dto.ShopCommissionSummaryItem { var balance, frozenBalance int64 if walletSummary != nil { balance = walletSummary.Balance frozenBalance = walletSummary.FrozenBalance } totalCommission := balance + frozenBalance + withdrawnAmount unwithdrawCommission := totalCommission - withdrawnAmount availableCommission := balance - withdrawingAmount if availableCommission < 0 { availableCommission = 0 } var username, phone string if account != nil { username = account.Username phone = account.Phone } return dto.ShopCommissionSummaryItem{ ShopID: shop.ID, ShopName: shop.ShopName, ShopCode: shop.ShopCode, Username: username, Phone: phone, TotalCommission: totalCommission, WithdrawnCommission: withdrawnAmount, UnwithdrawCommission: unwithdrawCommission, FrozenCommission: frozenBalance, WithdrawingCommission: withdrawingAmount, AvailableCommission: availableCommission, CreatedAt: shop.CreatedAt.Format("2006-01-02 15:04:05"), } } func (s *Service) ListShopWithdrawalRequests(ctx context.Context, shopID uint, req *dto.ShopWithdrawalRequestListReq) (*dto.ShopWithdrawalRequestPageResult, error) { _, err := s.shopStore.GetByID(ctx, shopID) if err != nil { return nil, errors.New(errors.CodeShopNotFound, "店铺不存在") } opts := &store.QueryOptions{ Page: req.Page, PageSize: req.PageSize, OrderBy: "created_at DESC", } if opts.Page == 0 { opts.Page = 1 } if opts.PageSize == 0 { opts.PageSize = constants.DefaultPageSize } filters := &postgres.WithdrawalRequestListFilters{ ShopID: shopID, WithdrawalNo: req.WithdrawalNo, } if req.StartTime != "" { t, err := time.Parse("2006-01-02 15:04:05", req.StartTime) if err == nil { filters.StartTime = &t } } if req.EndTime != "" { t, err := time.Parse("2006-01-02 15:04:05", req.EndTime) if err == nil { filters.EndTime = &t } } requests, total, err := s.commissionWithdrawalReqStore.ListByShopID(ctx, opts, filters) if err != nil { return nil, fmt.Errorf("查询提现记录失败: %w", err) } shop, _ := s.shopStore.GetByID(ctx, shopID) shopHierarchy := s.buildShopHierarchyPath(ctx, shop) applicantIDs := make([]uint, 0) processorIDs := make([]uint, 0) for _, r := range requests { if r.ApplicantID > 0 { applicantIDs = append(applicantIDs, r.ApplicantID) } if r.ProcessorID > 0 { processorIDs = append(processorIDs, r.ProcessorID) } } applicantMap := make(map[uint]string) processorMap := make(map[uint]string) if len(applicantIDs) > 0 { accounts, _ := s.accountStore.GetByIDs(ctx, applicantIDs) for _, acc := range accounts { applicantMap[acc.ID] = acc.Username } } if len(processorIDs) > 0 { accounts, _ := s.accountStore.GetByIDs(ctx, processorIDs) for _, acc := range accounts { processorMap[acc.ID] = acc.Username } } items := make([]dto.ShopWithdrawalRequestItem, 0, len(requests)) for _, r := range requests { item := s.buildWithdrawalRequestItem(r, shop.ShopName, shopHierarchy, applicantMap, processorMap) items = append(items, item) } return &dto.ShopWithdrawalRequestPageResult{ Items: items, Total: total, Page: opts.Page, Size: opts.PageSize, }, nil } func (s *Service) buildWithdrawalRequestItem(r *model.CommissionWithdrawalRequest, shopName, shopHierarchy string, applicantMap, processorMap map[uint]string) dto.ShopWithdrawalRequestItem { var processorID *uint if r.ProcessorID > 0 { processorID = &r.ProcessorID } var accountName, accountNumber, bankName string if len(r.AccountInfo) > 0 { var info map[string]interface{} if err := json.Unmarshal(r.AccountInfo, &info); err == nil { if v, ok := info["account_name"].(string); ok { accountName = v } if v, ok := info["account_number"].(string); ok { accountNumber = v } if v, ok := info["bank_name"].(string); ok { bankName = v } } } var processedAt, paidAt string if r.ProcessedAt != nil { processedAt = r.ProcessedAt.Format("2006-01-02 15:04:05") } if r.PaidAt != nil { paidAt = r.PaidAt.Format("2006-01-02 15:04:05") } return dto.ShopWithdrawalRequestItem{ ID: r.ID, WithdrawalNo: r.WithdrawalNo, Amount: r.Amount, FeeRate: r.FeeRate, Fee: r.Fee, ActualAmount: r.ActualAmount, Status: r.Status, StatusName: getWithdrawalStatusName(r.Status), ShopID: r.ShopID, ShopName: shopName, ShopHierarchy: shopHierarchy, ApplicantID: r.ApplicantID, ApplicantName: applicantMap[r.ApplicantID], ProcessorID: processorID, ProcessorName: processorMap[r.ProcessorID], WithdrawalMethod: r.WithdrawalMethod, PaymentType: r.PaymentType, AccountName: accountName, AccountNumber: accountNumber, BankName: bankName, RejectReason: r.RejectReason, Remark: r.Remark, CreatedAt: r.CreatedAt.Format("2006-01-02 15:04:05"), ProcessedAt: processedAt, PaidAt: paidAt, } } func (s *Service) buildShopHierarchyPath(ctx context.Context, shop *model.Shop) string { if shop == nil { return "" } path := shop.ShopName current := shop depth := 0 for current.ParentID != nil && depth < 2 { parent, err := s.shopStore.GetByID(ctx, *current.ParentID) if err != nil { break } path = parent.ShopName + "_" + path current = parent depth++ } return path } func (s *Service) ListShopCommissionRecords(ctx context.Context, shopID uint, req *dto.ShopCommissionRecordListReq) (*dto.ShopCommissionRecordPageResult, error) { _, err := s.shopStore.GetByID(ctx, shopID) if err != nil { return nil, errors.New(errors.CodeShopNotFound, "店铺不存在") } opts := &store.QueryOptions{ Page: req.Page, PageSize: req.PageSize, OrderBy: "created_at DESC", } if opts.Page == 0 { opts.Page = 1 } if opts.PageSize == 0 { opts.PageSize = constants.DefaultPageSize } filters := &postgres.CommissionRecordListFilters{ ShopID: shopID, CommissionSource: req.CommissionSource, ICCID: req.ICCID, DeviceNo: req.DeviceNo, OrderNo: req.OrderNo, } records, total, err := s.commissionRecordStore.ListByShopID(ctx, opts, filters) if err != nil { return nil, fmt.Errorf("查询佣金明细失败: %w", err) } items := make([]dto.ShopCommissionRecordItem, 0, len(records)) for _, r := range records { item := dto.ShopCommissionRecordItem{ ID: r.ID, Amount: r.Amount, BalanceAfter: r.BalanceAfter, CommissionSource: r.CommissionSource, Status: r.Status, StatusName: getCommissionStatusName(r.Status), OrderID: r.OrderID, OrderNo: "", DeviceNo: "", ICCID: "", OrderCreatedAt: "", CreatedAt: r.CreatedAt.Format("2006-01-02 15:04:05"), } items = append(items, item) } return &dto.ShopCommissionRecordPageResult{ Items: items, Total: total, Page: opts.Page, Size: opts.PageSize, }, nil } func getWithdrawalStatusName(status int) string { switch status { case constants.WithdrawalStatusPending: return "待审核" case constants.WithdrawalStatusApproved: return "已通过" case constants.WithdrawalStatusRejected: return "已拒绝" case constants.WithdrawalStatusPaid: return "已到账" default: return "未知" } } func getCommissionStatusName(status int) string { switch status { case constants.CommissionStatusFrozen: return "已冻结" case constants.CommissionStatusUnfreezing: return "解冻中" case constants.CommissionStatusReleased: return "已发放" case constants.CommissionStatusInvalid: return "已失效" default: return "未知" } } func containsSubstring(s, substr string) bool { return len(s) >= len(substr) && (s == substr || len(substr) == 0 || (len(s) > 0 && len(substr) > 0 && contains(s, substr))) } func contains(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false }