perf: 消除 outbound/borrow BOM 匹配的 while(true) 全量加载

## 后端
- outbound.py: 新增 POST /api/v1/outbound/bom-match-stock 端点
  接收 child_ids[],服务端按 base_id IN 查询三表有库存记录并返回

## 前端
- outbound.ts: 新增 bomMatchStock(childIds) API 函数
- Selection.vue: loadAllStockForBom (while(true) 全量) → loadStockForBom (单次 API)
- borrow/apply/index.vue: 同上

## 效果
- BOM 匹配从 ~90 次 HTTP 请求降为 1 次
- 浏览器内存从 ~18000 条降为 ~8-50 条
This commit is contained in:
yueli
2026-07-15 17:52:08 +08:00
parent 2556b77530
commit 329820117f
4 changed files with 131 additions and 55 deletions

View File

@ -199,6 +199,93 @@ def get_outbound_list():
return jsonify({'code': 500, 'msg': str(e)}), 500
# ==============================================================================
# BOM 匹配库存接口 (POST /api/v1/outbound/bom-match-stock)
# 替代前端 while(true) 全量加载:服务端按 child_ids 精确查询匹配库存
# ==============================================================================
@outbound_bp.route('/bom-match-stock', methods=['POST'])
@jwt_required()
def bom_match_stock():
"""
根据 BOM 子件 base_id 列表,查询三张库存表中有库存的匹配记录。
Body: { "child_ids": [1, 2, 3, ...] }
Returns: { "code": 200, "data": { "items": [...] } }
"""
try:
data = request.get_json() or {}
child_ids = data.get('child_ids', [])
if not child_ids:
return jsonify({'code': 400, 'msg': 'child_ids 不能为空'}), 400
# 去重
child_ids = list(set(int(x) for x in child_ids))
from app.models.inbound.buy import StockBuy
from app.models.inbound.semi import StockSemi
from app.models.inbound.product import StockProduct
from sqlalchemy.orm import joinedload
all_items = []
# 采购件
buy_items = StockBuy.query.filter(
StockBuy.base_id.in_(child_ids),
StockBuy.stock_quantity > 0
).options(joinedload(StockBuy.base)).all()
for s in buy_items:
d = s.to_dict()
d['type'] = 'material'
d['stock_type'] = 'material'
d['typeLabel'] = '采购件'
d['uniqueKey'] = f"material_{s.id}"
d['name'] = d.get('material_name', '')
d['standard'] = d.get('spec_model', '')
all_items.append(d)
# 半成品
try:
semi_items = StockSemi.query.filter(
StockSemi.base_id.in_(child_ids),
StockSemi.stock_quantity > 0
).options(joinedload(StockSemi.base)).all()
for s in semi_items:
d = s.to_dict()
d['type'] = 'semi'
d['stock_type'] = 'semi'
d['typeLabel'] = '半成品'
d['uniqueKey'] = f"semi_{s.id}"
d['name'] = d.get('material_name', '')
d['standard'] = d.get('spec_model', '')
all_items.append(d)
except Exception:
pass
# 成品
try:
prod_items = StockProduct.query.filter(
StockProduct.base_id.in_(child_ids),
StockProduct.stock_quantity > 0
).options(joinedload(StockProduct.base)).all()
for s in prod_items:
d = s.to_dict()
d['type'] = 'product'
d['stock_type'] = 'product'
d['typeLabel'] = '成品'
d['uniqueKey'] = f"product_{s.id}"
d['name'] = d.get('material_name', '')
d['standard'] = d.get('spec_model', '')
all_items.append(d)
except Exception:
pass
return jsonify({'code': 200, 'msg': 'success', 'data': {'items': all_items}})
except Exception as e:
traceback.print_exc()
return jsonify({'code': 500, 'msg': str(e)}), 500
# ==============================================================================
# 出库审批相关接口
# ==============================================================================