From 97ff63523c1ff4aaa0e6729d325140b710e6545b Mon Sep 17 00:00:00 2001 From: yueli Date: Fri, 11 Sep 2026 12:41:27 +0800 Subject: [PATCH] =?UTF-8?q?fix(export):=20=E5=85=AC=E5=8F=B8=E9=9A=94?= =?UTF-8?q?=E7=A6=BB=E7=A7=BB=E5=87=BA=20if=20filters=20=E5=88=A4=E5=AE=9A?= =?UTF-8?q?=EF=BC=8C=E7=A1=AE=E4=BF=9D=E6=97=A0=E6=9D=A1=E4=BB=B6=E6=89=A7?= =?UTF-8?q?=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit export_excel 的行级公司隔离原本嵌套在 `if filters:` 内部: 只要调用方不传或传空筛选条件,整段隔离会被跳过且不抛错 —— 静默失效。 (实测: 修复前 export_excel({}, None) 会导出跨公司全部 1816 行。) 现把 get_current_company_filter() 及其 filter_conditions.append 提到 if filters: 之前,无论有无筛选条件都绝对执行。 同时清理路由里 filters 字典的 'company' 死键 —— export_excel 从不消费它, 真正生效的是 get_current_company_filter() 直接读 request.args 上的公司标识。 注意:前端 handleExport 的 company 参数必须保留(已在代码中加注说明)。 实测 WAREHOUSE_MGR 带 ?company=IRIS 导出 1126 行仅 IRIS,不带则 1816 行 涵盖 IRIS+LICA —— 跨域角色按公司收窄范围完全依赖这个 query 参数。 实测(修复后): SALES/IRIS filters={} → 1126 行, 仅 IRIS SALES/IRIS filters={keyword:''}→ 1126 行, 仅 IRIS WAREHOUSE_MGR ?company=IRIS → 1126 行, 仅 IRIS WAREHOUSE_MGR (无参数) → 1816 行, IRIS+LICA --- inventory-backend/app/api/v1/inbound/base.py | 5 +++- .../app/services/inbound/base_service.py | 23 ++++++++++++------- inventory-web/src/views/material/list.vue | 3 +++ 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/inventory-backend/app/api/v1/inbound/base.py b/inventory-backend/app/api/v1/inbound/base.py index a6585a6..4ae1bbd 100644 --- a/inventory-backend/app/api/v1/inbound/base.py +++ b/inventory-backend/app/api/v1/inbound/base.py @@ -202,9 +202,12 @@ def get_options(): def export_data(): try: # 获取筛选条件 + # ★ 此处刻意不放 company:export_excel 从不消费该键,放进来是死参数、易误导。 + # 真正生效的公司隔离由 export_excel 内的 get_current_company_filter() 直接读 + # request.args 里的 company / company_name —— 所以前端仍需把它带在 query + # string 上,跨域角色(超管 / WAREHOUSE_MGR)按公司出报表就靠这个参数。 filters = { 'keyword': request.args.get('keyword', ''), - 'company': request.args.get('company', ''), 'category': request.args.get('category', ''), 'type': request.args.get('type', ''), 'isEnabled': request.args.get('isEnabled', None) diff --git a/inventory-backend/app/services/inbound/base_service.py b/inventory-backend/app/services/inbound/base_service.py index bd0698b..a4a8e79 100644 --- a/inventory-backend/app/services/inbound/base_service.py +++ b/inventory-backend/app/services/inbound/base_service.py @@ -790,6 +790,21 @@ class MaterialBaseService: try: # 1. 构造基础信息的筛选条件 (用于过滤库存) filter_conditions = [] + + # ============================================================ + # 【行级数据隔离】基于 JWT 多租户公司过滤 + # ★ 必须无条件执行:此前这段嵌套在 `if filters:` 内部,只要调用方 + # 不传或传空筛选条件,公司隔离就会被整段跳过且不报错(静默失效)。 + # company_limit 的取值规则见 get_current_company_filter(): + # - 普通用户 → JWT 中记录的本公司,强制隔离 + # - 超管 / 跨域角色 → 仅当请求显式带公司标识时才限制,否则返回 None + # ============================================================ + from app.utils.decorators import get_current_company_filter + + company_limit = get_current_company_filter() + if company_limit is not None: + filter_conditions.append(MaterialBase.company_name == company_limit) + if filters: if filters.get('keyword'): kw = f"%{filters['keyword']}%" @@ -799,14 +814,6 @@ class MaterialBaseService: MaterialBase.spec_model.ilike(kw), MaterialBase.company_name.ilike(kw) )) - # ============================================================ - # 【行级数据隔离】基于 JWT 多租户公司过滤 - # ============================================================ - from app.utils.decorators import get_current_company_filter - - company_limit = get_current_company_filter() - if company_limit is not None: - filter_conditions.append(MaterialBase.company_name == company_limit) category = filters.get('category') if category is not None and category != '': diff --git a/inventory-web/src/views/material/list.vue b/inventory-web/src/views/material/list.vue index 0a41316..83253cb 100644 --- a/inventory-web/src/views/material/list.vue +++ b/inventory-web/src/views/material/list.vue @@ -1295,6 +1295,9 @@ const handleExport = () => { exportLoading.value = true; const params = { keyword: queryParams.keyword, + // ★ company 必须保留:后端 export_excel 不消费 filters 里的 company 键, + // 但 get_current_company_filter() 会直接读 query string 上的 company —— + // 跨域角色(超管 / WAREHOUSE_MGR)导出时按所选公司收窄范围全靠它。 company: queryParams.company, category: queryParams.category, type: queryParams.type,