feat: refactor stocktake import flow to use a preview-and-select dialog

This commit is contained in:
DXC
2026-03-19 15:16:20 +08:00
parent 05410b5b4d
commit ae63748060
2 changed files with 218 additions and 43 deletions

View File

@ -225,40 +225,121 @@ def get_stocks():
# --------------------------------------------------------
# 5. 一键引入盘点差异
# POST /api/v1/stock/adjustment/import-from-stocktake
# 5. 获取盘点差异列表
# GET /api/v1/stock/adjustment/stocktake-discrepancies
# --------------------------------------------------------
@adjustment_bp.route('/import-from-stocktake', methods=['POST'])
@adjustment_bp.route('/stocktake-discrepancies', methods=['GET'])
@jwt_required()
@permission_required('stock_adjustment:operation')
def import_from_stocktake():
"""从盘点差异记录导入为盘盈盘亏单"""
identity = get_jwt_identity()
operator = identity.get('username', 'system') if isinstance(identity, dict) else str(identity)
@permission_required('stock_adjustment:list')
def get_stocktake_discrepancies():
"""获取所有有差异的盘点记录"""
try:
# 查询所有有差异的盘点记录
drafts = StocktakeDraft.query.filter(StocktakeDraft.diff_qty != 0).all()
if not drafts:
return jsonify({'code': 200, 'msg': '暂无盘点差异记录', 'data': {'count': 0}})
count = 0
items = []
for draft in drafts:
# 判断盘盈/盘亏
diff = float(draft.diff_qty or 0)
if diff == 0:
continue
adjust_type = 'profit' if diff > 0 else 'loss'
adjust_quantity = abs(diff)
# 获取物料基础信息
base_id = None
material_name = ''
spec_model = ''
sku = ''
warehouse_location = ''
# 根据source_table获取对应的库存记录
stock_model = get_stock_model(draft.source_table)
if stock_model and draft.stock_id:
stock = stock_model.query.get(draft.stock_id)
if stock:
base_id = getattr(stock, 'base_id', None)
sku = getattr(stock, 'sku', None) or getattr(stock, 'SKU', '')
warehouse_location = getattr(stock, 'warehouse_location', '')
# 联表查询 MaterialBase
if base_id:
material = MaterialBase.query.get(base_id)
if material:
material_name = material.name
spec_model = material.spec_model
items.append({
'draft_id': draft.id,
'source_table': draft.source_table,
'stock_id': draft.stock_id,
'base_id': base_id,
'sku': sku,
'material_name': material_name,
'spec_model': spec_model,
'warehouse_location': warehouse_location,
'stock_qty': float(draft.stock_qty or 0),
'quantity': float(draft.quantity or 0),
'diff_qty': diff,
'adjust_type': 'profit' if diff > 0 else 'loss',
'remark': draft.remark or ''
})
return jsonify({
'code': 200,
'data': {
'items': items,
'total': len(items)
}
})
except Exception as e:
return jsonify({'code': 500, 'msg': f'获取盘点差异失败: {str(e)}'}), 500
# --------------------------------------------------------
# 6. 批量导入盘点差异(按需勾选)
# POST /api/v1/stock/adjustment/import-from-stocktake
# --------------------------------------------------------
@adjustment_bp.route('/import-from-stocktake', methods=['POST'])
@jwt_required()
@permission_required('stock_adjustment:operation')
def import_from_stocktake():
"""批量导入选中的盘点差异记录"""
identity = get_jwt_identity()
# 修复操作人保存为0的Bug确保保存真实的用户名
operator = identity.get('username', 'system') if isinstance(identity, dict) else str(identity)
if not operator or operator == '0':
operator = identity if identity else 'system'
data = request.get_json()
if not data or 'records' not in data:
return jsonify({'code': 400, 'msg': '缺少records参数'}), 400
records = data.get('records', [])
if not records:
return jsonify({'code': 400, 'msg': '请选择要导入的记录'}), 400
try:
count = 0
for item in records:
draft_id = item.get('draft_id')
reason = item.get('reason', '盘点差异导入')
# 获取对应的盘点记录
draft = StocktakeDraft.query.get(draft_id)
if not draft:
continue
diff = float(draft.diff_qty or 0)
if diff == 0:
continue
adjust_type = 'profit' if diff > 0 else 'loss'
adjust_quantity = abs(diff)
# 获取物料基础信息
base_id = None
sku = ''
warehouse_location = ''
stock_model = get_stock_model(draft.source_table)
if stock_model and draft.stock_id:
stock = stock_model.query.get(draft.stock_id)
@ -270,9 +351,6 @@ def import_from_stocktake():
# 生成调整单号
order_no = generate_order_no()
# 使用备注或默认原因
reason = draft.remark if draft.remark else '盘点差异自动生成'
# 创建调整单
adjustment = StockAdjustment(
order_no=order_no,
@ -291,7 +369,7 @@ def import_from_stocktake():
count += 1
db.session.commit()
return jsonify({'code': 200, 'msg': '导入成功', 'data': {'count': count}})
return jsonify({'code': 200, 'msg': f'成功导入 {count} 条记录', 'data': {'count': count}})
except Exception as e:
db.session.rollback()

View File

@ -130,6 +130,49 @@
style="margin-top: 15px; justify-content: center"
/>
</el-dialog>
<!-- 盘点差异审核弹窗 -->
<el-dialog v-model="showReviewDialog" title="盘点差异审核" width="1200px" :close-on-click-modal="false">
<div v-loading="reviewLoading">
<el-table :data="reviewList" border stripe ref="reviewTableRef" @selection-change="handleReviewSelectionChange">
<el-table-column type="selection" width="50" />
<el-table-column prop="sku" label="SKU" width="140" />
<el-table-column prop="material_name" label="物料名称" min-width="150" show-overflow-tooltip />
<el-table-column prop="spec_model" label="规格" width="120" show-overflow-tooltip />
<el-table-column prop="warehouse_location" label="库位" width="100" />
<el-table-column prop="stock_qty" label="账面数" width="80" align="center" />
<el-table-column prop="quantity" label="实盘数" width="80" align="center" />
<el-table-column prop="diff_qty" label="差异数" width="80" align="center">
<template #default="{ row }">
<span :style="{ color: row.diff_qty > 0 ? '#67C23A' : '#F56C6C', fontWeight: 'bold' }">
{{ row.diff_qty > 0 ? '+' : '' }}{{ row.diff_qty }}
</span>
</template>
</el-table-column>
<el-table-column prop="adjust_type" label="类型" width="80" align="center">
<template #default="{ row }">
<el-tag :type="row.adjust_type === 'profit' ? 'success' : 'danger'" size="small">
{{ row.adjust_type === 'profit' ? '盘盈' : '盘亏' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="reason" label="调整原因" min-width="180">
<template #default="{ row }">
<el-input v-model="row.reason" placeholder="请输入调整原因" size="small" />
</template>
</el-table-column>
</el-table>
<div v-if="reviewList.length === 0" style="text-align: center; padding: 40px; color: #909399">
暂无盘点差异记录
</div>
</div>
<template #footer>
<el-button @click="showReviewDialog = false">取消</el-button>
<el-button type="primary" :disabled="selectedReviewRows.length === 0" @click="handleImportSelected" :loading="importLoading">
勾选导入 ({{ selectedReviewRows.length }})
</el-button>
</template>
</el-dialog>
</div>
</template>
@ -182,6 +225,80 @@ const stockLimit = ref(20)
const stockKeyword = ref('')
const selectedStock = ref<any>(null)
// 盘点差异审核
const showReviewDialog = ref(false)
const reviewLoading = ref(false)
const reviewList = ref<any[]>([])
const selectedReviewRows = ref<any[]>([])
const reviewTableRef = ref()
const importLoading = ref(false)
// 获取盘点差异列表
async function fetchReviewList() {
reviewLoading.value = true
try {
const res = await request({
url: '/v1/stock/adjustment/stocktake-discrepancies',
method: 'get'
})
if (res.code === 200) {
// 为每条记录设置默认原因
reviewList.value = (res.data.items || []).map((item: any) => ({
...item,
reason: item.remark || '盘点差异导入'
}))
}
} catch (e) {
ElMessage.error('获取盘点差异失败')
} finally {
reviewLoading.value = false
}
}
// 打开审核弹窗
async function openReviewDialog() {
showReviewDialog.value = true
selectedReviewRows.value = []
await fetchReviewList()
}
// 勾选变化
function handleReviewSelectionChange(rows: any[]) {
selectedReviewRows.value = rows
}
// 导入选中的记录
async function handleImportSelected() {
if (selectedReviewRows.value.length === 0) {
ElMessage.warning('请选择要导入的记录')
return
}
importLoading.value = true
try {
const records = selectedReviewRows.value.map(row => ({
draft_id: row.draft_id,
reason: row.reason || '盘点差异导入'
}))
const res = await request({
url: '/v1/stock/adjustment/import-from-stocktake',
method: 'post',
data: { records }
})
if (res.code === 200) {
ElMessage.success(res.msg || '导入成功')
showReviewDialog.value = false
fetchData()
} else {
ElMessage.error(res.msg || '导入失败')
}
} catch (e) {
ElMessage.error('导入失败')
} finally {
importLoading.value = false
}
}
// 获取列表
async function fetchData() {
loading.value = true
@ -297,29 +414,9 @@ async function submitForm() {
}
}
// 一键引入盘点差异
// 一键引入盘点差异 - 打开审核弹窗
async function handleImportStocktake() {
try {
await ElMessageBox.confirm('确定要将当前所有的盘点差异导入为盘盈盘亏单吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
const res = await request({
url: '/v1/stock/adjustment/import-from-stocktake',
method: 'post'
})
if (res.code === 200) {
ElMessage.success(`成功导入 ${res.data.count} 条盘点差异记录`)
fetchData()
} else {
ElMessage.error(res.msg || '导入失败')
}
} catch (e) {
if (e !== 'cancel') {
ElMessage.error('导入失败')
}
}
await openReviewDialog()
}
onMounted(() => {