Files
break c7f9e005af
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 1h43m42s
feat(业务用户组): AUG26-003 业务用户组与店铺负责人分组导入
- 迁移 000221:新增 tb_business_user_group、tb_business_user_group_member、tb_shop_business_owner_import_task,成员一账号一行由部分唯一索引保证,店铺所属组按当前负责人实时推导,不回填历史分组。
- 用户组 CRUD、成员改组/清空归属、店铺批量交接(原子失败不部分写入)。
- 店铺负责人 CSV 导入任务:逐行独立事务、逐行明细、任务级与行级失败分离。
- 读侧推导与筛选:未分组、业务线、停用组可筛出并带停用标记。
- 补齐操作审计动作与资源、openapi 清单、发布门禁巡检表清单。
- 归档 add-shop-salesperson-groups 变更并同步 openspec/specs/business-user-group,补齐 AUG26-003 验证证据链。
2026-09-14 16:51:44 +08:00

117 lines
4.1 KiB
Go

// Package businessusergroup 提供业务用户组及其成员规模的只读投影。
// Query 只做筛选、分页与 DTO 投影,不修改任何状态。
package businessusergroup
import (
"context"
"strings"
"time"
"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/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
)
// Query 查询业务用户组。
type Query struct {
db *gorm.DB
store *postgres.BusinessUserGroupStore
}
// NewQuery 创建业务用户组查询。
func NewQuery(db *gorm.DB, groupStore *postgres.BusinessUserGroupStore) *Query {
return &Query{db: db, store: groupStore}
}
// List 分页查询业务用户组,供维护与筛选下拉使用。
func (q *Query) List(ctx context.Context, request dto.BusinessUserGroupListRequest) (*dto.BusinessUserGroupPageResult, error) {
if q == nil || q.db == nil {
return nil, errors.New(errors.CodeServiceUnavailable, "业务用户组查询尚未配置")
}
page, pageSize := normalizePage(request.Page, request.PageSize)
status := enabledToStatus(request.Enabled)
query := q.db.WithContext(ctx).Model(&model.BusinessUserGroup{})
if status != nil {
query = query.Where("status = ?", *status)
}
if request.BusinessLine != nil {
query = query.Where("business_line = ?", *request.BusinessLine)
}
if keyword := strings.TrimSpace(request.Keyword); keyword != "" {
like := "%" + keyword + "%"
query = query.Where("name LIKE ? OR code LIKE ?", like, like)
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询业务用户组总数失败")
}
var groups []*model.BusinessUserGroup
if err := query.Order("sort_order ASC, id ASC").
Offset((page - 1) * pageSize).Limit(pageSize).Find(&groups).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询业务用户组列表失败")
}
items := make([]*dto.BusinessUserGroupResponse, 0, len(groups))
for _, group := range groups {
items = append(items, toResponse(group))
}
return &dto.BusinessUserGroupPageResult{Items: items, Total: total, Page: page, Size: pageSize}, nil
}
// Detail 查询单个业务用户组详情。
func (q *Query) Detail(ctx context.Context, groupID uint) (*dto.BusinessUserGroupResponse, error) {
if q == nil || q.store == nil {
return nil, errors.New(errors.CodeServiceUnavailable, "业务用户组查询尚未配置")
}
if groupID == 0 {
return nil, errors.New(errors.CodeInvalidParam)
}
group, err := q.store.GetByID(ctx, groupID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeNotFound, "业务用户组不存在")
}
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询业务用户组失败")
}
return toResponse(group), nil
}
// toResponse 将业务用户组投影为对外响应。
func toResponse(group *model.BusinessUserGroup) *dto.BusinessUserGroupResponse {
return &dto.BusinessUserGroupResponse{
ID: group.ID, Code: group.Code, Name: group.Name,
BusinessLine: group.BusinessLine, BusinessLineName: constants.GetBusinessLineName(group.BusinessLine),
Sort: group.SortOrder, Enabled: group.Status == constants.StatusEnabled, Remark: group.Remark,
CreatedAt: group.CreatedAt.Format(time.RFC3339), UpdatedAt: group.UpdatedAt.Format(time.RFC3339),
}
}
// enabledToStatus 把对外启停布尔映射为既有状态值;未提交时返回 nil 表示不过滤。
func enabledToStatus(enabled *bool) *int {
if enabled == nil {
return nil
}
status := constants.StatusDisabled
if *enabled {
status = constants.StatusEnabled
}
return &status
}
// normalizePage 归一化分页参数,执行 DefaultPageSize 与 MaxPageSize 上限。
func normalizePage(page, pageSize int) (int, int) {
if page <= 0 {
page = constants.DefaultPage
}
if pageSize <= 0 {
pageSize = constants.DefaultPageSize
}
if pageSize > constants.MaxPageSize {
pageSize = constants.MaxPageSize
}
return page, pageSize
}