perf: 修复 get_bom_with_stock_by_bom_no N+1 查询问题,改为批量 IN + 内存字典匹配
This commit is contained in:
@ -244,40 +244,46 @@ class BomService:
|
||||
@staticmethod
|
||||
def get_bom_with_stock_by_bom_no(bom_no):
|
||||
"""
|
||||
根据 bom_no 获取配方详情,并计算:
|
||||
1. 总可用库存
|
||||
2. 最大可生产套数
|
||||
3. ★ 聚合库位信息 (warehouse_locations)
|
||||
根据 bom_no 获取配方详情,并计算(已修复 N+1 性能问题)
|
||||
"""
|
||||
detail = BomService.get_bom_detail(bom_no)
|
||||
if not detail:
|
||||
return None
|
||||
if not detail or not detail.get('children'):
|
||||
return detail
|
||||
|
||||
# 1. 提取所有子件的 ID 列表
|
||||
child_ids = [child['child_id'] for child in detail['children']]
|
||||
|
||||
# 2. 用一条 IN 语句批量查出所有相关子件的库存和库位
|
||||
stock_stats = db.session.query(
|
||||
StockBuy.base_id,
|
||||
func.coalesce(func.sum(StockBuy.available_quantity), 0).label('total_qty'),
|
||||
func.string_agg(distinct(StockBuy.warehouse_location), ', ').label('locations')
|
||||
).filter(
|
||||
StockBuy.base_id.in_(child_ids),
|
||||
StockBuy.available_quantity > 0
|
||||
).group_by(
|
||||
StockBuy.base_id
|
||||
).all()
|
||||
|
||||
# 3. 将查询结果转换为字典 (Map),方便后续 O(1) 极速匹配
|
||||
stock_map = {
|
||||
stat.base_id: {
|
||||
'qty': stat.total_qty,
|
||||
'loc': stat.locations if stat.locations else ''
|
||||
}
|
||||
for stat in stock_stats
|
||||
}
|
||||
|
||||
# 4. 遍历组装数据(纯内存操作,极快)
|
||||
for child in detail['children']:
|
||||
# 1. 查询该子件的总库存
|
||||
stock_qty = db.session.query(
|
||||
func.coalesce(func.sum(StockBuy.available_quantity), 0)
|
||||
).filter(
|
||||
StockBuy.base_id == child['child_id']
|
||||
).scalar() or 0
|
||||
base_id = child['child_id']
|
||||
stat = stock_map.get(base_id, {'qty': 0, 'loc': ''})
|
||||
|
||||
# 2. ★ 查询该子件涉及的所有库位,并去重拼接 (PostgreSQL 使用 string_agg)
|
||||
# 注意:这里假设主要是 stock_buy 表,如果是成品或半成品也需要做类似 Union 查询
|
||||
# 为简化,这里演示只查 stock_buy 的库位
|
||||
locations = db.session.query(
|
||||
# 去除空值和重复值
|
||||
func.string_agg(distinct(StockBuy.warehouse_location), ', ')
|
||||
).filter(
|
||||
StockBuy.base_id == child['child_id'],
|
||||
StockBuy.available_quantity > 0, # 只看有货的库位
|
||||
StockBuy.warehouse_location != None,
|
||||
StockBuy.warehouse_location != ''
|
||||
).scalar()
|
||||
stock_qty = float(stat['qty'])
|
||||
dosage = float(child['dosage']) if child.get('dosage') else 0
|
||||
|
||||
child['current_stock'] = float(stock_qty)
|
||||
child['warehouse_location'] = locations or '' # 返回给前端
|
||||
|
||||
dosage = child['dosage']
|
||||
child['current_stock'] = stock_qty
|
||||
child['warehouse_location'] = stat['loc']
|
||||
child['max_producible'] = int(stock_qty // dosage) if dosage > 0 else 0
|
||||
|
||||
return detail
|
||||
|
||||
Reference in New Issue
Block a user