fix: 补全审计/出库审批/采购管理/借库审批的权限保护+公司隔离
- audit.py: 补@permission_required(system_audit)+import, 审计日志通过split_part关联用户表过滤公司 - outbound.py: 出库审批5个端点全补@permission_required(outbound_list) - purchase.py: 采购管理7个端点全补@permission_required(inbound_buy) - borrow_service: 借库审批列表通过applicant_id关联用户表过滤公司 - outbound_service: 出库审批列表通过applicant_id关联用户表过滤公司 - purchase_service: 采购列表通过base_id+requester_id双路过滤公司 - base_service/search_material: 去除debug日志 - decorators: 去除debug日志
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
# inventory-backend/app/api/v1/audit.py
|
||||
from flask import Blueprint, request, jsonify, current_app
|
||||
from flask_jwt_extended import jwt_required, get_jwt
|
||||
from app.utils.decorators import permission_required
|
||||
from app.models.audit import AuditLog
|
||||
from app.extensions import db
|
||||
from sqlalchemy import or_
|
||||
@ -12,6 +13,7 @@ audit_bp = Blueprint('audit', __name__)
|
||||
|
||||
@audit_bp.route('/logs', methods=['GET'])
|
||||
@jwt_required()
|
||||
@permission_required('system_audit')
|
||||
def get_audit_logs():
|
||||
"""获取审计日志列表(分页)"""
|
||||
try:
|
||||
@ -54,6 +56,15 @@ def get_audit_logs():
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# 【行级数据隔离】通过操作人 username 关联用户表过滤公司
|
||||
from app.utils.decorators import get_current_company_filter
|
||||
from app.models.system import SysUser
|
||||
from sqlalchemy import func
|
||||
company_limit = get_current_company_filter()
|
||||
if company_limit is not None:
|
||||
query = query.join(SysUser, func.split_part(SysUser.username, '/', 2) == AuditLog.username) \
|
||||
.filter(SysUser.department == company_limit)
|
||||
|
||||
# 排序
|
||||
query = query.order_by(AuditLog.created_at.desc())
|
||||
|
||||
@ -64,12 +75,16 @@ def get_audit_logs():
|
||||
# 序列化
|
||||
data = [log.to_dict() for log in logs]
|
||||
|
||||
# 获取可用的模块和操作类型(用于下拉选项)
|
||||
modules = db.session.query(AuditLog.module).distinct().all()
|
||||
modules = [m[0] for m in modules if m[0]]
|
||||
|
||||
actions = db.session.query(AuditLog.action).distinct().all()
|
||||
actions = [a[0] for a in actions if a[0]]
|
||||
# 获取可用的模块和操作类型(同公司范围内)
|
||||
modules_query = db.session.query(AuditLog.module).distinct()
|
||||
actions_query = db.session.query(AuditLog.action).distinct()
|
||||
if company_limit is not None:
|
||||
modules_query = modules_query.join(SysUser, func.split_part(SysUser.username, '/', 2) == AuditLog.username) \
|
||||
.filter(SysUser.department == company_limit)
|
||||
actions_query = actions_query.join(SysUser, func.split_part(SysUser.username, '/', 2) == AuditLog.username) \
|
||||
.filter(SysUser.department == company_limit)
|
||||
modules = [m[0] for m in modules_query.all() if m[0]]
|
||||
actions = [a[0] for a in actions_query.all() if a[0]]
|
||||
|
||||
return jsonify({
|
||||
'code': 200,
|
||||
|
||||
@ -234,6 +234,7 @@ def get_current_user_info():
|
||||
# --------------------------------------------------------
|
||||
@outbound_bp.route('/request', methods=['POST'])
|
||||
@jwt_required()
|
||||
@permission_required('outbound_list')
|
||||
def create_outbound_request():
|
||||
"""
|
||||
创建出库审批单(申请阶段,用户只需提交宏观物料信息,无需关联具体库存记录)
|
||||
@ -322,6 +323,7 @@ def create_outbound_request():
|
||||
# --------------------------------------------------------
|
||||
@outbound_bp.route('/request/<int:request_id>/approve', methods=['PATCH'])
|
||||
@jwt_required()
|
||||
@permission_required('outbound_list')
|
||||
def approve_outbound_request(request_id):
|
||||
"""
|
||||
审批出库申请
|
||||
@ -375,6 +377,7 @@ def approve_outbound_request(request_id):
|
||||
# --------------------------------------------------------
|
||||
@outbound_bp.route('/request', methods=['GET'])
|
||||
@jwt_required()
|
||||
@permission_required('outbound_list')
|
||||
def get_outbound_request_list():
|
||||
"""
|
||||
获取出库审批单列表
|
||||
@ -421,6 +424,7 @@ def get_outbound_request_list():
|
||||
# --------------------------------------------------------
|
||||
@outbound_bp.route('/request/<int:request_id>', methods=['GET'])
|
||||
@jwt_required()
|
||||
@permission_required('outbound_list')
|
||||
def get_outbound_request_detail(request_id):
|
||||
"""获取出库审批单详情"""
|
||||
try:
|
||||
|
||||
@ -26,6 +26,7 @@ def get_current_user_role():
|
||||
# --------------------------------------------------------
|
||||
@purchase_bp.route('', methods=['GET'])
|
||||
@jwt_required()
|
||||
@permission_required('inbound_buy')
|
||||
def get_purchase_list():
|
||||
"""获取采购申请列表"""
|
||||
try:
|
||||
@ -59,6 +60,7 @@ def get_purchase_list():
|
||||
# --------------------------------------------------------
|
||||
@purchase_bp.route('', methods=['POST'])
|
||||
@jwt_required()
|
||||
@permission_required('inbound_buy')
|
||||
def create_purchase_request():
|
||||
"""创建采购申请"""
|
||||
try:
|
||||
@ -100,6 +102,7 @@ def create_purchase_request():
|
||||
# --------------------------------------------------------
|
||||
@purchase_bp.route('/<int:purchase_id>', methods=['GET'])
|
||||
@jwt_required()
|
||||
@permission_required('inbound_buy')
|
||||
def get_purchase_detail(purchase_id):
|
||||
"""获取采购申请详情"""
|
||||
try:
|
||||
@ -125,6 +128,7 @@ def get_purchase_detail(purchase_id):
|
||||
# --------------------------------------------------------
|
||||
@purchase_bp.route('/<int:purchase_id>/approve', methods=['PATCH'])
|
||||
@jwt_required()
|
||||
@permission_required('inbound_buy')
|
||||
def approve_purchase_request(purchase_id):
|
||||
"""审批采购申请"""
|
||||
try:
|
||||
@ -167,6 +171,7 @@ def approve_purchase_request(purchase_id):
|
||||
# --------------------------------------------------------
|
||||
@purchase_bp.route('/approvers', methods=['GET'])
|
||||
@jwt_required()
|
||||
@permission_required('inbound_buy')
|
||||
def get_purchase_approvers():
|
||||
"""获取可选审批人列表(主管+超管)"""
|
||||
try:
|
||||
@ -194,6 +199,7 @@ def get_purchase_approvers():
|
||||
# --------------------------------------------------------
|
||||
@purchase_bp.route('/auto-fill', methods=['GET'])
|
||||
@jwt_required()
|
||||
@permission_required('inbound_buy')
|
||||
def auto_fill_purchase():
|
||||
"""根据名称或规格自动补全另一个字段"""
|
||||
try:
|
||||
@ -237,6 +243,7 @@ def get_approved_unstocked_requests():
|
||||
# --------------------------------------------------------
|
||||
@purchase_bp.route('/search-material', methods=['GET'])
|
||||
@jwt_required()
|
||||
@permission_required('inbound_buy')
|
||||
def search_material_for_purchase():
|
||||
"""物料基础信息搜索接口,支持分页,用于采购申请弹窗"""
|
||||
try:
|
||||
|
||||
@ -356,6 +356,8 @@ class BorrowApprovalService:
|
||||
"""
|
||||
获取审批单列表
|
||||
"""
|
||||
from app.models.system import SysUser
|
||||
from app.utils.decorators import get_current_company_filter
|
||||
from sqlalchemy import desc
|
||||
|
||||
query = BorrowApproval.query
|
||||
@ -366,6 +368,12 @@ class BorrowApprovalService:
|
||||
if status is not None:
|
||||
query = query.filter(BorrowApproval.status == status)
|
||||
|
||||
# 【行级数据隔离】通过申请人关联用户表过滤公司
|
||||
company_limit = get_current_company_filter()
|
||||
if company_limit is not None:
|
||||
query = query.join(SysUser, BorrowApproval.applicant_id == SysUser.id) \
|
||||
.filter(SysUser.department == company_limit)
|
||||
|
||||
query = query.order_by(desc(BorrowApproval.created_at))
|
||||
|
||||
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
|
||||
@ -995,6 +995,8 @@ class OutboundApprovalService:
|
||||
分页结果
|
||||
"""
|
||||
from app.models.outbound import OutboundApproval
|
||||
from app.models.system import SysUser
|
||||
from app.utils.decorators import get_current_company_filter
|
||||
from sqlalchemy import desc
|
||||
|
||||
query = OutboundApproval.query
|
||||
@ -1005,6 +1007,12 @@ class OutboundApprovalService:
|
||||
if status is not None:
|
||||
query = query.filter(OutboundApproval.status == status)
|
||||
|
||||
# 【行级数据隔离】通过申请人关联用户表过滤公司
|
||||
company_limit = get_current_company_filter()
|
||||
if company_limit is not None:
|
||||
query = query.join(SysUser, OutboundApproval.applicant_id == SysUser.id) \
|
||||
.filter(SysUser.department == company_limit)
|
||||
|
||||
query = query.order_by(desc(OutboundApproval.created_at))
|
||||
|
||||
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
|
||||
@ -128,6 +128,10 @@ class PurchaseService:
|
||||
@staticmethod
|
||||
def get_purchase_list(page=1, per_page=20, requester_id=None, status=None):
|
||||
"""获取采购申请列表,普通用户只看自己的,主管/超管看全部"""
|
||||
from app.utils.decorators import get_current_company_filter
|
||||
from app.models.system import SysUser
|
||||
from sqlalchemy import or_
|
||||
|
||||
query = PurchaseRequest.query
|
||||
|
||||
if requester_id is not None:
|
||||
@ -136,6 +140,16 @@ class PurchaseService:
|
||||
if status is not None:
|
||||
query = query.filter(PurchaseRequest.status == status)
|
||||
|
||||
# 【行级数据隔离】通过 base_id→MaterialBase 或 requester_id→SysUser 双重过滤
|
||||
company_limit = get_current_company_filter()
|
||||
if company_limit is not None:
|
||||
query = query.outerjoin(MaterialBase, PurchaseRequest.base_id == MaterialBase.id) \
|
||||
.outerjoin(SysUser, PurchaseRequest.requester_id == SysUser.id) \
|
||||
.filter(or_(
|
||||
MaterialBase.company_name == company_limit,
|
||||
SysUser.department == company_limit
|
||||
))
|
||||
|
||||
query = query.order_by(PurchaseRequest.created_at.desc())
|
||||
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user