Files
KCGL/db_migrations/add_defective_operation_perms.sql
yueli a252013573 feat(perm): 逆向物流专用权限码与菜单授权
为逆向物流的每个动作拆出独立权限码,终止此前复用其它模块权限的做法。

权限码一览(全部为无冒号形式,见下方说明):

  outbound_return       出库退回
  defective_list        不良品台账查看
  defective_restock     不良品回库
  defective_scrap       不良品报废
  stock_change_status   库存状态变更
  outbound_return_list  退回记录查看

另补建两个菜单:defective_goods(不良品在管台账)、outbound_returns(退回记录),
前者原先只有前端路由、sys_menu 中无对应行,导致页面在权限管理界面不可见、
其下权限也无从授予(sys_element.menu_code 有指向 sys_menu 的外键)。

默认授予:SUPER_ADMIN、SUPERVISOR、WAREHOUSE_MGR。
刻意不含 OUTBOUND —— 业务确认出库员只负责正向拣货发货,不参与逆向物流。

★ 为什么全部用无冒号形式,而不是 <menu>:<action>:
  后端 _expand_operation_perms() 对带冒号的权限码做**前缀桥接** —— 只要用户
  持有该菜单下任一以 :operation/:edit/:delete/... 结尾的权限就被放行。
  实测验证过这个放大效应:

      outbound_list:return  <- 持有 outbound_list:operation   ->  True
      outbound_return        <- 持有 outbound_list:operation   ->  False

  且前端 hasPermission() 是精确匹配,用冒号码会出现「接口能调、按钮却看不到」
  的错位。无冒号码不触发桥接,前后端判定完全一致。

★ add_return_view_support.sql 另含一处 schema 变更:trans_return 补
  company_name 快照列。退回流水的多租户隔离原先只能靠 join 链推
  (trans_return → trans_outbound → 库存表 → 物料主表),而库存行会被入库
  模块物理删除(实测 1077 条出库记录中已有 7 条悬空),链路一断该记录就会
  对普通用户静默消失。审计视图静默丢数据不可接受,故落快照。
  表当前为空,无需回填。

三个脚本均为幂等,含预检、回滚段与执行后核对。
2026-09-16 16:44:46 +08:00

122 lines
6.1 KiB
PL/PgSQL
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.

-- =============================================================================
-- 逆向物流动作权限码(拒绝搭便车)
--
-- 背景
-- 退回接口已拆出专用码 outbound_return见 add_outbound_return_perm.sql
-- 余下三个逆向动作此前仍在复用 inventory_stocktake:operation盲盘作业
-- 职责错配、审计不合规,此处一并收口:
--
-- code 名称 宿主菜单 用途
-- -------------------- -------------- ---------------- ------------------
-- defective_restock 不良品回库 defective_goods 修好后加回原库存
-- defective_scrap 不良品报废 defective_goods 确认无法维修后销毁
-- stock_change_status 库存状态变更 inventory_mgmt 冻结 / 标不良 / 恢复
--
-- ★ 三者均为**无冒号**形式,刻意的。原因见 add_outbound_return_perm.sql
-- 形如 <menu>:<action> 的权限码会命中 _expand_operation_perms() 的
-- 前缀桥接 —— 例如 inventory_stocktake 下已有 inventory_stocktake:operation
-- 若改用 inventory_stocktake:restock则所有持有盘点操作权的角色会被自动
-- 放行,授权面失控。无冒号码不触发桥接,与前端 hasPermission 的精确匹配一致。
--
-- ★ 顺带补建 defective_goods 菜单:二期只加了前端路由(侧边栏由路由驱动,
-- 页面本就能访问),但 sys_menu 中没有对应行,导致该页面在权限管理界面
-- 不可见、其下权限也无法授予。sys_element.menu_code 有指向 sys_menu(code)
-- 的外键,故必须先把菜单建出来。
--
-- 默认授予SUPER_ADMIN超管、SUPERVISOR主管、WAREHOUSE_MGR库管
-- company_name 置 NULL = 全局模板权限,对所有公司生效(与既有授权一致)。
-- ★ 刻意**不含** OUTBOUND业务确认出库员只负责正向拣货发货
-- 不参与任何逆向物流操作。
--
-- 幂等:全部带存在性判断,可重复执行。
-- 执行docker exec -i inventory_db psql -U test -d inventory_system < 本文件
-- =============================================================================
BEGIN;
-- ---------------------------------------------------------------------------
-- 1) 补建「不良品在管台账」菜单(挂在 入库管理 inventory_mgmt 下)
-- ---------------------------------------------------------------------------
INSERT INTO sys_menu (parent_id, name, code, path, sort_order, is_visible)
SELECT (SELECT id FROM sys_menu WHERE code = 'inventory_mgmt'),
'不良品在管台账', 'defective_goods', '/inventory/defective', 6, TRUE
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE code = 'defective_goods');
-- ---------------------------------------------------------------------------
-- 2) 注册三个权限元素
-- ---------------------------------------------------------------------------
INSERT INTO sys_element (menu_code, name, code, element_type)
SELECT v.menu_code, v.name, v.code, 'operation'
FROM (VALUES
('defective_goods', '不良品回库', 'defective_restock'),
('defective_goods', '不良品报废', 'defective_scrap'),
('inventory_mgmt', '库存状态变更', 'stock_change_status')
) AS v(menu_code, name, code)
WHERE NOT EXISTS (SELECT 1 FROM sys_element e WHERE e.code = v.code);
-- ---------------------------------------------------------------------------
-- 3) 默认授予三个角色(菜单 + 元素)
-- ---------------------------------------------------------------------------
-- 3.1 菜单
INSERT INTO sys_role_permission (role_code, target_code, type, company_name)
SELECT v.role_code, 'defective_goods', 'menu', NULL
FROM (VALUES ('SUPER_ADMIN'), ('SUPERVISOR'), ('WAREHOUSE_MGR')) AS v(role_code)
WHERE NOT EXISTS (
SELECT 1 FROM sys_role_permission r
WHERE r.role_code = v.role_code AND r.target_code = 'defective_goods'
AND r.type = 'menu'
);
-- 3.2 元素
INSERT INTO sys_role_permission (role_code, target_code, type, company_name)
SELECT r.role_code, v.target_code, 'element', NULL
FROM (VALUES ('SUPER_ADMIN'), ('SUPERVISOR'), ('WAREHOUSE_MGR')) AS r(role_code)
CROSS JOIN (VALUES
('defective_restock'), ('defective_scrap'), ('stock_change_status')
) AS v(target_code)
WHERE NOT EXISTS (
SELECT 1 FROM sys_role_permission x
WHERE x.role_code = r.role_code AND x.target_code = v.target_code
AND x.type = 'element'
);
-- ---------------------------------------------------------------------------
-- 4) 执行后核对
-- ---------------------------------------------------------------------------
\echo '--- 菜单已建立 ---'
SELECT code, name, path FROM sys_menu WHERE code = 'defective_goods';
\echo '--- 三个权限元素(应 3 行)---'
SELECT e.code, e.name, e.menu_code, e.element_type
FROM sys_element e
WHERE e.code IN ('defective_restock', 'defective_scrap', 'stock_change_status')
ORDER BY e.code;
\echo '--- 授权明细(应 3 菜单 + 9 元素 = 12 行)---'
SELECT target_code, type, string_agg(role_code, ', ' ORDER BY role_code) AS roles
FROM sys_role_permission
WHERE target_code IN ('defective_goods', 'defective_restock',
'defective_scrap', 'stock_change_status')
GROUP BY target_code, type ORDER BY type, target_code;
\echo '--- 确认 OUTBOUND 未获得任何逆向权限(应为 0 行)---'
SELECT target_code FROM sys_role_permission
WHERE role_code = 'OUTBOUND'
AND target_code IN ('outbound_return', 'defective_goods', 'defective_restock',
'defective_scrap', 'stock_change_status');
COMMIT;
-- =============================================================================
-- 回滚段
-- =============================================================================
-- BEGIN;
-- DELETE FROM sys_role_permission
-- WHERE target_code IN ('defective_goods','defective_restock',
-- 'defective_scrap','stock_change_status');
-- DELETE FROM sys_element
-- WHERE code IN ('defective_restock','defective_scrap','stock_change_status');
-- DELETE FROM sys_menu WHERE code = 'defective_goods';
-- COMMIT;