diff --git a/inventory-backend/app/services/permission_service.py b/inventory-backend/app/services/permission_service.py index 02783ce..ac17314 100644 --- a/inventory-backend/app/services/permission_service.py +++ b/inventory-backend/app/services/permission_service.py @@ -176,7 +176,28 @@ class PermissionService: db.session.flush() # 获取新插入的 ID print(f"✅ 审计日志菜单已创建 (code: {menu_code})") else: - print(f"ℹ️ 审计日志菜单已存在 (code: {menu_code})") + # ★★★ Dirty Check:仅当字段真正变化时才 add,避免触发 UPDATE 事件 + is_dirty = False + if existing_menu.parent_id != 0: + existing_menu.parent_id = 0 + is_dirty = True + if existing_menu.name != '审计日志': + existing_menu.name = '审计日志' + is_dirty = True + if existing_menu.path != '/system/audit': + existing_menu.path = '/system/audit' + is_dirty = True + if existing_menu.sort_order != 110: + existing_menu.sort_order = 110 + is_dirty = True + if existing_menu.is_visible != True: + existing_menu.is_visible = True + is_dirty = True + if is_dirty: + db.session.add(existing_menu) + print(f"🔄 审计日志菜单已更新 (code: {menu_code})") + else: + print(f"ℹ️ 审计日志菜单已存在,无需更新 (code: {menu_code})") # 2. 为超级管理员赋予审计日志菜单权限 role_code = 'SUPER_ADMIN' @@ -230,7 +251,14 @@ class PermissionService: db.session.flush() print(f"✅ 盘点管理顶级菜单已创建") else: - print(f"ℹ️ 盘点管理顶级菜单已存在") + is_dirty = False + if stocktake_menu.parent_id != 0: stocktake_menu.parent_id = 0; is_dirty = True + if stocktake_menu.name != '盘点管理': stocktake_menu.name = '盘点管理'; is_dirty = True + if stocktake_menu.path != '/stocktake': stocktake_menu.path = '/stocktake'; is_dirty = True + if stocktake_menu.sort_order != 30: stocktake_menu.sort_order = 30; is_dirty = True + if stocktake_menu.is_visible != True: stocktake_menu.is_visible = True; is_dirty = True + if is_dirty: db.session.add(stocktake_menu); print(f"🔄 盘点管理顶级菜单已更新") + else: print(f"ℹ️ 盘点管理顶级菜单已存在") # 2. 创建子菜单:盲盘作业 stocktake_op_code = 'inventory_stocktake' @@ -248,7 +276,13 @@ class PermissionService: db.session.flush() print(f"✅ 盲盘作业菜单已创建") else: - print(f"ℹ️ 盲盘作业菜单已存在") + is_dirty = False + if stocktake_op_menu.name != '盲盘作业': stocktake_op_menu.name = '盲盘作业'; is_dirty = True + if stocktake_op_menu.path != '/stocktake/operation': stocktake_op_menu.path = '/stocktake/operation'; is_dirty = True + if stocktake_op_menu.sort_order != 1: stocktake_op_menu.sort_order = 1; is_dirty = True + if stocktake_op_menu.is_visible != True: stocktake_op_menu.is_visible = True; is_dirty = True + if is_dirty: db.session.add(stocktake_op_menu); print(f"🔄 盲盘作业菜单已更新") + else: print(f"ℹ️ 盲盘作业菜单已存在") # 3. 为盲盘作业添加操作权限元素 stocktake_op_element = SysElement.query.filter_by( @@ -265,7 +299,11 @@ class PermissionService: db.session.add(stocktake_op_element) print(f"✅ 盲盘作业操作权限已创建") else: - print(f"ℹ️ 盲盘作业操作权限已存在") + is_dirty = False + if stocktake_op_element.name != '盲盘操作': stocktake_op_element.name = '盲盘操作'; is_dirty = True + if stocktake_op_element.element_type != 'operation': stocktake_op_element.element_type = 'operation'; is_dirty = True + if is_dirty: db.session.add(stocktake_op_element); print(f"🔄 盲盘作业操作权限已更新") + else: print(f"ℹ️ 盲盘作业操作权限已存在") # 4. 创建子菜单:盈亏调整 adjustment_code = 'stock_adjustment' @@ -283,7 +321,13 @@ class PermissionService: db.session.flush() print(f"✅ 盈亏调整菜单已创建") else: - print(f"ℹ️ 盈亏调整菜单已存在") + is_dirty = False + if adjustment_menu.name != '盈亏调整': adjustment_menu.name = '盈亏调整'; is_dirty = True + if adjustment_menu.path != '/stocktake/adjustment': adjustment_menu.path = '/stocktake/adjustment'; is_dirty = True + if adjustment_menu.sort_order != 2: adjustment_menu.sort_order = 2; is_dirty = True + if adjustment_menu.is_visible != True: adjustment_menu.is_visible = True; is_dirty = True + if is_dirty: db.session.add(adjustment_menu); print(f"🔄 盈亏调整菜单已更新") + else: print(f"ℹ️ 盈亏调整菜单已存在") # 5. 为盈亏调整添加列表权限元素 (stock_adjustment:list) adjustment_list_element = SysElement.query.filter_by( @@ -300,7 +344,11 @@ class PermissionService: db.session.add(adjustment_list_element) print(f"✅ 盈亏调整列表权限已创建") else: - print(f"ℹ️ 盈亏调整列表权限已存在") + is_dirty = False + if adjustment_list_element.name != '盈亏列表': adjustment_list_element.name = '盈亏列表'; is_dirty = True + if adjustment_list_element.element_type != 'element': adjustment_list_element.element_type = 'element'; is_dirty = True + if is_dirty: db.session.add(adjustment_list_element); print(f"🔄 盈亏调整列表权限已更新") + else: print(f"ℹ️ 盈亏调整列表权限已存在") # 6. 为盈亏调整添加操作权限元素 (stock_adjustment:operation) adjustment_op_element = SysElement.query.filter_by( @@ -317,7 +365,11 @@ class PermissionService: db.session.add(adjustment_op_element) print(f"✅ 盈亏调整操作权限已创建") else: - print(f"ℹ️ 盈亏调整操作权限已存在") + is_dirty = False + if adjustment_op_element.name != '盈亏操作': adjustment_op_element.name = '盈亏操作'; is_dirty = True + if adjustment_op_element.element_type != 'operation': adjustment_op_element.element_type = 'operation'; is_dirty = True + if is_dirty: db.session.add(adjustment_op_element); print(f"🔄 盈亏调整操作权限已更新") + else: print(f"ℹ️ 盈亏调整操作权限已存在") # 7. 为超级管理员分配所有盘点相关权限 menu_codes = [stocktake_mgmt_code, stocktake_op_code, adjustment_code] @@ -491,13 +543,19 @@ class PermissionService: db.session.delete(e) db.session.delete(dup) - # 第三步:强制重新设置所有子菜单的 parent_id,确保没有遗漏 - # 改为对象级更新以触发审计事件 + # 第三步:仅当子菜单 parent_id 有误时才更新(Dirty Check) + # 遍历所有子菜单,只在 parent_id 需要修正时才触碰对象 + child_codes = [m[0] for m in menu_defs if m[3] is not None] child_menus = SysMenu.query.filter(SysMenu.code.in_(child_codes)).all() for m in child_menus: - m.parent_id = None + # 只有 parent_id 为 0 或 None(即没有正确挂载父菜单)时才更新 + if m.parent_id == 0 or m.parent_id is None: + m.parent_id = None # SQLAlchemy 设为 None 表示挂载到根(parent_id=None) + db.session.add(m) + print(f"🔧 修正子菜单 parent_id: {m.code} (parent_id → None)") - # 创建或更新菜单 + # 第四步:创建或更新菜单(带字段级 Dirty Check) + # 只有至少有一个字段真正变化了才 add,避免 SQLAlchemy 产生 UPDATE 事件 menu_map = {} # code -> menu obj for code, name, path, parent_code, sort_order in menu_defs: @@ -508,21 +566,38 @@ class PermissionService: db.session.flush() print(f"✅ 菜单已创建: {name} ({code})") else: - # 更新已有菜单的属性 - menu.name = name - menu.path = path - menu.sort_order = sort_order + # ★★★ 字段级 Dirty Check:逐字段比较,仅在值真正变化时赋值 + is_dirty = False + + if menu.name != name: + menu.name = name + is_dirty = True + if menu.path != path: + menu.path = path + is_dirty = True + if menu.sort_order != sort_order: + menu.sort_order = sort_order + is_dirty = True + + # 只有至少一个字段变化了才 add(触发 UPDATE) + if is_dirty: + db.session.add(menu) + print(f"🔄 菜单已更新: {name} ({code})") menu_map[code] = menu - # 设置 parent_id + # 第五步:设置 parent_id(带 Dirty Check,只在值真正变化时更新) for code, name, path, parent_code, sort_order in menu_defs: if parent_code and parent_code in menu_map: menu = menu_map[code] parent = menu_map[parent_code] - menu.parent_id = parent.id + # 只有 parent_id 实际变化了才赋值,避免重复触发 UPDATE + if menu.parent_id != parent.id: + menu.parent_id = parent.id + db.session.add(menu) + print(f"🔗 菜单 {code} 已挂载到父菜单 {parent_code} (id={parent.id})") - # 为超级管理员分配所有菜单权限 + # 第六步:为超级管理员分配顶级菜单权限(只做 INSERT,不触碰已存在的记录) for code, name, path, parent_code, sort_order in menu_defs: if parent_code is None: # 只分配顶级菜单 existing_perm = SysRolePermission.query.filter_by(