feat: add borrowed quantity column and update stocktake export formulas

Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
This commit is contained in:
dxc
2026-02-28 11:55:19 +08:00
parent cc33108e88
commit 4b29912f6f
2 changed files with 91 additions and 28 deletions

View File

@ -115,6 +115,22 @@ def clear_draft():
return jsonify({"message": "Cleared"}), 200 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']) @bp.route('/print/selection', methods=['POST'])

View File

@ -277,6 +277,8 @@ interface StockItem {
type?: string type?: string
category?: string category?: string
price?: number price?: number
source_table?: string
stock_id?: number
[key: string]: any [key: string]: any
} }
@ -293,6 +295,7 @@ const showQtyDialog = ref(false)
const allData = ref<StockItem[]>([]) const allData = ref<StockItem[]>([])
const scannedMap = ref<Map<string, number>>(new Map()) const scannedMap = ref<Map<string, number>>(new Map())
const borrowedQuantities = ref<Record<string, number>>({})
const filterType = ref('all') const filterType = ref('all')
const searchKeyword = ref('') const searchKeyword = ref('')
@ -307,6 +310,34 @@ const api = {
clearDraft: () => request({ url: '/v1/inbound/stock/draft/clear', method: 'post', data: { user_id: currentUser } }) 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<void> {
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 () => { onMounted(async () => {
await checkServerDraft() await checkServerDraft()
}) })
@ -381,7 +412,9 @@ const loadData = async () => {
qty_stock: stock, qty_stock: stock,
qty_actual: isScanned ? scannedMap.value.get(uuid)! : 0, qty_actual: isScanned ? scannedMap.value.get(uuid)! : 0,
scanned: isScanned, 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')) if (res.products) res.products.forEach((i: any) => processItem(i, 'product'))
allData.value = list allData.value = list
await fetchBorrowedQuantities(list)
} catch (e) { } catch (e) {
ElMessage.error('数据加载失败') ElMessage.error('数据加载失败')
} finally { loading.value = false } } finally { loading.value = false }
@ -471,34 +505,47 @@ const closeOverlays = () => {
const exportToExcel = () => { const exportToExcel = () => {
try { try {
// 1. 已盘点 Sheet // 1. 已盘点 Sheet
const scannedData = allData.value.filter(i => i.scanned).map(item => ({ const scannedData = allData.value.filter(i => i.scanned).map(item => {
'物品名称': item.name, const key = item.source_table && item.stock_id ? `${item.source_table}_${item.stock_id}` : ''
'类型': item.type || item.material_type || '-', const borrowedQty = borrowedQuantities.value[key] || 0
'类别': item.category || '-', const actualTotal = item.qty_actual + borrowedQty
'规格型号': item.spec_model || item.standard || '-', // ★ 双重保险 const diff = actualTotal - item.qty_stock
'SKU': item.sku, const result = diff === 0 ? '正常' : diff < 0 ? '盘亏/差异' : '盘盈'
'批次/SN': item.serial_number || item.batch_no || '-', return {
'单位': item.unit || '个', '物品名称': item.name,
'单价': item.price || item.unit_price || 0, '类型': item.type || item.material_type || '-',
'账面库存': parseFloat(item.qty_stock as any), '类别': item.category || '-',
'实盘数量': item.qty_actual, '规格型号': item.spec_model || item.standard || '-', // ★ 双重保险
'盘点结果': item.qty_stock === item.qty_actual ? '相符' : '差异', 'SKU': item.sku,
'差异数': item.qty_actual - item.qty_stock '批次/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 // 2. 未盘点 Sheet
const missingData = allData.value.filter(i => !i.scanned).map(item => ({ const missingData = allData.value.filter(i => !i.scanned).map(item => {
'物品名称': item.name, const key = item.source_table && item.stock_id ? `${item.source_table}_${item.stock_id}` : ''
'类型': item.type || item.material_type || '-', const borrowedQty = borrowedQuantities.value[key] || 0
'类别': item.category || '-', return {
'规格型号': item.spec_model || item.standard || '-', // ★ 双重保险 '物品名称': item.name,
'SKU': item.sku, '类型': item.type || item.material_type || '-',
'批次/SN': item.serial_number || item.batch_no || '-', '类别': item.category || '-',
'单位': item.unit || '', '规格型号': item.spec_model || item.standard || '-', // ★ 双重保险
'单价': item.price || item.unit_price || 0, 'SKU': item.sku,
'账面库存': parseFloat(item.qty_stock as any), '批次/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 wb = XLSX.utils.book_new()
const ws1 = XLSX.utils.json_to_sheet(scannedData) const ws1 = XLSX.utils.json_to_sheet(scannedData)
@ -507,7 +554,7 @@ const exportToExcel = () => {
const wscols = [ const wscols = [
{wch: 20}, {wch: 10}, {wch: 10}, {wch: 15}, {wch: 20}, {wch: 10}, {wch: 10}, {wch: 15},
{wch: 15}, {wch: 15}, {wch: 5}, {wch: 8}, {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 ws1['!cols'] = wscols
ws2['!cols'] = wscols ws2['!cols'] = wscols