diff --git a/inventory-backend/app/services/bom_service.py b/inventory-backend/app/services/bom_service.py index 9c2962c..f30485c 100644 --- a/inventory-backend/app/services/bom_service.py +++ b/inventory-backend/app/services/bom_service.py @@ -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