feat: add generate-missing logic to identify unscanned stock as inventory loss
This commit is contained in:
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user