Files
junhong_cmp_fiber/internal/routes/business_user_group.go
break 22c3e7cc1a
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m42s
实施业务用户组成员管理
2026-09-21 21:20:38 +08:00

178 lines
9.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package routes
import (
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/handler/admin"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/openapi"
)
// shopOwnerImportDoc 说明店铺负责人导入 CSV 的模板要求。
// 模板由前端提供,后端只描述固定列序、取值与编码要求,不提供模板下载端点与模板资源。
const shopOwnerImportDoc = `仅超级管理员和平台账号可操作,代理与企业账号返回 403。
### 完整导入流程
1. **获取上传 URL**:调用 ` + "`POST /api/admin/storage/upload-url`" + `purpose 传 ` + "`shop_import`" + `
2. **上传 CSV**:使用返回的预签名 URL 上传文件到对象存储
3. **调用本接口**:使用返回的 ` + "`file_key`" + ` 创建导入任务(必须以 ` + "`shop-imports/`" + ` 开头且扩展名为 ` + "`.csv`" + `
### 模板列序(首行表头必须完全一致)
` + "`店铺编码`" + `` + "`操作类型`" + `` + "`业务员登录账号`" + `` + "`备注`" + `
- 店铺编码:以店铺编号唯一定位店铺;不存在或已删除时该行失败
- 操作类型:取值仅 ` + "`换绑`" + `` + "`清空`" + `;换绑必须填写业务员登录账号,清空不得填写
- 业务员登录账号:以登录账号唯一定位业务员;必须是启用平台用户
- 备注:可选,填写时写入该行审计
### 编码要求
文件编码为 UTF-8可带 BOM非 UTF-8 时按 GBK 尝试解码,仍失败时按任务级失败并给出明确原因。
### 执行语义
每行独立校验与执行:有效行成功更新,失败行保留原值且不影响其他已成功行。结果返回行号(自数据首行起计,表头不计入)、成功或失败状态与失败原因。表头或编码不符为任务级失败,不产生行明细。不设行数硬上限。`
// registerBusinessUserGroupRoutes 注册业务用户组、成员维护与店铺负责人批量交接路由。
// 入口只读认证上下文判断身份,代理与企业一律 403既有单店行为不变。
func registerBusinessUserGroupRoutes(router fiber.Router, handler *admin.BusinessUserGroupHandler, doc *openapi.Generator, basePath string) {
groups := router.Group("/business-user-groups")
groupPath := basePath + "/business-user-groups"
Register(groups, doc, groupPath, "POST", "", handler.Create, RouteSpec{
Summary: "创建业务用户组",
Description: "仅超级管理员和平台账号可操作。稳定编码创建时必填、未删除组内唯一且创建后不可修改;用户组不设上级、层级与组管理员,不改变角色权限与数据范围。",
Tags: []string{"业务用户组"},
Input: new(dto.CreateBusinessUserGroupRequest),
Output: new(dto.BusinessUserGroupResponse),
Auth: true,
})
Register(groups, doc, groupPath, "GET", "", handler.List, RouteSpec{
Summary: "查询业务用户组列表",
Description: "仅超级管理员和平台账号可操作。分页默认第 1 页、每页 20 条,最大 100返回组字段与业务线供维护与筛选下拉使用。",
Tags: []string{"业务用户组"},
Input: new(dto.BusinessUserGroupListRequest),
Output: new(dto.BusinessUserGroupPageResult),
Auth: true,
})
// 成员查询路径必须先于动态组详情注册,避免动态参数吞掉子路径。
Register(groups, doc, groupPath, "GET", "/:id/members", handler.ListMembers, RouteSpec{
Summary: "查询业务用户组成员",
Description: "仅超级管理员和平台账号可操作。分页返回目标组保留的启用、禁用及已删除账号关系,支持用户名或脱敏手机号关键字及账号状态筛选。",
Tags: []string{"业务用户组"},
Input: new(dto.BusinessUserGroupMemberListParams),
Output: new(dto.BusinessUserGroupMemberPageResult),
Auth: true,
})
Register(groups, doc, groupPath, "GET", "/:id/member-candidates", handler.ListMemberCandidates, RouteSpec{
Summary: "查询业务用户组成员候选",
Description: "仅超级管理员和平台账号可操作。分页返回启用且未删除的平台用户及当前归属,手机号仅返回脱敏值;支持全部、未分组、当前组和其他组筛选,停用目标组不可新增。",
Tags: []string{"业务用户组"},
Input: new(dto.BusinessUserGroupMemberCandidateParams),
Output: new(dto.BusinessUserGroupMemberCandidatePageResult),
Auth: true,
})
Register(groups, doc, groupPath, "GET", "/:id", handler.Detail, RouteSpec{
Summary: "查询业务用户组详情",
Description: "仅超级管理员和平台账号可操作。返回组字段含业务线,供维护与筛选下拉使用;不存在或已删除返回资源不存在。",
Tags: []string{"业务用户组"},
Input: new(dto.IDReq),
Output: new(dto.BusinessUserGroupResponse),
Auth: true,
})
Register(groups, doc, groupPath, "PUT", "/:id", handler.Update, RouteSpec{
Summary: "更新业务用户组",
Description: "仅超级管理员和平台账号可操作。允许更新名称、业务线、排序、启停与备注;稳定编码永不允许修改;不存在或已删除返回资源不存在。",
Tags: []string{"业务用户组"},
Input: new(dto.BusinessUserGroupUpdateParams),
Output: new(dto.BusinessUserGroupResponse),
Auth: true,
})
Register(groups, doc, groupPath, "DELETE", "/:id", handler.Delete, RouteSpec{
Summary: "删除业务用户组",
Description: "仅超级管理员和平台账号可操作,必须提交二次确认。仅无成员的用户组可删除;存在成员时返回“用户组仍有成员,只能停用或先移走成员”,不做物理删除。",
Tags: []string{"业务用户组"},
Input: new(dto.IDReq),
Body: new(dto.BusinessUserGroupDeleteRequest),
Output: nil,
Auth: true,
})
Register(groups, doc, groupPath, "POST", "/:id/members", handler.AddMembers, RouteSpec{
Summary: "增量增加业务用户组成员",
Description: "仅超级管理员和平台账号可操作。目标组必须启用;未分组账号新增、其他组账号直接迁入、当前组账号幂等不变;任一无效账号整批回滚。",
Tags: []string{"业务用户组"},
Input: new(dto.BusinessUserGroupMemberRequest),
Body: new(dto.AddBusinessUserGroupMembersRequest),
Output: new(dto.BusinessUserGroupMemberMutationResult),
Auth: true,
})
Register(groups, doc, groupPath, "DELETE", "/:id/members", handler.RemoveMembers, RouteSpec{
Summary: "增量移除业务用户组成员",
Description: "仅超级管理员和平台账号可操作。目标组启用或停用均可移除;请求账号必须全部属于目标组,禁用或已删除账号关系也可清理,任一不匹配整批冲突。",
Tags: []string{"业务用户组"},
Input: new(dto.BusinessUserGroupMemberRequest),
Body: new(dto.RemoveBusinessUserGroupMembersRequest),
Output: new(dto.BusinessUserGroupMemberMutationResult),
Auth: true,
})
}
// registerShopBusinessOwnerBatchRoute 注册勾选店铺批量设置或清空负责人路由。
func registerShopBusinessOwnerBatchRoute(router fiber.Router, handler *admin.BusinessUserGroupHandler, doc *openapi.Generator, basePath string) {
shops := router.Group("/shops")
groupPath := basePath + "/shops"
Register(shops, doc, groupPath, "PUT", "/business-owner/batch", handler.BatchUpdateShopBusinessOwner, RouteSpec{
Summary: "批量设置或清空店铺负责人",
Description: "仅超级管理员和平台账号可操作。提交前校验全部目标店铺均存在、未删除且可管理并校验目标业务员为启用平台用户任一项失败时整批不修改失败文案不区分无权、不存在与已删除。business_owner_account_id 缺失时请求非法,显式 null 表示清空负责人。成功时在同一事务内统一更新并为每家店铺记录负责人前后值、操作者、时间与入口审计,同时保留批次汇总结果。",
Tags: []string{"店铺管理"},
Input: new(dto.BatchUpdateShopBusinessOwnerRequest),
Output: new(dto.BatchUpdateShopBusinessOwnerResult),
Auth: true,
})
}
// registerShopBusinessOwnerImportRoutes 注册店铺负责人 CSV 导入任务路由。
func registerShopBusinessOwnerImportRoutes(router fiber.Router, handler *admin.ShopBusinessOwnerImportHandler, doc *openapi.Generator, basePath string) {
shops := router.Group("/shops")
groupPath := basePath + "/shops"
Register(shops, doc, groupPath, "POST", "/business-owner-imports", handler.Create, RouteSpec{
Summary: "创建店铺负责人 CSV 导入任务",
Description: shopOwnerImportDoc,
Tags: []string{"店铺负责人导入"},
Input: new(dto.CreateShopBusinessOwnerImportRequest),
Output: new(dto.ShopBusinessOwnerImportTaskResponse),
Auth: true,
})
Register(shops, doc, groupPath, "GET", "/business-owner-imports", handler.List, RouteSpec{
Summary: "查询店铺负责人导入任务列表",
Description: constants.ShopOwnerImportAccessDescription,
Tags: []string{"店铺负责人导入"},
Input: new(dto.ListShopBusinessOwnerImportRequest),
Output: new(dto.ShopBusinessOwnerImportTaskPageResult),
Auth: true,
})
Register(shops, doc, groupPath, "GET", "/business-owner-imports/:id", handler.Detail, RouteSpec{
Summary: "查询店铺负责人导入任务详情",
Description: constants.ShopOwnerImportAccessDescription + "返回成功数、失败数与逐行失败原因,并单独返回任务级错误原因。",
Tags: []string{"店铺负责人导入"},
Input: new(dto.GetShopBusinessOwnerImportRequest),
Output: new(dto.ShopBusinessOwnerImportTaskDetailResponse),
Auth: true,
})
}