From 9988d1b7ebb16249c20442091c0cf6b08f744f1f Mon Sep 17 00:00:00 2001 From: yueli Date: Wed, 15 Jul 2026 11:49:53 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=A1=A5=E5=85=A8=E5=AE=A1=E8=AE=A1/?= =?UTF-8?q?=E5=87=BA=E5=BA=93=E5=AE=A1=E6=89=B9/=E9=87=87=E8=B4=AD?= =?UTF-8?q?=E7=AE=A1=E7=90=86/=E5=80=9F=E5=BA=93=E5=AE=A1=E6=89=B9?= =?UTF-8?q?=E7=9A=84=E6=9D=83=E9=99=90=E4=BF=9D=E6=8A=A4+=E5=85=AC?= =?UTF-8?q?=E5=8F=B8=E9=9A=94=E7=A6=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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日志 --- inventory-backend/app/api/v1/audit.py | 27 ++++++++++++++----- inventory-backend/app/api/v1/outbound.py | 4 +++ inventory-backend/app/api/v1/purchase.py | 7 +++++ .../app/services/borrow_service.py | 8 ++++++ .../app/services/outbound_service.py | 8 ++++++ .../app/services/purchase_service.py | 14 ++++++++++ 6 files changed, 62 insertions(+), 6 deletions(-) diff --git a/inventory-backend/app/api/v1/audit.py b/inventory-backend/app/api/v1/audit.py index 8d383e3..788e726 100644 --- a/inventory-backend/app/api/v1/audit.py +++ b/inventory-backend/app/api/v1/audit.py @@ -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, diff --git a/inventory-backend/app/api/v1/outbound.py b/inventory-backend/app/api/v1/outbound.py index 57b8d51..68daefe 100644 --- a/inventory-backend/app/api/v1/outbound.py +++ b/inventory-backend/app/api/v1/outbound.py @@ -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//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/', methods=['GET']) @jwt_required() +@permission_required('outbound_list') def get_outbound_request_detail(request_id): """获取出库审批单详情""" try: diff --git a/inventory-backend/app/api/v1/purchase.py b/inventory-backend/app/api/v1/purchase.py index 2c21746..aeaa09a 100644 --- a/inventory-backend/app/api/v1/purchase.py +++ b/inventory-backend/app/api/v1/purchase.py @@ -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('/', 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('//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: diff --git a/inventory-backend/app/services/borrow_service.py b/inventory-backend/app/services/borrow_service.py index 22c1df0..c5cc30d 100644 --- a/inventory-backend/app/services/borrow_service.py +++ b/inventory-backend/app/services/borrow_service.py @@ -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) diff --git a/inventory-backend/app/services/outbound_service.py b/inventory-backend/app/services/outbound_service.py index cd0b83a..8f04bc0 100644 --- a/inventory-backend/app/services/outbound_service.py +++ b/inventory-backend/app/services/outbound_service.py @@ -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) diff --git a/inventory-backend/app/services/purchase_service.py b/inventory-backend/app/services/purchase_service.py index 1d477f8..3127307 100644 --- a/inventory-backend/app/services/purchase_service.py +++ b/inventory-backend/app/services/purchase_service.py @@ -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)