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

@ -31,16 +31,17 @@ auth_bp = Blueprint('auth', __name__)
def get_current_user_permissions():
"""
返回当前用户拥有的所有权限码列表(包括菜单和元素)
此函数根据角色查询数据库得到权限。
此函数根据角色+公司查询数据库得到权限。
"""
claims = get_jwt()
user_role = claims.get('role')
user_company = claims.get('company_name', '')
if not user_role:
return []
# 超级管理员返回所有字段权限 (忽略大小写)
# 超级管理员返回所有字段权限
if user_role.upper() == 'SUPER_ADMIN':
return ['system_user:*']
perm_dict = AuthService.get_user_permissions(user_role)
perm_dict = AuthService.get_user_permissions(user_role, company_name=user_company)
# 合并菜单和元素权限
perms = perm_dict.get('menus', []) + perm_dict.get('elements', [])
return perms
@ -269,7 +270,9 @@ def update_user(user_id):
@permission_required('system_user')
def get_users():
try:
users = AuthService.get_all_users()
from app.utils.decorators import get_current_company_filter
company_filter = get_current_company_filter()
users = AuthService.get_all_users(company_name=company_filter)
user_permissions = get_current_user_permissions()
filtered_users = [filter_item_by_permissions(user, user_permissions) for user in users]
return jsonify({'msg': '获取成功', 'data': filtered_users}), 200
@ -293,8 +296,9 @@ def delete_user(user_id):
try:
claims = get_jwt()
operator_role = claims.get('role')
operator_company = claims.get('company_name', '')
username = AuthService.delete_user(user_id, operator_role)
username = AuthService.delete_user(user_id, operator_role, operator_company)
return jsonify({'msg': '删除成功', 'username': username}), 200
except Exception as e:
current_app.logger.error(f"Delete User Failed: {str(e)}")
@ -311,7 +315,8 @@ def get_my_permissions():
try:
claims = get_jwt()
role = claims.get('role')
permissions = AuthService.get_user_permissions(role)
company_name = claims.get('company_name', '')
permissions = AuthService.get_user_permissions(role, company_name=company_name)
return jsonify({'msg': '获取成功', 'data': permissions}), 200
except Exception as e:
current_app.logger.error(f"Get Permissions Failed: {str(e)}")

View File

@ -22,12 +22,13 @@ def get_current_user_permissions():
"""
claims = get_jwt()
user_role = claims.get('role')
user_company = claims.get('company_name', '')
if not user_role:
return []
# 超级管理员返回所有字段权限 (忽略大小写)
if user_role.upper() == 'SUPER_ADMIN':
return ['bom_manage:*']
perm_dict = AuthService.get_user_permissions(user_role)
perm_dict = AuthService.get_user_permissions(user_role, company_name=user_company)
# 合并菜单和元素权限
perms = perm_dict.get('menus', []) + perm_dict.get('elements', [])
return perms

View File

@ -18,17 +18,17 @@ inbound_base_bp = Blueprint('stock_base', __name__)
def get_current_user_permissions():
"""
返回当前用户拥有的所有权限码列表(包括菜单和元素)
此函数根据角色查询数据库得到权限。
此函数根据角色+公司查询数据库得到权限。
"""
from flask_jwt_extended import get_jwt
from app.services.auth_service import AuthService
claims = get_jwt()
user_role = claims.get('role')
user_company = claims.get('company_name', '')
if not user_role:
return []
# 超级管理员返回所有字段权限 (忽略大小写)
# 超级管理员返回所有字段权限
if user_role.upper() == 'SUPER_ADMIN':
# 返回通配符权限(供列表脱敏使用)以及所有具体权限(供导出脱敏使用)
return [
'material_list:*',
'material_list:id',
@ -45,7 +45,7 @@ def get_current_user_permissions():
'material_list:isEnabled',
'material_list:operation'
]
perm_dict = AuthService.get_user_permissions(user_role)
perm_dict = AuthService.get_user_permissions(user_role, company_name=user_company)
# 合并菜单和元素权限
perms = perm_dict.get('menus', []) + perm_dict.get('elements', [])
return perms

View File

@ -18,6 +18,7 @@ def get_current_user_permissions():
from app.services.auth_service import AuthService
claims = get_jwt()
user_role = claims.get('role')
user_company = claims.get('company_name', '')
if not user_role:
return []
# 超级管理员返回所有字段权限 (忽略大小写)
@ -25,7 +26,7 @@ def get_current_user_permissions():
# 返回所有以 inbound_buy: 开头的权限码(这里我们返回一个特殊标记,表示全部)
# 为了简单,我们返回 ['inbound_buy:*'],在过滤函数中特殊处理
return ['inbound_buy:*']
perm_dict = AuthService.get_user_permissions(user_role)
perm_dict = AuthService.get_user_permissions(user_role, company_name=user_company)
# 合并菜单和元素权限
perms = perm_dict.get('menus', []) + perm_dict.get('elements', [])
return perms

View File

@ -12,9 +12,10 @@ def get_current_user_permissions():
from app.services.auth_service import AuthService
claims = get_jwt()
user_role = claims.get('role')
user_company = claims.get('company_name', '')
if not user_role: return []
if user_role.upper() == 'SUPER_ADMIN': return ['inbound_product:*']
perm_dict = AuthService.get_user_permissions(user_role)
perm_dict = AuthService.get_user_permissions(user_role, company_name=user_company)
return perm_dict.get('menus', []) + perm_dict.get('elements', [])
def filter_item_by_permissions(item_dict, user_permissions):

View File

@ -12,9 +12,10 @@ def get_current_user_permissions():
from app.services.auth_service import AuthService
claims = get_jwt()
user_role = claims.get('role')
user_company = claims.get('company_name', '')
if not user_role: return []
if user_role.upper() == 'SUPER_ADMIN': return ['inbound_semi:*']
perm_dict = AuthService.get_user_permissions(user_role)
perm_dict = AuthService.get_user_permissions(user_role, company_name=user_company)
return perm_dict.get('menus', []) + perm_dict.get('elements', [])
def filter_item_by_permissions(item_dict, user_permissions):

View File

@ -19,12 +19,13 @@ def get_current_user_permissions():
from app.services.auth_service import AuthService
claims = get_jwt()
user_role = claims.get('role')
user_company = claims.get('company_name', '')
if not user_role:
return []
# 超级管理员返回所有字段权限 (忽略大小写)
if user_role.upper() == 'SUPER_ADMIN':
return ['inbound_service:*']
perm_dict = AuthService.get_user_permissions(user_role)
perm_dict = AuthService.get_user_permissions(user_role, company_name=user_company)
# 合并菜单和元素权限
perms = perm_dict.get('menus', []) + perm_dict.get('elements', [])
return perms

View File

@ -20,12 +20,13 @@ def get_current_user_permissions():
from app.services.auth_service import AuthService
claims = get_jwt()
user_role = claims.get('role')
user_company = claims.get('company_name', '')
if not user_role:
return []
# 超级管理员返回所有字段权限 (忽略大小写)
if user_role.upper() == 'SUPER_ADMIN':
return ['outbound_list:*']
perm_dict = AuthService.get_user_permissions(user_role)
perm_dict = AuthService.get_user_permissions(user_role, company_name=user_company)
# 合并菜单和元素权限
perms = perm_dict.get('menus', []) + perm_dict.get('elements', [])
return perms
@ -116,12 +117,13 @@ def create_outbound():
# 权限检查:需要 outbound_create:operation 或 outbound_selection:operation 之一
claims = get_jwt()
user_role = claims.get('role')
user_company = claims.get('company_name', '')
if not user_role:
return jsonify({'code': 403, 'msg': '未授权'}), 403
# 超级管理员直接放行
if user_role.upper() != 'SUPER_ADMIN':
perm_dict = AuthService.get_user_permissions(user_role)
perm_dict = AuthService.get_user_permissions(user_role, company_name=user_company)
perms = perm_dict.get('menus', []) + perm_dict.get('elements', [])
if ('outbound_create:operation' not in perms) and ('outbound_selection:operation' not in perms):
return jsonify({'code': 403, 'msg': '权限不足'}), 403

View File

@ -1,32 +1,42 @@
# inventory-backend/app/api/v1/permission.py
from flask import Blueprint, request, jsonify, current_app
from flask_jwt_extended import jwt_required
from flask_jwt_extended import jwt_required, get_jwt
from app.services.permission_service import PermissionService
from app.utils.decorators import audit_log
from app.utils.decorators import permission_required, audit_log
permission_bp = Blueprint('permission', __name__)
def _get_operator_company():
"""从 JWT 获取当前操作者公司(None=超管)"""
claims = get_jwt()
role = claims.get('role', '')
if role and role.upper() == 'SUPER_ADMIN':
return None # 超管不限制公司
return claims.get('company_name', '')
@permission_bp.route('/tree', methods=['GET'])
@jwt_required()
@permission_required('system_permission')
def get_tree():
"""获取权限树"""
try:
data = PermissionService.get_permission_tree()
return jsonify({'code': 200, 'msg': '获取成功', 'data': data}), 200
except Exception as e:
# 打印详细错误到控制台,方便调试
current_app.logger.error(f"Get Tree Failed: {str(e)}")
# 返回 500 时带上错误信息
return jsonify({'code': 500, 'msg': f'服务器内部错误: {str(e)}'}), 500
@permission_bp.route('/role/<string:role_code>', methods=['GET'])
@jwt_required()
@permission_required('system_permission')
def get_role_perms(role_code):
"""获取某个角色的权限列表"""
"""获取某个角色的权限列表(已选中的)"""
try:
data = PermissionService.get_role_permissions(role_code)
company_name = _get_operator_company()
data = PermissionService.get_role_permissions(role_code, company_name=company_name)
return jsonify({'code': 200, 'msg': '获取成功', 'data': data}), 200
except Exception as e:
current_app.logger.error(f"Get Role Perms Failed: {str(e)}")
@ -35,19 +45,21 @@ def get_role_perms(role_code):
@permission_bp.route('/assign', methods=['POST'])
@jwt_required()
@permission_required('system_permission:operation')
@audit_log(
module='权限管理',
action='分配',
get_target_name_fn=lambda: request.get_json().get('role_code') if request.get_json() else None
)
def assign_perms():
"""保存权限分配"""
"""保存权限分配(自动带上当前操作者的公司标识)"""
try:
data = request.get_json()
role_code = data.get('role_code')
permissions = data.get('permissions', []) # list of codes
permissions = data.get('permissions', [])
PermissionService.assign_permissions(role_code, permissions)
company_name = _get_operator_company()
PermissionService.assign_permissions(role_code, permissions, company_name=company_name)
return jsonify({'code': 200, 'msg': '保存成功'}), 200
except Exception as e:
current_app.logger.error(f"Assign Perms Failed: {str(e)}")

View File

@ -24,11 +24,12 @@ def get_current_user_permissions():
from app.services.auth_service import AuthService
claims = get_jwt()
user_role = claims.get('role')
user_company = claims.get('company_name', '')
if not user_role:
return []
if user_role.upper() == 'SUPER_ADMIN':
return ['scrap_list:*']
perm_dict = AuthService.get_user_permissions(user_role)
perm_dict = AuthService.get_user_permissions(user_role, company_name=user_company)
perms = perm_dict.get('menus', []) + perm_dict.get('elements', [])
return perms
@ -71,12 +72,13 @@ def scan_barcode():
def create_scrap():
claims = get_jwt()
user_role = claims.get('role')
user_company = claims.get('company_name', '')
if not user_role:
return jsonify({'code': 403, 'msg': '未授权'}), 403
# 超级管理员直接放行
if user_role.upper() != 'SUPER_ADMIN':
perm_dict = AuthService.get_user_permissions(user_role)
perm_dict = AuthService.get_user_permissions(user_role, company_name=user_company)
perms = perm_dict.get('menus', []) + perm_dict.get('elements', [])
if 'scrap_create:operation' not in perms:
return jsonify({'code': 403, 'msg': '权限不足'}), 403

View File

@ -19,12 +19,13 @@ def get_current_user_permissions():
"""
claims = get_jwt()
user_role = claims.get('role')
user_company = claims.get('company_name', '')
if not user_role:
return []
# 超级管理员返回所有字段权限 (忽略大小写)
if user_role.upper() == 'SUPER_ADMIN':
return ['*']
perm_dict = AuthService.get_user_permissions(user_role)
perm_dict = AuthService.get_user_permissions(user_role, company_name=user_company)
# 合并菜单和元素权限
perms = perm_dict.get('menus', []) + perm_dict.get('elements', [])
return perms