修复工作人员资质关联与角色过滤

按安装、配送、运维菜单显式过滤工作人员,锁定资质所有者并使用姓名和角色回显。

扩展工作人员状态与多角色查询,补充权限校验、回归测试、操作日志和项目文档。
This commit is contained in:
czl231
2026-08-11 19:15:53 +08:00
parent 4b1f045806
commit 74c5e34275
20 changed files with 976 additions and 100 deletions

View File

@@ -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 {

View File

@@ -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")
}
}

View File

@@ -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

View File

@@ -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)
}
}
}