feat: 盘点合并列表 — 服务端JOIN+DB分页,消除99999全量加载

## stock.py
- 新增 GET /draft/merged-list: UNION ALL三表+LEFT JOIN draft
  数据库级 LIMIT/OFFSET 分页,不再加载全量到Python内存
- 参数: session_id, keyword, status_filter, page, pageSize
- 返回: list, total, total_scanned (已扫去重数)

## stock.ts
- 新增 getDraftMergedList() API函数

## stocktake/index.vue
- fetchInventoryList: 改用merged-list单次调用替代99999全量+find()
- resumeSession: limit 99999→1 先检查存在,再limit 500加载
This commit is contained in:
yueli
2026-07-16 13:08:29 +08:00
parent e313f575bf
commit 8c10bb0903
3 changed files with 168 additions and 68 deletions

View File

@ -1408,6 +1408,135 @@ def generate_missing_stocktake():
return jsonify({'code': 500, 'msg': f'生成漏盘数据失败: {str(e)}'}), 500
# --------------------------------------------------------
# 盘点物资合并列表(服务端 JOIN draft + stock
# GET /api/v1/inbound/stock/draft/merged-list
# --------------------------------------------------------
@bp.route('/draft/merged-list', methods=['GET'])
@permission_required('inventory_stocktake')
def get_draft_merged_list():
"""
数据库级分页合并UNION ALL + LEFT JOIN + LIMIT/OFFSET
不再加载全量库存到 Python 内存,由 PostgreSQL 完成分页。
"""
try:
session_id = request.args.get('session_id', '').strip()
if not session_id:
return jsonify({'code': 400, 'msg': 'session_id 不能为空'}), 400
keyword = request.args.get('keyword', '').strip().lower()
status_filter = request.args.get('status_filter', '').strip() # counted / uncounted
page = max(request.args.get('page', 1, type=int), 1)
page_size = min(request.args.get('pageSize', 20, type=int), 200)
# ── 公共 CTE / 子查询片段 ──
union_sql = """
SELECT id, 'stock_buy' AS source_table, sku,
stock_quantity AS stock_qty, warehouse_location, base_id
FROM stock_buy WHERE stock_quantity > 0
UNION ALL
SELECT id, 'stock_semi', sku,
stock_quantity, warehouse_location, base_id
FROM stock_semi WHERE stock_quantity > 0
UNION ALL
SELECT id, 'stock_product', sku,
stock_quantity, warehouse_location, base_id
FROM stock_product WHERE stock_quantity > 0
"""
# ── 构建 WHERE 条件 ──
conditions = []
params = {'sid': session_id}
if keyword:
conditions.append("(LOWER(cs.sku) LIKE :kw OR LOWER(mb.name) LIKE :kw)")
params['kw'] = f'%{keyword}%'
if status_filter == 'counted':
conditions.append("COALESCE(sd.quantity, 0) > 0")
elif status_filter == 'uncounted':
conditions.append("COALESCE(sd.quantity, 0) <= 0")
where_clause = ('WHERE ' + ' AND '.join(conditions)) if conditions else ''
# ── COUNT 查询 ──
count_sql = f"""
SELECT COUNT(*) FROM (
{union_sql}
) cs
LEFT JOIN material_base mb ON cs.base_id = mb.id
LEFT JOIN stocktake_draft sd ON sd.source_table = cs.source_table
AND sd.stock_id = cs.id AND sd.session_id = :sid
{where_clause}
"""
total = db.session.execute(db.text(count_sql), params).scalar()
# ── 已扫数量(去重) ──
scanned_sql = """
SELECT COUNT(DISTINCT (source_table, stock_id))
FROM stocktake_draft WHERE session_id = :sid
"""
total_scanned = db.session.execute(db.text(scanned_sql), {'sid': session_id}).scalar() or 0
# ── 数据查询LIMIT/OFFSET ──
offset = (page - 1) * page_size
data_sql = f"""
SELECT
cs.id AS stock_id, cs.source_table, cs.sku,
cs.stock_qty, cs.warehouse_location,
mb.name AS mat_name, mb.spec_model AS mat_spec,
sd.id AS draft_id, sd.quantity AS draft_qty,
sd.remark AS draft_remark
FROM (
{union_sql}
) cs
LEFT JOIN material_base mb ON cs.base_id = mb.id
LEFT JOIN stocktake_draft sd ON sd.source_table = cs.source_table
AND sd.stock_id = cs.id AND sd.session_id = :sid
{where_clause}
ORDER BY cs.sku
LIMIT :limit OFFSET :offset
"""
params['limit'] = page_size
params['offset'] = offset
rows = db.session.execute(db.text(data_sql), params).fetchall()
# ── 组装结果 ──
items = []
for row in rows:
draft_qty = float(row.draft_qty or 0)
stock_qty = float(row.stock_qty or 0)
diff_qty = (draft_qty - stock_qty) if row.draft_id else -stock_qty
items.append({
'draft_id': row.draft_id,
'stock_id': row.stock_id,
'source_table': row.source_table,
'uniqueKey': f"{row.source_table}_{row.stock_id}",
'sku': row.sku or '',
'material_name': row.mat_name or '',
'spec_model': row.mat_spec or '',
'stock_qty': stock_qty,
'quantity': draft_qty,
'diff_qty': round(diff_qty, 4),
'remark': row.draft_remark or '',
'warehouse_location': row.warehouse_location or '',
})
return jsonify({
'code': 200, 'msg': '获取成功',
'data': {
'list': items, 'total': total,
'total_scanned': total_scanned,
'page': page, 'pageSize': page_size
}
}), 200
except Exception as e:
current_app.logger.error(f'合并列表查询失败: {str(e)}')
return jsonify({'code': 500, 'msg': str(e)}), 500
# --------------------------------------------------------
# 获取应盘物资清单(盘点基数)
# GET /api/v1/inbound/stock/stocktake/all-items