refactor: redesign stocktake flow to require manual discrepancy audit and individual adjustments
This commit is contained in:
@ -25,6 +25,17 @@
|
||||
>
|
||||
继续上次盘点 <span class="sub-text">({{ serverDraftCount }}项)</span>
|
||||
</el-button>
|
||||
|
||||
<!-- ★ 新增: 查看差异报告按钮 -->
|
||||
<el-button
|
||||
type="info"
|
||||
plain
|
||||
size="large"
|
||||
class="action-btn-full"
|
||||
@click="goToVarianceReview"
|
||||
>
|
||||
📋 差异审核 <span class="sub-text">(查看历史差异)</span>
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="safe-tip">
|
||||
@ -247,37 +258,96 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="showFinishDialog" title="📊 盘点结算" width="90%" align-center :close-on-click-modal="false" class="preview-dialog">
|
||||
<div class="report-summary">
|
||||
<div class="summary-row"><span>截止时间:</span><span>{{ new Date().toLocaleString() }}</span></div>
|
||||
<div class="summary-stats">
|
||||
<div class="s-item"><div class="num">{{ stats.total }}</div><div class="txt">总数</div></div>
|
||||
<div class="s-item success"><div class="num">{{ stats.scanned }}</div><div class="txt">已盘</div></div>
|
||||
<div class="s-item error"><div class="num">{{ stats.total - stats.scanned }}</div><div class="txt">未盘</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="missing-list-header">差异/未盘预览</div>
|
||||
<el-table :data="varianceList" height="300" border size="small" style="margin-bottom: 10px;">
|
||||
<el-table-column prop="name" label="名称" show-overflow-tooltip />
|
||||
<el-table-column prop="batch_no" label="批次" width="90" show-overflow-tooltip />
|
||||
<el-table-column label="账/实" width="80" align="center">
|
||||
<template #default="scope">
|
||||
{{ parseFloat(scope.row.qty_stock) }} /
|
||||
<span :class="{'text-red': scope.row.scanned && scope.row.qty_actual !== scope.row.qty_stock}">
|
||||
{{ scope.row.scanned ? scope.row.qty_actual : '未' }}
|
||||
</span>
|
||||
<!-- ★ 新增: 盘点差异审核对话框 -->
|
||||
<el-dialog
|
||||
v-model="showVarianceDialog"
|
||||
title="📋 盘点差异审核"
|
||||
width="95%"
|
||||
align-center
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
class="variance-dialog"
|
||||
>
|
||||
<div v-loading="varianceLoading">
|
||||
<el-alert
|
||||
title="差异审核说明"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 15px;"
|
||||
>
|
||||
<template #default>
|
||||
以下列表显示所有已结束盘点但尚未平账的差异记录。请逐条核实后,点击"确认平账"按钮调整系统库存。
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-alert>
|
||||
|
||||
<el-table
|
||||
:data="varianceList"
|
||||
height="500"
|
||||
border
|
||||
stripe
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column prop="uuid" label="SKU/条码" width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="stock_name" label="物品名称" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="stock_spec" label="规格型号" width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="stock_location" label="库位" width="100" />
|
||||
<el-table-column label="账面库存" width="80" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.stock_qty }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实盘数量" width="80" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.quantity }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="差异" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
:type="scope.row.diff_qty > 0 ? 'success' : 'danger'"
|
||||
size="large"
|
||||
effect="dark"
|
||||
>
|
||||
{{ scope.row.diff_qty > 0 ? '+' : '' }}{{ scope.row.diff_qty }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="差异类型" width="80" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.diff_qty > 0 ? 'success' : 'danger'">
|
||||
{{ scope.row.diff_qty > 0 ? '盘盈' : '盘亏' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="80" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.is_processed" type="info">已处理</el-tag>
|
||||
<el-tag v-else type="warning">待审核</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="!scope.row.is_processed && userStore.hasPermission('inventory_stocktake:operation')"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleAdjust(scope.row)"
|
||||
>
|
||||
确认平账
|
||||
</el-button>
|
||||
<span v-else class="text-gray">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-empty v-if="varianceList.length === 0" description="暂无待审核的差异记录" />
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="showFinishDialog = false">返回修改</el-button>
|
||||
<div class="footer-right">
|
||||
<el-button type="success" @click="exportToExcel" :icon="Download">导出Excel</el-button>
|
||||
<el-button v-if="userStore.hasPermission('inventory_stocktake:operation')" type="danger" @click="finishStocktake" :loading="printing" :icon="Checked">结束</el-button>
|
||||
</div>
|
||||
<el-button @click="showVarianceDialog = false">关闭</el-button>
|
||||
<el-button type="success" @click="exportToExcel" :icon="Download">导出差异报告</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@ -337,10 +407,20 @@ const showList = ref(false)
|
||||
const showFinishDialog = ref(false)
|
||||
const showQtyDialog = ref(false)
|
||||
|
||||
// ★ 新增: 差异审核对话框
|
||||
const showVarianceDialog = ref(false)
|
||||
|
||||
const allData = ref<StockItem[]>([])
|
||||
const scannedMap = ref<Map<string, number>>(new Map())
|
||||
const borrowedQuantities = ref<Record<string, number>>({})
|
||||
|
||||
// ★ 新增: 会话ID
|
||||
const currentSessionId = ref<string>('')
|
||||
|
||||
// ★ 新增: 差异报告列表
|
||||
const varianceList = ref<any[]>([])
|
||||
const varianceLoading = ref(false)
|
||||
|
||||
const filterType = ref('all')
|
||||
const searchKeyword = ref('')
|
||||
|
||||
@ -349,9 +429,46 @@ const inputQty = ref<number | undefined>(undefined)
|
||||
const qtyInputRef = ref()
|
||||
|
||||
const api = {
|
||||
getDrafts: () => request({ url: '/v1/inbound/stock/draft/list', method: 'get', params: { user_id: currentUser } }),
|
||||
addDraft: (data: any) => request({ url: '/v1/inbound/stock/draft/add', method: 'post', data: { ...data, user_id: currentUser } }),
|
||||
clearDraft: () => request({ url: '/v1/inbound/stock/draft/clear', method: 'post', data: { user_id: currentUser } })
|
||||
getDrafts: (sessionId?: string) => request({
|
||||
url: '/v1/inbound/stock/draft/list',
|
||||
method: 'get',
|
||||
params: { user_id: currentUser, session_id: sessionId }
|
||||
}),
|
||||
addDraft: (data: any) => request({
|
||||
url: '/v1/inbound/stock/draft/add',
|
||||
method: 'post',
|
||||
data: { ...data, user_id: currentUser, session_id: currentSessionId.value }
|
||||
}),
|
||||
// ★ 新增: 开始新会话
|
||||
startNewSession: () => request({
|
||||
url: '/v1/inbound/stock/draft/start-new',
|
||||
method: 'post',
|
||||
data: { user_id: currentUser }
|
||||
}),
|
||||
// ★ 新增: 结束盘点
|
||||
finishStocktake: () => request({
|
||||
url: '/v1/inbound/stock/finish',
|
||||
method: 'post',
|
||||
data: { user_id: currentUser, session_id: currentSessionId.value }
|
||||
}),
|
||||
// ★ 新增: 获取差异报告
|
||||
getVarianceReport: () => request({
|
||||
url: '/v1/inbound/stock/variance-report',
|
||||
method: 'get',
|
||||
params: { user_id: currentUser }
|
||||
}),
|
||||
// ★ 新增: 单条库存调整
|
||||
adjustStock: (draftId: number, remark: string) => request({
|
||||
url: '/v1/inbound/stock/adjust',
|
||||
method: 'post',
|
||||
data: { draft_id: draftId, operator_name: currentUser, remark: remark }
|
||||
}),
|
||||
// ★ 保留清除功能(用于兼容性)
|
||||
clearDraft: () => request({
|
||||
url: '/v1/inbound/stock/draft/clear',
|
||||
method: 'post',
|
||||
data: { user_id: currentUser }
|
||||
})
|
||||
}
|
||||
|
||||
const typeToSourceTable = (type: string): string => {
|
||||
@ -388,31 +505,62 @@ onMounted(async () => {
|
||||
|
||||
const checkServerDraft = async () => {
|
||||
try {
|
||||
const res: any = await api.getDrafts()
|
||||
// 只获取未完成的草稿数量
|
||||
const res: any = await request({
|
||||
url: '/v1/inbound/stock/draft/list',
|
||||
method: 'get',
|
||||
params: { user_id: currentUser, is_finished: 'false' }
|
||||
})
|
||||
serverDraftCount.value = (res && res.length) || 0
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// ★ 重写: 开始新盘点 - 使用新 API
|
||||
const startNewSession = async () => {
|
||||
try {
|
||||
if (serverDraftCount.value > 0) {
|
||||
await ElMessageBox.confirm('存在未完成记录,开始新盘点将清除它们,确定吗?', '警告', { type: 'warning' })
|
||||
}
|
||||
btnLoading.value = true
|
||||
await api.clearDraft()
|
||||
// 调用新 API 开始新会话
|
||||
const res: any = await api.startNewSession()
|
||||
currentSessionId.value = res.session_id || ''
|
||||
scannedMap.value.clear()
|
||||
await loadData()
|
||||
isSessionActive.value = true
|
||||
} catch (e) {} finally { btnLoading.value = false }
|
||||
ElMessage.success('新盘点会话已开始')
|
||||
} catch (e: any) {
|
||||
if (e !== 'cancel') {
|
||||
ElMessage.error(e?.message || '操作失败')
|
||||
}
|
||||
} finally { btnLoading.value = false }
|
||||
}
|
||||
|
||||
// ★ 重写: 继续上次盘点
|
||||
const resumeSession = async () => {
|
||||
btnLoading.value = true
|
||||
try {
|
||||
const drafts: any = await api.getDrafts()
|
||||
// 获取最新的未完成会话
|
||||
const drafts: any = await request({
|
||||
url: '/v1/inbound/stock/draft/list',
|
||||
method: 'get',
|
||||
params: { user_id: currentUser, is_finished: 'false' }
|
||||
})
|
||||
|
||||
if (!drafts || drafts.length === 0) {
|
||||
ElMessage.warning('没有找到未完成的盘点记录')
|
||||
return
|
||||
}
|
||||
|
||||
// 从草稿中获取 session_id
|
||||
const sessionIds = [...new Set(drafts.map((d: any) => d.session_id))]
|
||||
currentSessionId.value = sessionIds[0]
|
||||
|
||||
const map = new Map<string, number>()
|
||||
drafts.forEach((d: any) => {
|
||||
map.set(d.uuid, d.quantity !== undefined ? parseFloat(d.quantity) : 1)
|
||||
if (d.session_id === currentSessionId.value) {
|
||||
map.set(d.uuid, d.quantity !== undefined ? parseFloat(d.quantity) : 1)
|
||||
}
|
||||
})
|
||||
scannedMap.value = map
|
||||
await loadData()
|
||||
@ -695,22 +843,71 @@ const openFinishDialog = () => {
|
||||
|
||||
const finishStocktake = async () => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要结束本次盘点吗?结束后当前进度将清空。', '结束确认', {
|
||||
await ElMessageBox.confirm('确定要结束本次盘点吗?结束后将进入差异审核流程。', '结束确认', {
|
||||
type: 'warning', confirmButtonText: '确定结束', cancelButtonText: '取消'
|
||||
})
|
||||
|
||||
printing.value = true
|
||||
await api.clearDraft()
|
||||
|
||||
// ★ 修改: 调用结束盘点 API,不再删除草稿
|
||||
const res: any = await api.finishStocktake()
|
||||
|
||||
// 结束会话
|
||||
scannedMap.value.clear()
|
||||
isSessionActive.value = false
|
||||
showFinishDialog.value = false
|
||||
checkServerDraft()
|
||||
|
||||
ElMessage.success('盘点已完成,会话已结束')
|
||||
} catch (e) {
|
||||
if (e !== 'cancel') ElMessage.error('操作失败')
|
||||
ElMessage.success('盘点已结束,请查看差异报告进行审核')
|
||||
|
||||
// ★ 新增: 自动打开差异审核对话框
|
||||
await openVarianceDialog()
|
||||
|
||||
} catch (e: any) {
|
||||
if (e !== 'cancel') ElMessage.error(e?.message || '操作失败')
|
||||
} finally { printing.value = false }
|
||||
}
|
||||
|
||||
// ★ 新增: 打开差异审核对话框
|
||||
const openVarianceDialog = async () => {
|
||||
varianceLoading.value = true
|
||||
showVarianceDialog.value = true
|
||||
try {
|
||||
const res: any = await api.getVarianceReport()
|
||||
varianceList.value = res.list || []
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.message || '获取差异报告失败')
|
||||
} finally {
|
||||
varianceLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 新增: 确认平账
|
||||
const handleAdjust = async (row: any) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要对 "${row.uuid}" 进行平账调整吗?\n\n差异: ${row.diff_qty > 0 ? '盘盈 +' : '盘亏 '}${row.diff_qty}`,
|
||||
'确认平账',
|
||||
{ type: 'warning', confirmButtonText: '确认调整', cancelButtonText: '取消' }
|
||||
)
|
||||
|
||||
const remark = `盘点差异调整 - ${row.diff_qty > 0 ? '盘盈入库' : '盘亏出库'}`
|
||||
|
||||
const res: any = await api.adjustStock(row.id, remark)
|
||||
|
||||
ElMessage.success(res.message || '调整成功')
|
||||
|
||||
// 刷新差异列表
|
||||
await openVarianceDialog()
|
||||
} catch (e: any) {
|
||||
if (e !== 'cancel') ElMessage.error(e?.message || '操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 新增: 跳转到差异审核页面
|
||||
const goToVarianceReview = () => {
|
||||
openVarianceDialog()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user