From 4b29912f6fe0407df9a5d8bc6a2297b3f52f213a Mon Sep 17 00:00:00 2001 From: dxc Date: Sat, 28 Feb 2026 11:55:19 +0800 Subject: [PATCH] feat: add borrowed quantity column and update stocktake export formulas Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) --- inventory-backend/app/api/v1/inbound/stock.py | 16 +++ .../src/views/stock/stocktake/index.vue | 103 +++++++++++++----- 2 files changed, 91 insertions(+), 28 deletions(-) diff --git a/inventory-backend/app/api/v1/inbound/stock.py b/inventory-backend/app/api/v1/inbound/stock.py index 0c49ea7..3e53e59 100644 --- a/inventory-backend/app/api/v1/inbound/stock.py +++ b/inventory-backend/app/api/v1/inbound/stock.py @@ -115,6 +115,22 @@ def clear_draft(): return jsonify({"message": "Cleared"}), 200 +@bp.route('/borrowed-quantities', methods=['POST']) +@permission_required('inventory_stocktake') +def get_borrowed_quantities(): + """批量获取借出未还数量""" + from app.models.transaction import TransBorrow + data = request.json.get('items', []) + result = {} + for item in data: + source = item.get('source_table') + stock_id = item.get('stock_id') + if source and stock_id is not None: + qty = TransBorrow.get_borrowed_quantity(source, stock_id) + result[f"{source}_{stock_id}"] = qty + return jsonify(result), 200 + + # --- 打印接口 --- @bp.route('/print/selection', methods=['POST']) diff --git a/inventory-web/src/views/stock/stocktake/index.vue b/inventory-web/src/views/stock/stocktake/index.vue index 3a81abe..6204fbe 100644 --- a/inventory-web/src/views/stock/stocktake/index.vue +++ b/inventory-web/src/views/stock/stocktake/index.vue @@ -277,6 +277,8 @@ interface StockItem { type?: string category?: string price?: number + source_table?: string + stock_id?: number [key: string]: any } @@ -293,6 +295,7 @@ const showQtyDialog = ref(false) const allData = ref([]) const scannedMap = ref>(new Map()) +const borrowedQuantities = ref>({}) const filterType = ref('all') const searchKeyword = ref('') @@ -307,6 +310,34 @@ const api = { clearDraft: () => request({ url: '/v1/inbound/stock/draft/clear', method: 'post', data: { user_id: currentUser } }) } +const typeToSourceTable = (type: string): string => { + switch (type) { + case 'material': return 'stock_buy' + case 'semi': return 'stock_semi' + case 'product': return 'stock_product' + default: return '' + } +} + +async function fetchBorrowedQuantities(items: StockItem[]): Promise { + const payload = items.filter(i => i.source_table && i.stock_id).map(i => ({ + source_table: i.source_table, + stock_id: i.stock_id + })) + if (payload.length === 0) return + try { + const res = await request({ + url: '/v1/inbound/stock/borrowed-quantities', + method: 'post', + data: { items: payload } + }) + // res is map of key->qty + borrowedQuantities.value = { ...borrowedQuantities.value, ...res } + } catch (e) { + console.error('获取借出数量失败', e) + } +} + onMounted(async () => { await checkServerDraft() }) @@ -381,7 +412,9 @@ const loadData = async () => { qty_stock: stock, qty_actual: isScanned ? scannedMap.value.get(uuid)! : 0, scanned: isScanned, - uniqueKey: `${type}_${item.id}` + uniqueKey: `${type}_${item.id}`, + source_table: typeToSourceTable(type), + stock_id: item.id }) } @@ -390,6 +423,7 @@ const loadData = async () => { if (res.products) res.products.forEach((i: any) => processItem(i, 'product')) allData.value = list + await fetchBorrowedQuantities(list) } catch (e) { ElMessage.error('数据加载失败') } finally { loading.value = false } @@ -471,34 +505,47 @@ const closeOverlays = () => { const exportToExcel = () => { try { // 1. 已盘点 Sheet - const scannedData = allData.value.filter(i => i.scanned).map(item => ({ - '物品名称': item.name, - '类型': item.type || item.material_type || '-', - '类别': item.category || '-', - '规格型号': item.spec_model || item.standard || '-', // ★ 双重保险 - 'SKU': item.sku, - '批次/SN': item.serial_number || item.batch_no || '-', - '单位': item.unit || '个', - '单价': item.price || item.unit_price || 0, - '账面库存': parseFloat(item.qty_stock as any), - '实盘数量': item.qty_actual, - '盘点结果': item.qty_stock === item.qty_actual ? '相符' : '差异', - '差异数': item.qty_actual - item.qty_stock - })) + const scannedData = allData.value.filter(i => i.scanned).map(item => { + const key = item.source_table && item.stock_id ? `${item.source_table}_${item.stock_id}` : '' + const borrowedQty = borrowedQuantities.value[key] || 0 + const actualTotal = item.qty_actual + borrowedQty + const diff = actualTotal - item.qty_stock + const result = diff === 0 ? '正常' : diff < 0 ? '盘亏/差异' : '盘盈' + return { + '物品名称': item.name, + '类型': item.type || item.material_type || '-', + '类别': item.category || '-', + '规格型号': item.spec_model || item.standard || '-', // ★ 双重保险 + 'SKU': item.sku, + '批次/SN': item.serial_number || item.batch_no || '-', + '单位': item.unit || '个', + '单价': item.price || item.unit_price || 0, + '账面库存': parseFloat(item.qty_stock as any), + '实盘数量': item.qty_actual, + '借出未还数量': borrowedQty, + '盘点结果': result, + '差异数': diff + } + }) // 2. 未盘点 Sheet - const missingData = allData.value.filter(i => !i.scanned).map(item => ({ - '物品名称': item.name, - '类型': item.type || item.material_type || '-', - '类别': item.category || '-', - '规格型号': item.spec_model || item.standard || '-', // ★ 双重保险 - 'SKU': item.sku, - '批次/SN': item.serial_number || item.batch_no || '-', - '单位': item.unit || '个', - '单价': item.price || item.unit_price || 0, - '账面库存': parseFloat(item.qty_stock as any), - '状态': '未盘点' - })) + const missingData = allData.value.filter(i => !i.scanned).map(item => { + const key = item.source_table && item.stock_id ? `${item.source_table}_${item.stock_id}` : '' + const borrowedQty = borrowedQuantities.value[key] || 0 + return { + '物品名称': item.name, + '类型': item.type || item.material_type || '-', + '类别': item.category || '-', + '规格型号': item.spec_model || item.standard || '-', // ★ 双重保险 + 'SKU': item.sku, + '批次/SN': item.serial_number || item.batch_no || '-', + '单位': item.unit || '个', + '单价': item.price || item.unit_price || 0, + '账面库存': parseFloat(item.qty_stock as any), + '借出未还数量': borrowedQty, + '状态': '未盘点' + } + }) const wb = XLSX.utils.book_new() const ws1 = XLSX.utils.json_to_sheet(scannedData) @@ -507,7 +554,7 @@ const exportToExcel = () => { const wscols = [ {wch: 20}, {wch: 10}, {wch: 10}, {wch: 15}, {wch: 15}, {wch: 15}, {wch: 5}, {wch: 8}, - {wch: 8}, {wch: 8}, {wch: 8}, {wch: 8} + {wch: 8}, {wch: 8}, {wch: 8}, {wch: 8}, {wch: 8} ] ws1['!cols'] = wscols ws2['!cols'] = wscols