@ -448,7 +448,22 @@ def _allocate_bom_requirements(requirements, company_limit,
from sqlalchemy . orm import joinedload # ★ 必须在此导入:本函数模块级作用域不可见
# 归一化需求,容忍字符串数字
#
# ★ 必须按 base_id 合并。同一物料会以多行进入本函数,来源都是正常业务:
# · 购物车里同一物料的多批次就是多行( Selection.vue 提交时只带
# base_id + quantity, stock_id 被丢弃);
# · BOM 明细里同一子件被多处引用(前端 requirements 按 child_id 不去重);
# · 调拨 / 补发等拆行场景。
# 而下方分配是「按 base_id 拉全量批次行、降序分配」,且候选快照在整轮
# for req 循环里**不更新** —— 同一个 base_id 出现 N 行,每行都会从同一份
# 快照重新分配一遍,把同一个 stock_id 重复分配 N 次。
# 超配在分配阶段不会暴露,直到 reserve_for_items 的二次校验
# ( take > avail) 才炸, 报「可用库存不足」而实际库存充足。
# 合并后 required_qty 求和,分配语义不变(输入里的 stock_id 本就被忽略)。
# ★ 不采用「合并入参 items」的方案: 那是症状侧, _allocate_bom_requirements
# 才是全系统库存分配的唯一权威入口,在此收口可同时覆盖出库与借库。
reqs = [ ]
merged = { }
for r in requirements :
try :
bid = int ( r . get ( ' base_id ' ) )
@ -460,8 +475,13 @@ def _allocate_bom_requirements(requirements, company_limit,
need = 0.0
if bid < = 0 or need < = 0 :
continue
reqs . append ( { ' base_id ' : bid , ' required_qty ' : nee d,
' name ' : r . get ( ' name ' ) or ' ' , ' spec_model ' : r . get ( ' spec_model ' ) or ' ' } )
hit = merged . get ( bi d)
if hit is not None :
hit [ ' required_qty ' ] + = need
else :
merged [ bid ] = { ' base_id ' : bid , ' required_qty ' : need ,
' name ' : r . get ( ' name ' ) or ' ' , ' spec_model ' : r . get ( ' spec_model ' ) or ' ' }
reqs = list ( merged . values ( ) )
if not reqs :
return jsonify ( { ' code ' : 400 , ' msg ' : ' requirements 中无有效的 base_id/required_qty ' } ) , 400