feat(borrow,outbound): 需审批判定收敛——ID优先 + 名称精确AND核心规格(斜杠前段)
- approval_control 重写:带 base_id 按主键绝对判定;无 id 才名称100%一致 AND 核心规格(_code_of)一致兜底,杜绝同名不同规误杀 - 借/出库服务命中需审批但未选审批人时,报错列出需审批物料名/规格
This commit is contained in:
88
inventory-backend/app/services/approval_control.py
Normal file
88
inventory-backend/app/services/approval_control.py
Normal file
@ -0,0 +1,88 @@
|
||||
from app.models.base import MaterialBase
|
||||
|
||||
|
||||
def _code_of(spec):
|
||||
"""取规格的首段编码('S0013/S0013' -> 'S0013';'S0013' -> 'S0013')"""
|
||||
s = (spec or '').strip()
|
||||
if not s:
|
||||
return ''
|
||||
return s.split('/')[0].strip().upper()
|
||||
|
||||
|
||||
def _materials_flagged():
|
||||
"""一次性取出所有“启用且需审批”的物料(量小,逐项判定前先全量拉一次)"""
|
||||
rows = MaterialBase.query.filter(
|
||||
MaterialBase.is_enabled == True,
|
||||
MaterialBase.is_approval_required == True
|
||||
).all()
|
||||
return rows
|
||||
|
||||
|
||||
def _is_int(v):
|
||||
try:
|
||||
int(v)
|
||||
return True
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
|
||||
|
||||
def resolve_approval_control(items):
|
||||
"""
|
||||
判断一批出库/借库申请明细是否需要审批。
|
||||
|
||||
判定优先级:
|
||||
1) 方案1(最高):明细带有效 base_id → 直接按物料主键查 is_approval_required,
|
||||
且只要有有效 ID,无论命中与否都不再做任何字符串兜底(杜绝同名/同规格误杀)。
|
||||
2) 方案2(无 ID 兜底):名称必须 100% 精确一致 AND 核心规格一致(斜杠前段,
|
||||
或前端未传规格视为防呆拦截),两条件同时成立才判定命中。
|
||||
"""
|
||||
flagged = []
|
||||
seen = set()
|
||||
|
||||
def _record(m_obj, req_name):
|
||||
if m_obj.id in seen:
|
||||
return
|
||||
seen.add(m_obj.id)
|
||||
flagged.append({
|
||||
'name': m_obj.name or req_name,
|
||||
'spec_model': m_obj.spec_model or '',
|
||||
'base_id': m_obj.id,
|
||||
})
|
||||
|
||||
# 1. 查出所有“启用且需审批”的危险物料库
|
||||
flagged_materials = _materials_flagged()
|
||||
|
||||
for item in items or []:
|
||||
name = str(item.get('name') or '').strip()
|
||||
spec = str(item.get('spec_model') or '').strip()
|
||||
base_id = item.get('base_id')
|
||||
|
||||
# --- 方案 1:最高优先级,依据 base_id 绝对判定 ---
|
||||
if base_id is not None:
|
||||
bid = int(base_id) if _is_int(base_id) else 0
|
||||
if bid > 0:
|
||||
m = MaterialBase.query.get(bid)
|
||||
if m and m.is_enabled and m.is_approval_required:
|
||||
_record(m, name)
|
||||
# ★ 关键:只要有有效 ID,无论是否需审批,绝不允许往下走字符串兜底
|
||||
continue
|
||||
|
||||
# --- 方案 2:无 ID 时的降级兜底 (名称精确相等 AND 核心规格相等) ---
|
||||
if not name:
|
||||
continue
|
||||
|
||||
# 提取当前申请行的核心规格
|
||||
req_core_spec = _code_of(spec)
|
||||
|
||||
for fm in flagged_materials:
|
||||
# 条件 A:名称必须 100% 绝对一致
|
||||
if fm.name != name:
|
||||
continue
|
||||
|
||||
# 条件 B:核心规格一致 (或前端未传规格视为防呆拦截)
|
||||
fm_core_spec = _code_of(fm.spec_model)
|
||||
if not req_core_spec or req_core_spec == fm_core_spec:
|
||||
_record(fm, name)
|
||||
break # 命中即判定本申请行需审批,跳出内层循环
|
||||
|
||||
return bool(flagged), flagged
|
||||
Reference in New Issue
Block a user