feat: JWT多租户数据权限隔离 & 主管系统管理权限 & 含税单价补齐

## 多租户公司数据隔离
- 新增 get_current_company_filter() 工具函数 (decorators.py)
  SUPER_ADMIN: 可传company_name参数过滤或传ALL看全量
  其他角色: 强制隔离到JWT中的company_name
- 重构 base_service.py / buy_service.py: 用集中式函数替换内联公司过滤
- SysRolePermission 表新增 company_name 字段,支持同角色不同公司权限
- get_user_permissions() 新增 company_name 参数,查公司定制+全局模板权限
- permission.py API 新增 @permission_required 拦截 + 公司过滤
- 19个API/service文件传递 company_name 到权限查询

## 主管系统管理权限
- delete_user() 允许SUPERVISOR删除同公司用户 (原仅SUPER_ADMIN)
- get_all_users() 新增 company_name 参数过滤
- 用户列表/权限分配 API 应用 get_current_company_filter()
- 前端 UserCreate.vue: 超管可见公司下拉框,主管隐藏部门字段

## 前端多租户适配
- material/list.vue / buy.vue: 公司下拉框仅超管可见,默认ALL
- UserCreate.vue: 新增搜索栏公司筛选,部门字段按角色显隐
- auth.ts: getUserList() 支持 params 参数

## Bug修复: 含税单价字段补齐
- buy.vue: 表格列/高级筛选/排序/权限映射新增 post_tax_unit_price
- buy_service.py: allowed_fields/sort_field_map 新增 post_tax_unit_price
This commit is contained in:
yueli
2026-07-13 15:12:22 +08:00
parent 4ce42309db
commit 4f5965db02
22 changed files with 289 additions and 172 deletions

View File

@ -1,7 +1,7 @@
from app.models.system import SysMenu, SysElement, SysRolePermission
from app.extensions import db
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import func
from sqlalchemy import func, or_
class PermissionService:
@ -60,83 +60,106 @@ class PermissionService:
return tree_data
@staticmethod
def get_role_permissions(role_code):
"""获取指定角色拥有的所有权限Code"""
def get_role_permissions(role_code, company_name=None):
"""
获取指定角色拥有的所有权限Code(支持公司级别隔离)
参数:
company_name — None=不过滤, 具体值=查公司定制+全局模板权限
"""
try:
# === 新增逻辑:超级管理员上帝模式 ===
# === 超级管理员上帝模式 ===
if role_code == 'SUPER_ADMIN':
# 直接获取所有可见菜单和元素,无视配置表
all_menus = [m.code for m in SysMenu.query.filter(SysMenu.is_visible == True).all()]
all_elements = [e.code for e in SysElement.query.all()]
return {
'menus': all_menus,
'elements': all_elements
}
# =================================
perms = SysRolePermission.query.filter_by(role_code=role_code).all()
# 构建公司过滤条件
if company_name:
company_filter = or_(
SysRolePermission.company_name == company_name,
SysRolePermission.company_name.is_(None),
SysRolePermission.company_name == '',
SysRolePermission.company_name == 'System'
)
else:
company_filter = True
perms = SysRolePermission.query.filter(
SysRolePermission.role_code == role_code,
company_filter
).all()
menu_codes = []
element_codes = []
for p in perms:
# 这里假设你的数据库存的是 target_code
if p.type == 'menu':
menu_codes.append(p.target_code)
else:
element_codes.append(p.target_code)
# 前端 handleRoleSelect 会合并这两个数组,所以分开返回没问题
return {
'menus': menu_codes,
'elements': element_codes
}
except Exception as e:
# 记录日志或处理错误
print(f"Error fetching role permissions: {e}")
return {'menus': [], 'elements': []}
@staticmethod
def assign_permissions(role_code, permissions):
def assign_permissions(role_code, permissions, company_name=None):
"""
保存角色的权限
permissions: 前端传来的 list,混合了 menu_code 和 element_code
保存角色的权限(支持公司级别隔离)
参数:
role_code — 角色代码
permissions — 前端传来的 list,混合了 menu_code 和 element_code
company_name — 所属公司(None=全局模板,具体值=公司定制权限)
"""
if not role_code:
raise ValueError("角色代码不能为空")
session = db.session
try:
# 1. 开启事务 (Flask-SQLAlchemy 自动管理,但明确逻辑更好)
# 1. 删除该角色+该公司的旧权限(只删同公司的,不影响全局模板和其他公司)
if company_name:
old_perms = SysRolePermission.query.filter_by(
role_code=role_code, company_name=company_name
).all()
else:
# 公司为空时兼容旧逻辑:删除 company_name 为 NULL 的旧权限
old_perms = SysRolePermission.query.filter(
SysRolePermission.role_code == role_code,
or_(
SysRolePermission.company_name.is_(None),
SysRolePermission.company_name == ''
)
).all()
# 2. 删除该角色旧的所有权限(改为对象级删除以触发审计事件)
old_perms = SysRolePermission.query.filter_by(role_code=role_code).all()
for p in old_perms:
db.session.delete(p)
# 3. 准备新数据
# 2. 准备新数据
if permissions:
# 3.1 去重
unique_codes = set(permissions)
# 3.2 预加载所有 Menu Code,用于区分是 Menu 还是 Element
# 这一步很重要,因为 SysRolePermission 表需要 type 字段
all_menu_codes = {res[0] for res in session.query(SysMenu.code).all()}
new_records = []
for code in unique_codes:
if not code: continue
# 判断类型:如果 code 存在于菜单表中,就是 menu,否则就是 element
p_type = 'menu' if code in all_menu_codes else 'element'
new_records.append(SysRolePermission(
role_code=role_code,
target_code=code,
type=p_type
type=p_type,
company_name=company_name # ★ 存储公司标识
))
# 3.3 批量插入
if new_records:
session.add_all(new_records)