From e152f16ebcae94889105132f4568b825a27e58ec Mon Sep 17 00:00:00 2001 From: yueli Date: Thu, 10 Sep 2026 11:32:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(scrap):=20=E6=8A=A5=E5=BA=9F=E4=B8=80?= =?UTF-8?q?=E5=BE=8B=E9=9C=80=E5=AE=A1=E6=89=B9=EF=BC=8C=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E6=8C=89=E7=89=A9=E6=96=99=E6=A0=87=E8=AE=B0=E5=8C=BA=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 业务规则变更:所有报废申请都必须由指定审批人审批通过后才能执行。 原逻辑走 resolve_approval_control 判定是否需审批,而 material_base 表中 仅 1/3012 个物料标记了 is_approval_required,意味着 99.97% 的报废申请会 走免审批分支——status 直接置 1、actual_approver_id 被赋为申请人自己、 审批人参数被静默丢弃。前端即便做了必填也只是摆设。 改动(规则收敛到单一来源): · 新增 SCRAP_ALWAYS_REQUIRES_APPROVAL = True,作为唯一开关; · submit_approval() 无审批人一律拒绝;恒置 status=0(待审批); 删除免审批自动通过分支; · resolve_approval_control 仍调用,但仅用于生成提示文案, 不再参与是否审批的判定; · /request/check-approval 返回 need_approval=SCRAP_ALWAYS_REQUIRES_APPROVAL, 否则该接口会继续返回 false,导致前端预检结果失真。 实测:不传审批人 → 拒绝;传审批人 → status=0 且 actual_approver_id 为空。 ⚠ 注意:此前自动通过的单据今后一律进入审批队列。 --- inventory-backend/app/api/v1/scrap.py | 11 +++-- .../app/services/scrap_approval_service.py | 45 ++++++++++--------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/inventory-backend/app/api/v1/scrap.py b/inventory-backend/app/api/v1/scrap.py index ed1b423..5453f91 100644 --- a/inventory-backend/app/api/v1/scrap.py +++ b/inventory-backend/app/api/v1/scrap.py @@ -503,14 +503,19 @@ def _current_user_role(): @jwt_required() @permission_required('scrap_apply') def scrap_check_approval(): - """提交前预检:判断所选库存(物料)是否命中“需审批”""" + """ + 提交前预检:报废一律需审批,本接口返回 need_approval=true, + 并附带命中「需审批物料」标记的明细,供前端展示审批提示文案。 + """ try: from app.services.approval_control import resolve_approval_control + from app.services.scrap_approval_service import SCRAP_ALWAYS_REQUIRES_APPROVAL data = request.get_json() or {} items = data.get('items', []) or [] - need, flagged = resolve_approval_control(items) + _, flagged = resolve_approval_control(items) return jsonify({'code': 200, 'msg': 'success', - 'data': {'need_approval': need, 'materials': flagged}}), 200 + 'data': {'need_approval': SCRAP_ALWAYS_REQUIRES_APPROVAL, + 'materials': flagged}}), 200 except Exception as e: traceback.print_exc() return jsonify({'code': 500, 'msg': f'预检失败: {str(e)}'}), 500 diff --git a/inventory-backend/app/services/scrap_approval_service.py b/inventory-backend/app/services/scrap_approval_service.py index 5dd0f35..0e3f9cd 100644 --- a/inventory-backend/app/services/scrap_approval_service.py +++ b/inventory-backend/app/services/scrap_approval_service.py @@ -21,6 +21,16 @@ def _beijing(): return beijing_time() +# ============================================================================= +# ★ 业务规则(单一事实来源):报废一律需审批 +# +# 与出库/借库不同,报废不过滤 is_approval_required —— 无论物料是否命中该标记, +# 所有报废申请都必须由指定审批人审批通过后才能执行。 +# 前端 apply/index.vue 的「审批人」必填项与此规则保持一致。 +# ============================================================================= +SCRAP_ALWAYS_REQUIRES_APPROVAL = True + + STOCK_MODELS = {} @@ -101,21 +111,19 @@ class ScrapApprovalService: 'available_at_apply': avail, }) - # ★ 是否需审批:命中需审批物料 或 库管代建(force_approval) + # ★ 报废一律需审批(见 SCRAP_ALWAYS_REQUIRES_APPROVAL)。 + # resolve_approval_control 仍调用,但仅用于生成「哪些物料命中需审批」的提示文案, + # 不再用它决定是否需要审批。 from app.services.approval_control import resolve_approval_control - need_approval, flagged_materials = resolve_approval_control(normalized) - if force_approval: - need_approval = True - if need_approval and not approver_id: - if force_approval: - raise ValueError("库管代建报废申请必须选择审批人后再提交") - _names = ";".join(f"{m['name']}({m['spec_model'] or '-'})" for m in flagged_materials) - raise ValueError(f"以下物料需审批报废:{_names}。请选择审批人后再提交") + _, flagged_materials = resolve_approval_control(normalized) - if approver_id: - allowed_approvers = [{"type": "user", "value": int(approver_id)}] - elif not need_approval: - allowed_approvers = [] + if not approver_id: + if flagged_materials: + _names = ";".join(f"{m['name']}({m['spec_model'] or '-'})" for m in flagged_materials) + raise ValueError(f"以下物料需审批报废:{_names}。请选择审批人后再提交") + raise ValueError("报废申请必须选择审批人后再提交") + + allowed_approvers = [{"type": "user", "value": int(approver_id)}] req = ScrapApproval( request_no=ScrapApprovalService.generate_request_no(), @@ -125,17 +133,12 @@ class ScrapApprovalService: req.set_items(normalized) req.set_allowed_approvers(allowed_approvers) - if need_approval: - req.status = 0 # 待审批 - else: - # 默认不审批:直接进入“待执行” - req.status = 1 - req.actual_approver_id = applicant_id - req.approved_at = _beijing() + # ★ 恒为「待审批」,不再走免审批自动通过分支 + req.status = 0 db.session.add(req) db.session.commit() - logger.info(f"[ScrapApproval] 提交成功 {req.request_no} need_approval={need_approval}") + logger.info(f"[ScrapApproval] 提交成功 {req.request_no} approver={approver_id}") return req # ------------------------------------------------------------------