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:
@ -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
|
||||
|
||||
Reference in New Issue
Block a user