From f589962e5c427a9776fe1d279c1e7ed3ace1355d Mon Sep 17 00:00:00 2001 From: yueli Date: Thu, 10 Sep 2026 11:32:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(scrap):=20=E6=96=B0=E5=A2=9E=E6=8A=A5?= =?UTF-8?q?=E5=BA=9F=E4=B8=93=E7=94=A8=E5=BA=93=E5=AD=98=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E7=AB=AF=E7=82=B9=EF=BC=8C=E8=A7=A3=E8=80=A6=E5=87=BA=E5=BA=93?= =?UTF-8?q?=E9=80=89=E5=8D=95=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 报废申请页原复用 /inbound/stock/list,该端点权限是 outbound_selection。 持有 scrap_apply 的角色目前恰好也都持有该权限,故能跑通,但属隐性耦合: 任一角色授权脱钩就会静默 403,被前端 catch 掩盖成「加载库存失败」。 照抄借库既有先例(transactions.py 的 /borrow/stock-list),新增: GET /v1/scrap/stock-list @permission_required('scrap_apply') ★ 同步把 'scrap_apply' 登记进 stock.py 的 SELECTION_PREFIXES。 _make_price_stripper 仅当前缀为 None 或已在集合中时才剥离价格, 漏登记会导致价格/成本字段泄露给报废申请人(Fail-Closed 失效)。 实测:OUTBOUND/WAREHOUSE_MGR 均 200,无 token 401, 响应中 unit_price/total_price/sale_price 等价格字段零泄漏。 --- inventory-backend/app/api/v1/inbound/stock.py | 3 ++- inventory-backend/app/api/v1/scrap.py | 18 ++++++++++++++++++ inventory-web/src/api/scrap.ts | 9 +++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/inventory-backend/app/api/v1/inbound/stock.py b/inventory-backend/app/api/v1/inbound/stock.py index 4d36530..fcb12c9 100644 --- a/inventory-backend/app/api/v1/inbound/stock.py +++ b/inventory-backend/app/api/v1/inbound/stock.py @@ -252,7 +252,8 @@ def _make_price_stripper(permission_prefix=None): 仅当 permission_prefix 不在已知选单前缀列表中时放行(例如 material_list 场景由调用方自行处理)。 """ # 已知的选单/借用前缀 → 无条件剥离所有价格成本 - SELECTION_PREFIXES = {'outbound_selection', 'op_borrow_apply', 'inventory_stocktake'} + # ★ 新增前缀必须登记在此,否则该前缀的调用方会拿到价格/成本字段(Fail-Closed 失效) + SELECTION_PREFIXES = {'outbound_selection', 'op_borrow_apply', 'inventory_stocktake', 'scrap_apply'} def stripper(d, stock_category): if permission_prefix is None or permission_prefix in SELECTION_PREFIXES: diff --git a/inventory-backend/app/api/v1/scrap.py b/inventory-backend/app/api/v1/scrap.py index faef9a3..ed1b423 100644 --- a/inventory-backend/app/api/v1/scrap.py +++ b/inventory-backend/app/api/v1/scrap.py @@ -104,6 +104,24 @@ def create_scrap(): return jsonify({'code': 400, 'msg': str(e)}), 400 +# -------------------------------------------------------- +# 2.1 报废申请专用库存列表(独立权限,解耦出库选单) +# GET /api/v1/scrap/stock-list +# -------------------------------------------------------- +@scrap_bp.route('/stock-list', methods=['GET']) +@jwt_required() +@permission_required('scrap_apply') +def get_scrap_stock_list(): + """ + 报废申请专用库存列表 — Fail-Closed: 剥离价格字段 + + 与借库 /transactions/borrow/stock-list 同构:复用 _do_get_stock_list, + 但挂 scrap_apply 权限,报废申请人无需持有出库选单权限。 + """ + from app.api.v1.inbound.stock import _do_get_stock_list + return _do_get_stock_list(permission_prefix='scrap_apply') + + # -------------------------------------------------------- # 3. 报废记录查询接口 # GET /api/v1/scrap/records diff --git a/inventory-web/src/api/scrap.ts b/inventory-web/src/api/scrap.ts index 92e0bfd..369736c 100644 --- a/inventory-web/src/api/scrap.ts +++ b/inventory-web/src/api/scrap.ts @@ -27,6 +27,15 @@ export function getScrapRecords(params: any) { }) } +// 3.1 报废申请专用库存列表(独立于出库选单权限,Fail-Closed 剥离价格) +export function getScrapStockList(params: { page?: number; pageSize?: number; keyword?: string }) { + return request({ + url: '/v1/scrap/stock-list', + method: 'get', + params + }) +} + // ========================================== // 报废申请/审批/执行(新流程,镜像出/借库) // ==========================================