// Package shop 提供店铺业务员归属与资金概况读取投影。 package shop import ( "context" "strings" "gorm.io/gorm" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/model/dto" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" "github.com/break/junhong_cmp_fiber/pkg/middleware" ) // BusinessOwnerQuery 提供店铺业务员归属读取能力。 type BusinessOwnerQuery struct { db *gorm.DB } // NewBusinessOwnerQuery 创建店铺业务员归属 Query。 func NewBusinessOwnerQuery(db *gorm.DB) *BusinessOwnerQuery { return &BusinessOwnerQuery{db: db} } // List 查询调用者数据范围内的店铺,并批量投影上级和业务员摘要。 func (q *BusinessOwnerQuery) List(ctx context.Context, request dto.ShopListRequest) ([]*dto.ShopResponse, int64, error) { base := middleware.ApplyShopIDFilter(ctx, q.db.WithContext(ctx).Model(&model.Shop{})) base = applyShopFilters(base, request) var total int64 if err := base.Count(&total).Error; err != nil { return nil, 0, errors.Wrap(errors.CodeDatabaseError, err, "查询店铺总数失败") } var shops []*model.Shop offset := (request.Page - 1) * request.PageSize if err := base.Order("created_at DESC, id DESC").Offset(offset).Limit(request.PageSize).Find(&shops).Error; err != nil { return nil, 0, errors.Wrap(errors.CodeDatabaseError, err, "查询店铺列表失败") } responses, err := q.project(ctx, shops) if err != nil { return nil, 0, err } return responses, total, nil } // Detail 查询调用者数据范围内的一家店铺详情。 func (q *BusinessOwnerQuery) Detail(ctx context.Context, shopID uint) (*dto.ShopResponse, error) { if shopID == 0 { return nil, errors.New(errors.CodeInvalidParam) } var shop model.Shop db := middleware.ApplyShopIDFilter(ctx, q.db.WithContext(ctx).Model(&model.Shop{})) if err := db.Where("id = ?", shopID).First(&shop).Error; err != nil { return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在") } responses, err := q.project(ctx, []*model.Shop{&shop}) if err != nil { return nil, err } return responses[0], nil } // Candidates 查询当前可人工绑定的平台业务员最小投影。 func (q *BusinessOwnerQuery) Candidates(ctx context.Context, request dto.ShopBusinessOwnerCandidateRequest) ([]dto.ShopBusinessOwnerCandidate, int64, int, int, error) { userType := middleware.GetUserTypeFromContext(ctx) if userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform { return nil, 0, 0, 0, errors.New(errors.CodeForbidden, "无权限查询业务员候选") } page, pageSize := request.Page, request.PageSize if page == 0 { page = constants.DefaultPage } if pageSize == 0 { pageSize = constants.DefaultPageSize } base := q.db.WithContext(ctx).Model(&model.Account{}). Where("user_type = ? AND status = ?", constants.UserTypePlatform, constants.StatusEnabled) if request.Keyword != "" { keyword := "%" + request.Keyword + "%" base = base.Where("username ILIKE ? OR phone ILIKE ?", keyword, keyword) } var total int64 if err := base.Count(&total).Error; err != nil { return nil, 0, 0, 0, errors.Wrap(errors.CodeDatabaseError, err, "查询业务员候选总数失败") } var accounts []model.Account if err := base.Order("id ASC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&accounts).Error; err != nil { return nil, 0, 0, 0, errors.Wrap(errors.CodeDatabaseError, err, "查询业务员候选失败") } items := make([]dto.ShopBusinessOwnerCandidate, 0, len(accounts)) for _, account := range accounts { items = append(items, dto.ShopBusinessOwnerCandidate{ ID: account.ID, Username: account.Username, PhoneSummary: maskPhone(account.Phone), }) } return items, total, page, pageSize, nil } func applyShopFilters(db *gorm.DB, request dto.ShopListRequest) *gorm.DB { if request.ShopName != "" { db = db.Where("shop_name LIKE ?", "%"+request.ShopName+"%") } if request.ShopCode != "" { db = db.Where("shop_code = ?", request.ShopCode) } if request.ContactPhone != "" { db = db.Where("contact_phone = ?", request.ContactPhone) } if request.BusinessOwnerAccountID != nil { db = db.Where("business_owner_account_id = ?", *request.BusinessOwnerAccountID) } if request.ParentID != nil { db = db.Where("parent_id = ?", *request.ParentID) } if request.Level != nil { db = db.Where("level = ?", *request.Level) } if request.Status != nil { db = db.Where("status = ?", *request.Status) } return db } func (q *BusinessOwnerQuery) project(ctx context.Context, shops []*model.Shop) ([]*dto.ShopResponse, error) { parentIDs, ownerIDs := collectProjectionIDs(shops) parentNames, err := q.loadParentNames(ctx, parentIDs) if err != nil { return nil, err } owners, err := q.loadBusinessOwners(ctx, ownerIDs) if err != nil { return nil, err } responses := make([]*dto.ShopResponse, 0, len(shops)) for _, shop := range shops { response := &dto.ShopResponse{ ID: shop.ID, ShopName: shop.ShopName, ShopCode: shop.ShopCode, ParentID: shop.ParentID, BusinessOwnerAccountID: shop.BusinessOwnerAccountID, Level: shop.Level, ContactName: shop.ContactName, ContactPhone: shop.ContactPhone, Province: shop.Province, City: shop.City, District: shop.District, Address: shop.Address, Status: shop.Status, ClientLoginDisabled: shop.ClientLoginDisabled, StatusName: constants.GetStatusName(shop.Status), CreatedAt: shop.CreatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: shop.UpdatedAt.Format("2006-01-02 15:04:05"), } if shop.ParentID != nil { response.ParentShopName = parentNames[*shop.ParentID] } if shop.BusinessOwnerAccountID != nil { if owner, exists := owners[*shop.BusinessOwnerAccountID]; exists { response.BusinessOwnerUsername = owner.Username response.BusinessOwnerPhoneSummary = maskPhone(owner.Phone) response.BusinessOwnerAvailable = owner.UserType == constants.UserTypePlatform && owner.Status == constants.StatusEnabled && !owner.DeletedAt.Valid } } responses = append(responses, response) } return responses, nil } func collectProjectionIDs(shops []*model.Shop) ([]uint, []uint) { parents := make(map[uint]struct{}) owners := make(map[uint]struct{}) for _, shop := range shops { if shop.ParentID != nil { parents[*shop.ParentID] = struct{}{} } if shop.BusinessOwnerAccountID != nil { owners[*shop.BusinessOwnerAccountID] = struct{}{} } } return mapKeys(parents), mapKeys(owners) } func (q *BusinessOwnerQuery) loadParentNames(ctx context.Context, ids []uint) (map[uint]string, error) { result := make(map[uint]string, len(ids)) if len(ids) == 0 { return result, nil } var shops []model.Shop db := middleware.ApplyShopIDFilter(ctx, q.db.WithContext(ctx).Model(&model.Shop{})) if err := db.Select("id", "shop_name").Where("id IN ?", ids).Find(&shops).Error; err != nil { return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询上级店铺摘要失败") } for _, shop := range shops { result[shop.ID] = shop.ShopName } return result, nil } func (q *BusinessOwnerQuery) loadBusinessOwners(ctx context.Context, ids []uint) (map[uint]model.Account, error) { result := make(map[uint]model.Account, len(ids)) if len(ids) == 0 { return result, nil } var accounts []model.Account if err := q.db.WithContext(ctx).Unscoped().Where("id IN ?", ids).Find(&accounts).Error; err != nil { return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询业务员摘要失败") } for _, account := range accounts { result[account.ID] = account } return result, nil } func mapKeys(values map[uint]struct{}) []uint { keys := make([]uint, 0, len(values)) for key := range values { keys = append(keys, key) } return keys } func maskPhone(phone string) string { phone = strings.TrimSpace(phone) if len(phone) < 7 { return "" } return phone[:3] + "****" + phone[len(phone)-4:] }