修复工作人员资质关联与角色过滤
按安装、配送、运维菜单显式过滤工作人员,锁定资质所有者并使用姓名和角色回显。 扩展工作人员状态与多角色查询,补充权限校验、回归测试、操作日志和项目文档。
This commit is contained in:
@@ -149,9 +149,11 @@ func platformScopedRequestAllowed(ctx *gin.Context, menus []platformbase.Menu) b
|
||||
return hasMenuIdentity(menus, "staff_add")
|
||||
}
|
||||
if ctx.Request.Method == "GET" {
|
||||
required := staffMenuIdentity(ctx.Query("role_code"))
|
||||
return required != "" && (hasMenuIdentity(menus, required) ||
|
||||
(required == "staff_delivery" && hasMenuIdentity(menus, "gasorder_basic")))
|
||||
return staffListRolesAllowed(
|
||||
menus,
|
||||
ctx.Query("role_code"),
|
||||
ctx.Query("role_codes"),
|
||||
)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -217,6 +219,33 @@ func staffMenuIdentity(roleCode string) string {
|
||||
}
|
||||
}
|
||||
|
||||
// staffListRolesAllowed 要求查询中的每一种工作人员角色都具备对应菜单权限。
|
||||
func staffListRolesAllowed(menus []platformbase.Menu, roleCode, roleCodes string) bool {
|
||||
roleCode = strings.TrimSpace(roleCode)
|
||||
roleCodes = strings.TrimSpace(roleCodes)
|
||||
if (roleCode == "") == (roleCodes == "") {
|
||||
return false
|
||||
}
|
||||
requested := []string{roleCode}
|
||||
if roleCodes != "" {
|
||||
requested = strings.Split(roleCodes, ",")
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, role := range requested {
|
||||
role = strings.TrimSpace(role)
|
||||
required := staffMenuIdentity(role)
|
||||
if required == "" || seen[role] {
|
||||
return false
|
||||
}
|
||||
seen[role] = true
|
||||
if !hasMenuIdentity(menus, required) &&
|
||||
!(required == "staff_delivery" && hasMenuIdentity(menus, "gasorder_basic")) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func hasMenuIdentity(menus []platformbase.Menu, identity string) bool {
|
||||
for _, menu := range menus {
|
||||
if menu.Identity == identity {
|
||||
|
||||
@@ -76,3 +76,31 @@ func TestLocationScopeValuesAreExplicit(t *testing.T) {
|
||||
t.Fatal("ambiguous location scope was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStaffListMultiRoleQueryRequiresEveryRoleMenu(t *testing.T) {
|
||||
allMenus := []platformbase.Menu{
|
||||
{Identity: "staff_installer"},
|
||||
{Identity: "staff_delivery"},
|
||||
{Identity: "staff_operations"},
|
||||
}
|
||||
if !staffListRolesAllowed(allMenus, "", "installer,delivery,operations") {
|
||||
t.Fatal("authorized multi-role staff query was rejected")
|
||||
}
|
||||
partialMenus := []platformbase.Menu{{Identity: "staff_installer"}}
|
||||
if staffListRolesAllowed(partialMenus, "", "installer,delivery,operations") {
|
||||
t.Fatal("partial staff permission granted a multi-role query")
|
||||
}
|
||||
if staffListRolesAllowed(allMenus, "delivery", "installer,delivery") {
|
||||
t.Fatal("ambiguous staff role filters were accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderMenuCanOnlyQueryDeliveryStaff(t *testing.T) {
|
||||
menus := []platformbase.Menu{{Identity: "gasorder_basic"}}
|
||||
if !staffListRolesAllowed(menus, "delivery", "") {
|
||||
t.Fatal("order management could not query delivery staff")
|
||||
}
|
||||
if staffListRolesAllowed(menus, "installer", "") {
|
||||
t.Fatal("order management could query installer staff")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package staff
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.apinb.com/bsm-sdk/core/errcode"
|
||||
@@ -12,23 +13,80 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ListStaff 查询服务人员分页列表。
|
||||
func ListStaff(ctx *gin.Context) {
|
||||
roleCode := strings.TrimSpace(ctx.Query("role_code"))
|
||||
if roleCode == "" {
|
||||
common.ListPage[models.StaffAccount](ctx)
|
||||
return
|
||||
type staffListFilters struct {
|
||||
RoleCodes []string
|
||||
Status *int
|
||||
WorkStatus string
|
||||
}
|
||||
|
||||
// parseStaffListFilters 将工作人员列表查询参数收敛为闭集条件,拒绝含糊或冲突值。
|
||||
func parseStaffListFilters(roleCode, roleCodes, status, workStatus string) (staffListFilters, bool) {
|
||||
filters := staffListFilters{}
|
||||
roleCode = strings.TrimSpace(roleCode)
|
||||
roleCodes = strings.TrimSpace(roleCodes)
|
||||
if roleCode != "" && roleCodes != "" {
|
||||
return filters, false
|
||||
}
|
||||
if !validStaffRole(roleCode) {
|
||||
if roleCode != "" {
|
||||
filters.RoleCodes = []string{roleCode}
|
||||
} else if roleCodes != "" {
|
||||
seen := map[string]bool{}
|
||||
for _, value := range strings.Split(roleCodes, ",") {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" || seen[value] {
|
||||
return staffListFilters{}, false
|
||||
}
|
||||
seen[value] = true
|
||||
filters.RoleCodes = append(filters.RoleCodes, value)
|
||||
}
|
||||
}
|
||||
for _, value := range filters.RoleCodes {
|
||||
if !validStaffRole(value) {
|
||||
return staffListFilters{}, false
|
||||
}
|
||||
}
|
||||
status = strings.TrimSpace(status)
|
||||
if status != "" {
|
||||
value, err := strconv.Atoi(status)
|
||||
if err != nil || !common.IsGenericRecordStatus(value) || value == common.StatusArchived {
|
||||
return staffListFilters{}, false
|
||||
}
|
||||
filters.Status = &value
|
||||
}
|
||||
workStatus = strings.TrimSpace(workStatus)
|
||||
if workStatus != "" && !validWorkStatus(workStatus) {
|
||||
return staffListFilters{}, false
|
||||
}
|
||||
filters.WorkStatus = workStatus
|
||||
return filters, true
|
||||
}
|
||||
|
||||
// ListStaff 查询服务人员分页列表,并应用调用方显式声明的角色与在岗状态条件。
|
||||
func ListStaff(ctx *gin.Context) {
|
||||
filters, ok := parseStaffListFilters(
|
||||
ctx.Query("role_code"),
|
||||
ctx.Query("role_codes"),
|
||||
ctx.Query("status"),
|
||||
ctx.Query("work_status"),
|
||||
)
|
||||
if !ok {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
page, size := common.PageSize(ctx)
|
||||
var list []models.StaffAccount
|
||||
var total int64
|
||||
query := common.ApplyKeywordFilter(ctx,
|
||||
common.ActiveRecords(impl.DBService.Model(&models.StaffAccount{})).Where("role_code = ?", roleCode),
|
||||
&models.StaffAccount{})
|
||||
query := common.ActiveRecords(impl.DBService.Model(&models.StaffAccount{}))
|
||||
if len(filters.RoleCodes) > 0 {
|
||||
query = query.Where("role_code IN ?", filters.RoleCodes)
|
||||
}
|
||||
if filters.Status != nil {
|
||||
query = query.Where("status = ?", *filters.Status)
|
||||
}
|
||||
if filters.WorkStatus != "" {
|
||||
query = query.Where("work_status = ?", filters.WorkStatus)
|
||||
}
|
||||
query = common.ApplyKeywordFilter(ctx, query, &models.StaffAccount{})
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
|
||||
@@ -23,3 +23,33 @@ func TestStaffRoleIsClosedEnumeration(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStaffListFiltersAcceptExplicitBusinessScopes(t *testing.T) {
|
||||
filters, ok := parseStaffListFilters("delivery", "", "1", "on_duty")
|
||||
if !ok || len(filters.RoleCodes) != 1 || filters.RoleCodes[0] != "delivery" {
|
||||
t.Fatal("delivery assignment filters were rejected")
|
||||
}
|
||||
if filters.Status == nil || *filters.Status != 1 || filters.WorkStatus != "on_duty" {
|
||||
t.Fatal("staff status filters were not preserved")
|
||||
}
|
||||
filters, ok = parseStaffListFilters("", "installer,delivery,operations", "1", "")
|
||||
if !ok || len(filters.RoleCodes) != 3 {
|
||||
t.Fatal("multi-role service-relation filters were rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStaffListFiltersRejectAmbiguousOrUnknownValues(t *testing.T) {
|
||||
tests := [][4]string{
|
||||
{"delivery", "installer,delivery", "", ""},
|
||||
{"", "delivery,delivery", "", ""},
|
||||
{"admin", "", "", ""},
|
||||
{"", "", "3", ""},
|
||||
{"", "", "enabled", ""},
|
||||
{"", "", "", "available"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
if _, ok := parseStaffListFilters(test[0], test[1], test[2], test[3]); ok {
|
||||
t.Fatalf("invalid staff filters were accepted: %#v", test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user