feat: add generate-missing logic to identify unscanned stock as inventory loss

This commit is contained in:
DXC
2026-03-19 15:34:54 +08:00
parent 6cc3d1b6e0
commit b37049a4d7
2 changed files with 138 additions and 1 deletions

View File

@ -1,5 +1,5 @@
from flask import Blueprint, jsonify, request, send_file
from app.extensions import db
from app.extensions import db, beijing_time
# ★★★ 修复点:必须引入 datetime否则下方更新时间时会报错 500 ★★★
from datetime import datetime, timedelta
from app.utils.decorators import permission_required
@ -889,3 +889,95 @@ def export_stocktake():
import traceback
traceback.print_exc()
return jsonify({"message": f"导出失败: {str(e)}"}), 500
# --------------------------------------------------------
# 生成漏盘数据 - 将未扫描的库存标记为全额盘亏
# POST /api/v1/inbound/stocktake/generate-missing
# --------------------------------------------------------
@bp.route('/stocktake/generate-missing', methods=['POST'])
@permission_required('inventory_stocktake:operation')
def generate_missing_stocktake():
"""
生成漏盘数据:
找出所有真实库存 > 0但未被盘点扫描到的物料
自动生成盘点草稿,标记为盘亏(实盘=0差异=-库存数)
"""
try:
# 1. 获取所有已有盘点记录的 (source_table, stock_id) 集合
existing_records = db.session.query(
StocktakeDraft.source_table,
StocktakeDraft.stock_id
).distinct().all()
scanned_keys = set()
for src_table, stock_id in existing_records:
if stock_id:
scanned_keys.add((src_table, stock_id))
# 2. 获取所有真实库存 > 0 的记录
all_stock = []
# 采购库存
for item in StockBuy.query.filter(StockBuy.stock_quantity > 0).all():
all_stock.append({
'source_table': 'stock_buy',
'stock_id': item.id,
'base_id': item.base_id,
'stock_qty': float(item.stock_quantity or 0)
})
# 半成品库存
if StockSemi:
for item in StockSemi.query.filter(StockSemi.stock_quantity > 0).all():
all_stock.append({
'source_table': 'stock_semi',
'stock_id': item.id,
'base_id': item.base_id,
'stock_qty': float(item.stock_quantity or 0)
})
# 成品库存
if StockProduct:
for item in StockProduct.query.filter(StockProduct.stock_quantity > 0).all():
all_stock.append({
'source_table': 'stock_product',
'stock_id': item.id,
'base_id': item.base_id,
'stock_qty': float(item.stock_quantity or 0)
})
# 3. 找出漏盘记录(库存中有但盘点中没有的)
missing_count = 0
for stock in all_stock:
key = (stock['source_table'], stock['stock_id'])
if key not in scanned_keys:
# 生成漏盘草稿
draft = StocktakeDraft(
user_id='system',
uuid=f'MISSING-{stock["source_table"]}-{stock["stock_id"]}',
quantity=0, # 实盘数为0
scan_time=beijing_time(),
session_id='AUTO_GENERATED',
source_table=stock['source_table'],
stock_id=stock['stock_id'],
stock_qty=stock['stock_qty'],
diff_qty=-stock['stock_qty'], # 差异 = 0 - 库存数 = 负数
remark='未盘点到,系统自动标记为盘亏'
)
db.session.add(draft)
missing_count += 1
db.session.commit()
return jsonify({
'code': 200,
'msg': f'成功生成 {missing_count} 条漏盘记录',
'data': {'count': missing_count}
})
except Exception as e:
db.session.rollback()
import traceback
traceback.print_exc()
return jsonify({'code': 500, 'msg': f'生成漏盘数据失败: {str(e)}'}), 500

View File

@ -107,6 +107,11 @@
结束盘点
</el-button>
</el-col>
<el-col :span="12">
<el-button v-if="userStore.hasPermission('inventory_stocktake:operation')" type="warning" size="large" class="w-100 action-btn" @click="handleGenerateMissing" :icon="Warning">
结束盘点 (计算漏盘)
</el-button>
</el-col>
</el-row>
</div>
</el-card>
@ -993,6 +998,46 @@ const openFinishDialog = () => {
finishStocktake()
}
// ★ 新增:结束盘点(计算漏盘)- 将未扫描的库存标记为全额盘亏
const handleGenerateMissing = async () => {
if (stats.value.total === 0) {
ElMessage.warning('暂无盘点数据')
return
}
try {
await ElMessageBox.confirm(
'确认结束当前盘点吗?系统将自动把所有未扫描到的库存标记为全额盘亏!',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
btnLoading.value = true
const res = await request({
url: '/v1/inbound/stocktake/generate-missing',
method: 'post'
})
if (res.code === 200) {
ElMessage.success(`成功生成 ${res.data.count} 条漏盘记录`)
// 刷新差异列表
await checkServerDraft()
} else {
ElMessage.error(res.msg || '生成漏盘数据失败')
}
} catch (e) {
if (e !== 'cancel') {
ElMessage.error('生成漏盘数据失败')
}
} finally {
btnLoading.value = false
}
}
// ★ 重写: 结束盘点 - 纯前端状态流转,不再调用后端
const finishStocktake = async () => {
try {