主要变更: 1. 后端:重构工位管理逻辑,新增工位自定义扩展能力,将工位号上限调整为999并支持14+虚拟工位 2. 前后端:全量替换"BOM"术语为"物料清单",包括提示语、注释和文档 3. WMS模块:新增AGV任务取消接口与前端页面,对接海康RCS取消任务逻辑 4. 权限与种子数据:新增工位新增权限并更新权限配置 5. 修复错误校验逻辑,优化AGV客户端签名与请求处理
282 lines
12 KiB
Go
282 lines
12 KiB
Go
package logic
|
||
|
||
import (
|
||
"context"
|
||
"strconv"
|
||
|
||
"bj_power_mes/ent/permission"
|
||
"bj_power_mes/ent/role"
|
||
"bj_power_mes/ent/station"
|
||
"bj_power_mes/ent/user"
|
||
|
||
"golang.org/x/crypto/bcrypt"
|
||
)
|
||
|
||
// Seed 初始化:超级管理员、默认角色、默认菜单权限、12 道工序与工位派工
|
||
func (s *Service) Seed(ctx context.Context) error {
|
||
// 1. 默认角色(已存在则合并权限码,保证新按钮权限能落到已有角色上)
|
||
roles := []struct{ name, code string }{
|
||
{"超级管理员", "SUPER_ADMIN"},
|
||
{"生产操作员", "OPERATOR"},
|
||
{"质检员", "INSPECTOR"},
|
||
}
|
||
for _, r := range roles {
|
||
codes := []string{"*"}
|
||
if r.code == "OPERATOR" {
|
||
codes = []string{
|
||
"produce.workorder", "produce.dailyplan", "produce.bom", "produce.material", "produce.scan",
|
||
"produce.trace", "produce.wip", "produce.torque", "produce.plc", "produce.product",
|
||
"produce.processflow", "produce.station", "produce.performance", "produce.processcard",
|
||
// 按钮级权限(块2)
|
||
"produce.workorder:add", "produce.workorder:edit", "produce.workorder:delete",
|
||
"produce.workorder:dailyplan", "produce.bom:edit", "produce.material:generate",
|
||
"produce.qtyreport", "produce.quality", "produce.quality:edit",
|
||
"produce.plc:send", "produce.product:add", "produce.product:edit", "produce.product:delete",
|
||
"produce.processflow:add", "produce.processflow:edit", "produce.processflow:delete",
|
||
"produce.processflow:upload", "produce.station:edit", "produce.station:add",
|
||
}
|
||
} else if r.code == "INSPECTOR" {
|
||
codes = []string{"produce.trace", "produce.wip", "produce.torque", "produce.performance", "sys.eventlog",
|
||
"sys.inspect", "sys.inspect:checkin", "sys.inspect:point", "sys.inspect:process",
|
||
"sys.inspect:done", "sys.inspect:alarm", "sys.inspect:view", "produce.quality", "produce.quality:edit"}
|
||
}
|
||
exist, err := s.ctx.EntClient.Role.Query().Where(role.Code(r.code)).First(ctx)
|
||
if err != nil {
|
||
_ = s.ctx.EntClient.Role.Create().SetName(r.name).SetCode(r.code).SetPermissionCodes(codes).Exec(ctx)
|
||
} else {
|
||
_ = s.ctx.EntClient.Role.UpdateOneID(exist.ID).SetPermissionCodes(mergeCodes(exist.PermissionCodes, codes)).Exec(ctx)
|
||
}
|
||
}
|
||
|
||
// 2. 超级管理员账号 admin / 123456(对已存在的旧行做对齐,避免残留禁用状态)
|
||
hash, _ := bcrypt.GenerateFromPassword([]byte("123456"), bcrypt.DefaultCost)
|
||
adminRole, _ := s.ctx.EntClient.Role.Query().Where(role.Code("SUPER_ADMIN")).First(ctx)
|
||
roleId := 0
|
||
if adminRole != nil {
|
||
roleId = adminRole.ID
|
||
}
|
||
if s.ctx.EntClient.User.Query().Where(user.Username("admin")).ExistX(ctx) {
|
||
_ = s.ctx.EntClient.User.Update().
|
||
Where(user.Username("admin")).
|
||
SetPassword(string(hash)).SetStatus("ENABLED").SetRoleId(roleId).Exec(ctx)
|
||
} else {
|
||
_ = s.ctx.EntClient.User.Create().
|
||
SetUsername("admin").SetPassword(string(hash)).SetName("系统管理员").
|
||
SetRoleId(roleId).SetStatus("ENABLED").Exec(ctx)
|
||
}
|
||
|
||
// 3. 默认菜单权限
|
||
menus := []struct{ code, name, typ, path string }{
|
||
{"produce.workorder", "工单管理", "MENU", "/work-order"},
|
||
{"produce.dailyplan", "日排产", "MENU", "/daily-plan"},
|
||
{"produce.bom", "产品物料清单", "MENU", "/bom"},
|
||
{"produce.material", "备料单", "MENU", "/material-request"},
|
||
{"produce.plc", "工位组合下发", "MENU", "/plc-send"},
|
||
{"produce.torque", "拧紧查询", "MENU", "/torque"},
|
||
{"produce.processflow", "工艺流程", "MENU", "/process-flow"},
|
||
{"produce.station", "关联工位", "MENU", "/station"}, {"produce.performance", "绩效报表", "MENU", "/performance"},
|
||
{"produce.scan", "手动报工", "MENU", "/scan"},
|
||
{"produce.trace", "工件追溯", "MENU", "/trace"},
|
||
{"produce.wip", "在制品", "MENU", "/wip"},
|
||
{"produce.processcard", "生产流程卡", "MENU", "/process-card"},
|
||
{"produce.product", "产品型号", "MENU", "/product-type"},
|
||
{"produce.qtyreport", "数量不符上报", "MENU", "/qty-report"},
|
||
{"produce.quality", "质量检验", "MENU", "/quality"},
|
||
{"sys.inspect", "巡检终端", "MENU", "/inspect"},
|
||
{"sys.eventlog", "操作日志", "MENU", "/event-log"},
|
||
// sys.rbac(角色权限管理) 已拆分:账号管理 + 角色管理(对齐 WMS 系统管理菜单)
|
||
{"sys.account", "账号管理", "MENU", "/account"},
|
||
{"sys.role", "角色管理", "MENU", "/role"},
|
||
}
|
||
for _, m := range menus {
|
||
if s.ctx.EntClient.Permission.Query().Where(permission.Code(m.code)).ExistX(ctx) {
|
||
continue
|
||
}
|
||
_ = s.ctx.EntClient.Permission.Create().
|
||
SetCode(m.code).SetName(m.name).SetType(m.typ).SetPath(m.path).Exec(ctx)
|
||
}
|
||
|
||
// 3.1 按钮级权限(块2):前端按钮 + 后端接口 双重校验
|
||
// parent = 所属菜单权限码:每个按钮显式声明挂在哪个菜单下,前端据此组装权限树,
|
||
// 不再用 code.startsWith(菜单+':') 这类前缀通配推断。
|
||
buttons := []struct{ code, name, typ, path, parent string }{
|
||
{"produce.workorder:add", "工单-新增", "BUTTON", "", "produce.workorder"},
|
||
{"produce.workorder:edit", "工单-编辑", "BUTTON", "", "produce.workorder"},
|
||
{"produce.workorder:delete", "工单-删除", "BUTTON", "", "produce.workorder"},
|
||
{"produce.workorder:dailyplan", "工单-日排产", "BUTTON", "", "produce.workorder"},
|
||
{"produce.bom:edit", "物料清单-维护", "BUTTON", "", "produce.bom"},
|
||
{"produce.material:generate", "备料单-生成", "BUTTON", "", "produce.material"},
|
||
{"produce.plc:send", "工位组合-下发", "BUTTON", "", "produce.plc"},
|
||
{"produce.product:add", "产品型号-新增", "BUTTON", "", "produce.product"},
|
||
{"produce.product:edit", "产品型号-编辑", "BUTTON", "", "produce.product"},
|
||
{"produce.product:delete", "产品型号-删除", "BUTTON", "", "produce.product"},
|
||
{"produce.processflow:add", "工艺流程-新增", "BUTTON", "", "produce.processflow"},
|
||
{"produce.processflow:edit", "工艺流程-编辑", "BUTTON", "", "produce.processflow"},
|
||
{"produce.processflow:delete", "工艺流程-删除", "BUTTON", "", "produce.processflow"},
|
||
{"produce.processflow:upload", "工艺流程-上传图纸", "BUTTON", "", "produce.processflow"},
|
||
{"produce.station:edit", "工位-绑定与改名", "BUTTON", "", "produce.station"},
|
||
{"produce.station:add", "工位-新增", "BUTTON", "", "produce.station"},
|
||
{"sys.account:add", "账号-新增", "BUTTON", "", "sys.account"},
|
||
{"sys.account:edit", "账号-编辑", "BUTTON", "", "sys.account"},
|
||
{"sys.account:delete", "账号-删除", "BUTTON", "", "sys.account"},
|
||
{"sys.role:add", "角色-新增", "BUTTON", "", "sys.role"},
|
||
{"sys.role:edit", "角色-编辑", "BUTTON", "", "sys.role"},
|
||
{"sys.role:delete", "角色-删除", "BUTTON", "", "sys.role"},
|
||
{"sys.role:perm", "角色-权限定义", "BUTTON", "", "sys.role"},
|
||
{"sys.inspect:checkin", "巡检-开工签到", "BUTTON", "", "sys.inspect"},
|
||
{"sys.inspect:point", "巡检-工位点检", "BUTTON", "", "sys.inspect"},
|
||
{"sys.inspect:process", "巡检-过程巡检", "BUTTON", "", "sys.inspect"},
|
||
{"sys.inspect:done", "巡检-完工确认", "BUTTON", "", "sys.inspect"},
|
||
{"sys.inspect:alarm", "巡检-异常上报", "BUTTON", "", "sys.inspect"},
|
||
{"sys.inspect:view", "巡检-记录查看", "BUTTON", "", "sys.inspect"},
|
||
{"produce.quality:edit", "质量检验-录入/处置", "BUTTON", "", "produce.quality"},
|
||
}
|
||
for _, b := range buttons {
|
||
exist, err := s.ctx.EntClient.Permission.Query().Where(permission.Code(b.code)).Only(ctx)
|
||
if err == nil && exist != nil {
|
||
// 已存在:幂等回填 parentCode(避免旧数据没有父子关系导致权限树漏按钮)
|
||
if exist.ParentCode != b.parent {
|
||
_ = exist.Update().SetParentCode(b.parent).Exec(ctx)
|
||
}
|
||
continue
|
||
}
|
||
_ = s.ctx.EntClient.Permission.Create().
|
||
SetCode(b.code).SetName(b.name).SetType(b.typ).SetPath(b.path).
|
||
SetParentCode(b.parent).Exec(ctx)
|
||
}
|
||
|
||
// 3.2 旧版权限码迁移(sys.rbac 家族 → sys.account / sys.role,幂等)
|
||
migrateLegacySysRbac(ctx, s)
|
||
|
||
// 3.3 工艺路线模块已整块删除(D1):清掉存量角色里的路线权限码与旧菜单/按钮定义行(幂等)
|
||
migrateLegacyRoute(ctx, s)
|
||
|
||
// 4. 初始化工位与默认工艺流程(工位绑定流程,流程承载步骤)
|
||
seedFlowsAndStations(ctx, s)
|
||
return nil
|
||
}
|
||
|
||
func seedFlowsAndStations(ctx context.Context, s *Service) {
|
||
// 初始数据:12 个工位(工位数量以数据库为准,这里只做首次初始化;要 13 个就往 station 表插一行)
|
||
for i := 1; i <= 12; i++ {
|
||
st, err := s.ctx.EntClient.Station.Query().Where(station.StationNo(i)).First(ctx)
|
||
if err != nil {
|
||
st, _ = s.ctx.EntClient.Station.Create().
|
||
SetStationNo(i).SetName("装配工位" + strconv.Itoa(i)).SetStatus("ENABLED").Save(ctx)
|
||
}
|
||
if st == nil || st.FlowId > 0 {
|
||
continue
|
||
}
|
||
flow, err := s.ctx.EntClient.ProcessFlow.Create().
|
||
SetName("工位" + strconv.Itoa(i) + "_默认").
|
||
SetStatus("ACTIVE").SetRemark("默认工艺流程").Save(ctx)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
if _, err := s.ctx.EntClient.ProcessStep.Create().
|
||
SetFlowId(flow.ID).SetSeq(1).SetName("装配完成").SetCollectType("NONE").Save(ctx); err == nil && i == 3 {
|
||
_, _ = s.ctx.EntClient.ProcessStep.Create().
|
||
SetFlowId(flow.ID).SetSeq(2).SetName("拧紧扭矩").SetCollectType("AUTO").SetIsTorque(true).Save(ctx)
|
||
}
|
||
_, _ = s.ctx.EntClient.Station.UpdateOneID(st.ID).SetFlowId(flow.ID).Save(ctx)
|
||
}
|
||
// 0 号(上线位) / 13 号(下线位) 虚拟工位:OFFLINE 类型,用 PAD 操作,不连 PLC、
|
||
// 不叫料、不进入产线组合排序;仅做上下线登记,不参与物理工位计数。
|
||
for _, v := range []struct {
|
||
no int
|
||
name string
|
||
}{
|
||
{0, "上线位(虚拟)"},
|
||
{13, "下线位(虚拟)"},
|
||
} {
|
||
if _, err := s.ctx.EntClient.Station.Query().Where(station.StationNo(v.no)).First(ctx); err != nil {
|
||
_, _ = s.ctx.EntClient.Station.Create().
|
||
SetStationNo(v.no).SetName(v.name).SetStationType("OFFLINE").SetStatus("ENABLED").Save(ctx)
|
||
}
|
||
}
|
||
}
|
||
|
||
// mergeCodes 合并两份权限码列表(去重、保序)
|
||
func mergeCodes(a, b []string) []string {
|
||
seen := map[string]bool{}
|
||
out := make([]string, 0, len(a)+len(b))
|
||
for _, c := range append(append([]string{}, a...), b...) {
|
||
if c == "" || seen[c] {
|
||
continue
|
||
}
|
||
seen[c] = true
|
||
out = append(out, c)
|
||
}
|
||
return out
|
||
}
|
||
|
||
// migrateLegacySysRbac 迁移旧 sys.rbac 家族权限码(幂等,可重复执行):
|
||
// sys.rbac(角色权限管理) 已拆分为 sys.account(账号管理)+sys.role(角色管理)。
|
||
// 对存量角色 permissionCodes 做码替换,并删除已废弃的权限定义行。
|
||
func migrateLegacySysRbac(ctx context.Context, s *Service) {
|
||
legacyMap := map[string][]string{
|
||
"sys.rbac": {"sys.account", "sys.role"},
|
||
"sys.rbac:user": {"sys.account:add", "sys.account:edit", "sys.account:delete"},
|
||
"sys.rbac:role": {"sys.role:add", "sys.role:edit", "sys.role:delete"},
|
||
"sys.rbac:perm": {"sys.role:perm"},
|
||
}
|
||
// 1) 角色 permissionCodes 替换
|
||
roleList, _ := s.ctx.EntClient.Role.Query().All(ctx)
|
||
for _, rl := range roleList {
|
||
changed := false
|
||
out := make([]string, 0, len(rl.PermissionCodes))
|
||
for _, c := range rl.PermissionCodes {
|
||
if repl, ok := legacyMap[c]; ok {
|
||
out = append(out, repl...)
|
||
changed = true
|
||
continue
|
||
}
|
||
out = append(out, c)
|
||
}
|
||
if changed {
|
||
_ = s.ctx.EntClient.Role.UpdateOneID(rl.ID).
|
||
SetPermissionCodes(mergeCodes(out, nil)).Exec(ctx)
|
||
}
|
||
}
|
||
// 2) 删除旧权限定义行
|
||
for oldCode := range legacyMap {
|
||
_, _ = s.ctx.EntClient.Permission.Delete().
|
||
Where(permission.Code(oldCode)).Exec(ctx)
|
||
}
|
||
}
|
||
|
||
// migrateLegacyRoute 清理已删除的「工艺路线」模块残留(幂等,可重复执行):
|
||
// 产线定义已简化为「关联工位(station.flow_id) + 工位号 1→12 顺序 + 工单工位组合」,
|
||
// 工艺路线/路线段整块删除,故需从存量角色的 permissionCodes 中摘除路线权限码,
|
||
// 并删除对应的菜单/按钮权限定义行,避免菜单树里留下失效入口。
|
||
func migrateLegacyRoute(ctx context.Context, s *Service) {
|
||
legacyCodes := map[string]bool{
|
||
"produce.route": true,
|
||
"produce.route:add": true,
|
||
"produce.route:edit": true,
|
||
"produce.route:delete": true,
|
||
}
|
||
// 1) 角色 permissionCodes 剔除
|
||
roleList, _ := s.ctx.EntClient.Role.Query().All(ctx)
|
||
for _, rl := range roleList {
|
||
kept := make([]string, 0, len(rl.PermissionCodes))
|
||
changed := false
|
||
for _, c := range rl.PermissionCodes {
|
||
if legacyCodes[c] {
|
||
changed = true
|
||
continue
|
||
}
|
||
kept = append(kept, c)
|
||
}
|
||
if changed {
|
||
_ = s.ctx.EntClient.Role.UpdateOneID(rl.ID).
|
||
SetPermissionCodes(mergeCodes(kept, nil)).Exec(ctx)
|
||
}
|
||
}
|
||
// 2) 删除权限定义行
|
||
for code := range legacyCodes {
|
||
_, _ = s.ctx.EntClient.Permission.Delete().
|
||
Where(permission.Code(code)).Exec(ctx)
|
||
}
|
||
}
|