-- ============================================================================= -- 基础信息 · 附件备注的读写权限修复 -- -- 现象 -- 主管(SUPERVISOR)在基础信息里填「产品图备注 / 说明书备注」,点确定后 -- 刷新页面又是空的 —— 看起来像「保存不了」。 -- -- 根因:写通、读断,且读侧引用的是**两个从未注册过的权限码** -- 写侧:PUT /inbound/base/ 的 field_to_perm 里**没有**这两个字段, -- 落入「不在映射中 → 默认允许」的兜底分支 → 直接落库。**保存是成功的。** -- 读侧:列表/搜索接口用 apply_strict_rbac 按 field_permissions.py 过滤, -- 该映射要求 material_list:productImageRemark / material_list:manualLinkRemark。 -- 而这两个码 **在 sys_element 里根本不存在**,也从未授予任何角色 —— -- 除 SUPER_ADMIN(走 material_list:* 通配符绕过)外,**没有任何人能读到**。 -- 于是:填了 → 存进去了 → 回显被抹成 null → 看起来没保存。 -- -- 本次修复(读写双向) -- 一、读侧:注册这两个元素,并授予与 material_list:files **完全相同的 6 个角色** -- —— 逻辑是「能看图的人,就应该能看备注」。 -- 二、写侧:新建 material_list:remark_edit,只授予 3 个核心管理角色。 -- 后端同时把这两个字段显式纳入 field_to_perm 映射,废止那个兜底分支。 -- -- --------------------------------------------------------------------------- -- ★ 为什么写侧要单独建码,而不复用 material_list:operation -- material_list:operation 授予了 INBOUND / OUTBOUND / SUPERVISOR / -- WAREHOUSE_MGR / SUPER_ADMIN 共 5 个角色,比业务要求的 3 个更宽。 -- 复用会让出库员、入库员也能改备注,达不到「只有核心管理角色可改」。 -- -- ★ 为什么码用冒号形式(material_list:remark_edit) -- 本系统有两类权限码:动作类(无冒号,如 outbound_return)与字段类 -- (冒号形式,如 material_list:files)。这是**字段级**权限,与 -- material_list:* 家族保持一致,便于在权限管理界面归组。 -- ⚠ 它只用于 base.py 里的**精确匹配**(perm_code in user_permissions), -- **切勿用 @permission_required('material_list:remark_edit') 包裹路由** —— -- _expand_operation_perms() 会做前缀桥接,凡持有 material_list:operation -- 的角色都会被放行,等于把刚收紧的口子又捅开。 -- -- 幂等:全部带存在性判断,可重复执行。 -- 执行:docker exec -i inventory_db psql -U test -d inventory_system < 本文件 -- ============================================================================= BEGIN; -- --------------------------------------------------------------------------- -- 一、读侧:注册两个字段权限元素(此前从未注册,是「幽灵权限」) -- --------------------------------------------------------------------------- INSERT INTO sys_element (menu_code, name, code, element_type) SELECT 'material_list', v.name, v.code, 'element' FROM (VALUES ('产品图备注', 'material_list:productImageRemark'), ('说明书备注', 'material_list:manualLinkRemark') ) AS v(name, code) WHERE NOT EXISTS (SELECT 1 FROM sys_element e WHERE e.code = v.code); -- 授予与 material_list:files 完全相同的 6 个角色 INSERT INTO sys_role_permission (role_code, target_code, type, company_name) SELECT r.role_code, e.code, 'element', NULL FROM (VALUES ('material_list:productImageRemark'), ('material_list:manualLinkRemark')) AS e(code) CROSS JOIN (VALUES ('INBOUND'), ('OUTBOUND'), ('SALES'), ('SUPERVISOR'), ('WAREHOUSE_MGR'), ('SUPER_ADMIN') ) AS r(role_code) WHERE NOT EXISTS ( SELECT 1 FROM sys_role_permission x WHERE x.role_code = r.role_code AND x.target_code = e.code AND x.type = 'element' ); -- --------------------------------------------------------------------------- -- 二、写侧:新建「附件备注编辑」权限码,只授予 3 个核心管理角色 -- --------------------------------------------------------------------------- INSERT INTO sys_element (menu_code, name, code, element_type) SELECT 'material_list', '附件备注编辑(产品图/说明书)', 'material_list:remark_edit', 'element' WHERE NOT EXISTS (SELECT 1 FROM sys_element WHERE code = 'material_list:remark_edit'); INSERT INTO sys_role_permission (role_code, target_code, type, company_name) SELECT r.role_code, 'material_list:remark_edit', 'element', NULL FROM (VALUES ('SUPER_ADMIN'), ('SUPERVISOR'), ('WAREHOUSE_MGR')) AS r(role_code) WHERE NOT EXISTS ( SELECT 1 FROM sys_role_permission x WHERE x.role_code = r.role_code AND x.target_code = 'material_list:remark_edit' AND x.type = 'element' ); COMMIT; -- ============================================================================= -- 执行后核对 -- ============================================================================= SELECT '=== 1) 三个权限元素已注册(应 3 行)===' AS "核对项"; SELECT code, name, menu_code, element_type FROM sys_element WHERE code IN ('material_list:productImageRemark','material_list:manualLinkRemark','material_list:remark_edit') ORDER BY code; SELECT '=== 2) 读权限:应各 6 个角色,与 material_list:files 一致 ===' AS "核对项"; SELECT target_code AS 权限码, count(*) AS 角色数, string_agg(role_code, ', ' ORDER BY role_code) AS 角色 FROM sys_role_permission WHERE target_code IN ('material_list:files','material_list:productImageRemark','material_list:manualLinkRemark') AND type = 'element' GROUP BY target_code ORDER BY target_code; SELECT '=== 3) 写权限:应 3 个角色(SUPER_ADMIN / SUPERVISOR / WAREHOUSE_MGR)===' AS "核对项"; SELECT role_code FROM sys_role_permission WHERE target_code = 'material_list:remark_edit' AND type = 'element' ORDER BY role_code; SELECT '=== 4) 三组角色集合是否完全对齐(应全为 true)===' AS "核对项"; SELECT (SELECT bool_and(a = b) FROM ( SELECT (SELECT string_agg(role_code,',' ORDER BY role_code) FROM sys_role_permission WHERE target_code='material_list:files' AND type='element') AS a, (SELECT string_agg(role_code,',' ORDER BY role_code) FROM sys_role_permission WHERE target_code='material_list:productImageRemark' AND type='element') AS b ) t) AS 产品图备注读权与files一致, (SELECT bool_and(a = b) FROM ( SELECT (SELECT string_agg(role_code,',' ORDER BY role_code) FROM sys_role_permission WHERE target_code='material_list:files' AND type='element') AS a, (SELECT string_agg(role_code,',' ORDER BY role_code) FROM sys_role_permission WHERE target_code='material_list:manualLinkRemark' AND type='element') AS b ) t) AS 说明书备注读权与files一致; -- ============================================================================= -- 回滚段 -- ============================================================================= -- BEGIN; -- DELETE FROM sys_role_permission WHERE target_code = 'material_list:remark_edit'; -- DELETE FROM sys_element WHERE code = 'material_list:remark_edit'; -- DELETE FROM sys_role_permission -- WHERE target_code IN ('material_list:productImageRemark','material_list:manualLinkRemark'); -- DELETE FROM sys_element -- WHERE code IN ('material_list:productImageRemark','material_list:manualLinkRemark'); -- COMMIT;