From 8c10bb090315ae4ddf78266b0ec3406668a7a0b0 Mon Sep 17 00:00:00 2001 From: yueli Date: Thu, 16 Jul 2026 13:08:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=9B=98=E7=82=B9=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E5=88=97=E8=A1=A8=20=E2=80=94=20=E6=9C=8D=E5=8A=A1=E7=AB=AFJOI?= =?UTF-8?q?N+DB=E5=88=86=E9=A1=B5=EF=BC=8C=E6=B6=88=E9=99=A499999=E5=85=A8?= =?UTF-8?q?=E9=87=8F=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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加载 --- inventory-backend/app/api/v1/inbound/stock.py | 129 ++++++++++++++++++ inventory-web/src/api/inbound/stock.ts | 15 ++ .../src/views/stock/stocktake/index.vue | 92 ++++--------- 3 files changed, 168 insertions(+), 68 deletions(-) diff --git a/inventory-backend/app/api/v1/inbound/stock.py b/inventory-backend/app/api/v1/inbound/stock.py index af78a3f..6c71caa 100644 --- a/inventory-backend/app/api/v1/inbound/stock.py +++ b/inventory-backend/app/api/v1/inbound/stock.py @@ -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 diff --git a/inventory-web/src/api/inbound/stock.ts b/inventory-web/src/api/inbound/stock.ts index d116950..cb7c45e 100644 --- a/inventory-web/src/api/inbound/stock.ts +++ b/inventory-web/src/api/inbound/stock.ts @@ -73,6 +73,21 @@ export function getBom(parentId: number) { }) } +// 盘点物资合并列表(服务端 JOIN draft + stock,替代前端全量加载+find) +export function getDraftMergedList(params: { + session_id: string + keyword?: string + status_filter?: string + page?: number + pageSize?: number +}) { + return request({ + url: '/v1/inbound/stock/draft/merged-list', + method: 'get', + params + }) +} + // 获取应盘物资清单(盘点基数) export function getAllStocktakeItems(params?: { keyword?: string; session_id?: string }) { return request({ diff --git a/inventory-web/src/views/stock/stocktake/index.vue b/inventory-web/src/views/stock/stocktake/index.vue index 5042447..aeb1ab2 100644 --- a/inventory-web/src/views/stock/stocktake/index.vue +++ b/inventory-web/src/views/stock/stocktake/index.vue @@ -413,7 +413,7 @@