feat: 完成WMS系统权限体系与业务功能迭代

本提交完成了WMS系统的多维度优化升级:
1. 新增RBAC权限系统,支持角色/菜单/按钮三级权限管控
2. 重构库存盘点、物料管理、区域维护等模块的查询筛选与展示逻辑
3. 优化入库/出库/质检等业务流程,完善数据冗余与业务闭环
4. 移除旧装箱表,将装箱逻辑合并到出库主表
5. 新增前端权限判断工具、导出工具与端到端测试用例
6. 补充完善各类注释与数据库字段说明
This commit is contained in:
SunYF
2026-09-03 14:20:37 +08:00
parent 665ff257dc
commit 572fa975bc
101 changed files with 14531 additions and 4692 deletions
+259
View File
@@ -0,0 +1,259 @@
package handler
import (
"net/http"
"bj_power_wms/ent"
"bj_power_wms/ent/permission"
"bj_power_wms/ent/role"
"bj_power_wms/ent/user"
"bj_power_wms/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
// 种子权限定义:MENU=菜单权限(对应侧栏,path 为路由);BUTTON=按钮级权限(页面内操作)
type seedPerm struct {
Code string
Name string
Type string
Path string
Icon string
Sort int
}
var seedPermissions = []seedPerm{
// ===== 菜单权限 =====
{"dashboard:view", "工作台", "MENU", "/", "HomeFilled", 10},
{"inbound:view", "入库管理", "MENU", "/inbound", "Download", 20},
{"outbound:view", "出库管理", "MENU", "/outbound", "Upload", 30},
{"inventory:view", "库存查询", "MENU", "/inventory", "Search", 40},
{"inspection:view", "质量检验", "MENU", "/inspection", "CircleCheck", 50},
{"stocktake:view", "库存盘点", "MENU", "/stocktake", "List", 60},
{"semi:view", "半成品/成品", "MENU", "/semi", "Box", 70},
{"ledger:view", "备料台账", "MENU", "/ledger", "Tickets", 80},
{"zone:view", "区域维护", "MENU", "/zone", "Location", 90},
{"material:view", "物料档案", "MENU", "/material", "Goods", 100},
{"user:manage", "账号管理", "MENU", "/users", "EditPen", 110},
{"role:manage", "角色管理", "MENU", "/roles", "Key", 120},
// ===== 按钮权限 =====
{"inbound:create", "批量入库", "BUTTON", "", "", 200},
{"inbound:import", "Excel导入", "BUTTON", "", "", 201},
{"outbound:create", "发起出库", "BUTTON", "", "", 210},
{"inspection:create", "录入检验", "BUTTON", "", "", 220},
{"stocktake:start", "发起盘点", "BUTTON", "", "", 230},
{"stocktake:writeback", "差异写回", "BUTTON", "", "", 231},
{"material:create", "新增物料", "BUTTON", "", "", 240},
{"material:edit", "编辑物料", "BUTTON", "", "", 241},
{"material:delete", "删除物料", "BUTTON", "", "", 242},
{"zone:create", "新增区域", "BUTTON", "", "", 250},
{"zone:edit", "编辑区域", "BUTTON", "", "", 251},
{"zone:delete", "删除区域", "BUTTON", "", "", 252},
{"user:create", "新增账号", "BUTTON", "", "", 260},
{"user:edit", "编辑账号", "BUTTON", "", "", 261},
{"user:delete", "删除账号", "BUTTON", "", "", 262},
{"role:create", "新增角色", "BUTTON", "", "", 270},
{"role:edit", "编辑角色", "BUTTON", "", "", 271},
{"role:delete", "删除角色", "BUTTON", "", "", 272},
{"*:export", "导出", "BUTTON", "", "", 280},
}
// 三角色预置权限(对齐原硬编码 rolePermissions,并补充按钮权限)
// admin 全量;operator 除账号/角色管理外全部;inspector 仅质检相关
func seedRoleCodes() map[string][]string {
all := make([]string, 0, len(seedPermissions))
for _, p := range seedPermissions {
all = append(all, p.Code)
}
oper := make([]string, 0, len(seedPermissions))
for _, p := range seedPermissions {
if p.Code == "user:manage" || p.Code == "role:manage" ||
p.Code == "user:create" || p.Code == "user:edit" || p.Code == "user:delete" ||
p.Code == "role:create" || p.Code == "role:edit" || p.Code == "role:delete" {
continue
}
oper = append(oper, p.Code)
}
insp := []string{
"dashboard:view", "inventory:view", "inspection:view", "inspection:create",
"stocktake:view", "ledger:view", "zone:view", "material:view", "*:export",
}
return map[string][]string{"admin": all, "operator": oper, "inspector": insp}
}
var seedRoleMeta = []struct {
Code string
Name string
Remark string
}{
{"admin", "管理员", "系统管理员,拥有全部权限"},
{"operator", "库房保管员", "入库/出库/库存/盘点等日常作业"},
{"inspector", "质检员", "质量检验相关"},
}
// seedRbacHandler POST /api/rbac/seed 幂等初始化权限与角色(已存在按 code 跳过)
func seedRbacHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return requireAdmin(func(w http.ResponseWriter, r *http.Request) {
createdP, createdR := 0, 0
for _, p := range seedPermissions {
exist, _ := ctx.EntClient.Permission.Query().
Where(permission.CodeEQ(p.Code)).Exist(ctx0())
if exist {
continue
}
_, err := ctx.EntClient.Permission.Create().
SetCode(p.Code).SetName(p.Name).SetType(p.Type).
SetPath(p.Path).SetIcon(p.Icon).SetSort(p.Sort).
Save(ctx0())
if err == nil {
createdP++
}
}
codes := seedRoleCodes()
bound := 0
for _, m := range seedRoleMeta {
rl, err := ctx.EntClient.Role.Query().
Where(role.CodeEQ(m.Code)).Only(ctx0())
if err != nil {
// 内置角色不存在则创建
rl, err = ctx.EntClient.Role.Create().
SetCode(m.Code).SetName(m.Name).SetRemark(m.Remark).
SetPermissionCodes(codes[m.Code]).
Save(ctx0())
if err != nil {
continue
}
createdR++
}
// 把「role 字符串 == 该角色 code」的历史用户绑定到 role_id(幂等,仅绑定 role_id 为空者)
users, _ := ctx.EntClient.User.Query().
Where(user.RoleEQ(m.Code), user.RoleIDIsNil()).All(ctx0())
for _, u := range users {
if _, err := ctx.EntClient.User.UpdateOneID(u.ID).SetRoleID(rl.ID).Save(ctx0()); err == nil {
bound++
}
}
}
logx.Infof("rbac seed: 新增权限 %d 条、角色 %d 个、绑定历史用户 %d 个", createdP, createdR, bound)
ok(w, map[string]any{"createdPermissions": createdP, "createdRoles": createdR, "boundUsers": bound})
})
}
// listRolesHandler GET /api/roles 角色列表(含权限码)
func listRolesHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
list, err := ctx.EntClient.Role.Query().Order(ent.Asc("id")).All(ctx0())
if err != nil {
fail(w, http.StatusInternalServerError, err.Error())
return
}
ok(w, map[string]any{"list": list})
}
}
// createRoleHandler POST /api/roles 新增角色
func createRoleHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return requireAdmin(func(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
Code string `json:"code"`
Remark string `json:"remark"`
PermissionCodes []string `json:"permissionCodes"`
}
if err := parseJSON(r, &req); err != nil || req.Code == "" || req.Name == "" {
fail(w, http.StatusBadRequest, "角色名称与编码必填")
return
}
exist, _ := ctx.EntClient.Role.Query().Where(role.CodeEQ(req.Code)).Exist(ctx0())
if exist {
fail(w, http.StatusConflict, "角色编码已存在: "+req.Code)
return
}
codes := req.PermissionCodes
if codes == nil {
codes = []string{}
}
rl, err := ctx.EntClient.Role.Create().
SetName(req.Name).SetCode(req.Code).
SetNillableRemark(strPtr(req.Remark)).
SetPermissionCodes(codes).Save(ctx0())
if err != nil {
fail(w, http.StatusInternalServerError, err.Error())
return
}
ok(w, rl)
})
}
// updateRoleHandler POST /api/roles/update 编辑角色(含权限勾选)
func updateRoleHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return requireAdmin(func(w http.ResponseWriter, r *http.Request) {
var req struct {
ID int `json:"id"`
Name string `json:"name"`
Remark string `json:"remark"`
PermissionCodes []string `json:"permissionCodes"`
}
if err := parseJSON(r, &req); err != nil || req.ID <= 0 {
fail(w, http.StatusBadRequest, "参数错误")
return
}
// 角色编码创建后不可修改(前端亦置灰),此处只改名称/备注/权限
upd := ctx.EntClient.Role.UpdateOneID(req.ID)
if req.Name != "" {
upd = upd.SetName(req.Name)
}
if req.Remark != "" {
upd = upd.SetRemark(req.Remark)
}
if req.PermissionCodes != nil {
upd = upd.SetPermissionCodes(req.PermissionCodes)
}
rl, err := upd.Save(ctx0())
if err != nil {
fail(w, http.StatusInternalServerError, err.Error())
return
}
ok(w, rl)
})
}
// deleteRoleHandler POST /api/roles/delete 删除角色(内置三角色禁止删除)
func deleteRoleHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return requireAdmin(func(w http.ResponseWriter, r *http.Request) {
var req struct {
ID int `json:"id"`
}
if err := parseJSON(r, &req); err != nil || req.ID <= 0 {
fail(w, http.StatusBadRequest, "参数错误")
return
}
target, err := ctx.EntClient.Role.Get(ctx0(), req.ID)
if err != nil {
fail(w, http.StatusNotFound, "角色不存在")
return
}
if target.Code == "admin" || target.Code == "operator" || target.Code == "inspector" {
fail(w, http.StatusBadRequest, "内置角色不可删除: "+target.Code)
return
}
if err := ctx.EntClient.Role.DeleteOneID(req.ID).Exec(ctx0()); err != nil {
fail(w, http.StatusInternalServerError, err.Error())
return
}
ok(w, nil)
})
}
// listPermissionsHandler GET /api/permissions 权限列表(菜单+按钮,按 sort 排序)
func listPermissionsHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
list, err := ctx.EntClient.Permission.Query().
Order(ent.Asc("sort"), ent.Asc("id")).All(ctx0())
if err != nil {
fail(w, http.StatusInternalServerError, err.Error())
return
}
ok(w, map[string]any{"list": list})
}
}