1. 新增预警表添加工单号字段,支持按工单号检索预警 2. 添加工单过滤在制品/绩效报表/预警中心查询 3. 补全WMS台账字段口径与权限配置 4. 修复备料单与数量不符处理流程 5. 新增配送进度/库位联动/自动出库功能 6. 修复AGV配送状态更新逻辑 7. 优化退料与库存管理细节
190 lines
9.6 KiB
Vue
190 lines
9.6 KiB
Vue
<template>
|
||
<el-card>
|
||
<el-alert type="info" :closable="false" style="margin-bottom:12px"
|
||
title="数量不符处理:工位终端接料时发现「实收 ≠ 应发」后上报,差异单汇总到本页。仓管/班组长按差异方向处理——少发走「补发」(自动生成补料单并叫 AGV)、多发走「退库」(生成退库单,WMS 确认后库存加回)、少量差异可「调整关闭」(不补不退,必须填说明)。" />
|
||
|
||
<el-row :gutter="12" style="margin-bottom:12px">
|
||
<el-col :span="6">
|
||
<el-card shadow="never" class="stat"><div class="stat-num warn">{{ stats.pending }}</div><div class="stat-label">待处理</div></el-card>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-card shadow="never" class="stat"><div class="stat-num ok">{{ stats.replenished }}</div><div class="stat-label">已补发</div></el-card>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-card shadow="never" class="stat"><div class="stat-num info">{{ stats.returned }}</div><div class="stat-label">已退库</div></el-card>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-card shadow="never" class="stat"><div class="stat-num">{{ stats.closed }}</div><div class="stat-label">已关闭</div></el-card>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<el-form inline>
|
||
<el-form-item label="状态">
|
||
<el-select v-model="query.status" clearable placeholder="全部" style="width:130px">
|
||
<el-option label="待处理" value="PENDING" />
|
||
<el-option label="已补发" value="REPLENISHED" />
|
||
<el-option label="已退库" value="RETURNED" />
|
||
<el-option label="已关闭" value="CLOSED" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="工位"><el-input v-model="query.stationNo" clearable style="width:90px" placeholder="如 3" @keyup.enter="search" /></el-form-item>
|
||
<el-form-item label="工单号"><el-input v-model="query.orderNo" clearable style="width:150px" @keyup.enter="search" /></el-form-item>
|
||
<el-form-item label="图号"><el-input v-model="query.materialCode" clearable style="width:150px" placeholder="不区分大小写" @keyup.enter="search" /></el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="search">查询</el-button>
|
||
<el-button @click="reset">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<el-table :data="list" border size="small" v-loading="loading">
|
||
<el-table-column prop="reportNo" label="差异单号" width="100" />
|
||
<el-table-column prop="stationNo" label="工位" width="70" align="center" />
|
||
<el-table-column prop="orderNo" label="工单号" min-width="130" show-overflow-tooltip />
|
||
<el-table-column prop="materialCode" label="图号" min-width="110" show-overflow-tooltip />
|
||
<el-table-column prop="materialName" label="物料名称" min-width="110" show-overflow-tooltip />
|
||
<el-table-column prop="sn" label="序列号" min-width="120" show-overflow-tooltip />
|
||
<el-table-column prop="planQty" label="应发" width="70" align="center" />
|
||
<el-table-column prop="actualQty" label="实收" width="70" align="center" />
|
||
<el-table-column label="差异" width="80" align="center">
|
||
<template #default="{row}">
|
||
<span :class="row.diffQty > 0 ? 'diff-short' : row.diffQty < 0 ? 'diff-over' : ''">
|
||
{{ row.diffQty > 0 ? '少 ' + row.diffQty : row.diffQty < 0 ? '多 ' + (-row.diffQty) : '-' }}
|
||
</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="cause" label="上报原因" min-width="130" show-overflow-tooltip />
|
||
<el-table-column label="上报时间" width="150">
|
||
<template #default="{row}">{{ fmtTime(row.createdAt) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="operator" label="上报人" width="90" />
|
||
<el-table-column label="状态" width="90">
|
||
<template #default="{row}">
|
||
<el-tag :type="statusTag(row.status)" size="small">{{ statusText(row.status) }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="处理方式" width="90">
|
||
<template #default="{row}">{{ handleText(row.handleType) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="handleDocNo" label="处理单据" min-width="170" show-overflow-tooltip />
|
||
<el-table-column prop="handleNote" label="处理说明" min-width="150" show-overflow-tooltip />
|
||
<el-table-column prop="handledBy" label="处理人" width="90" />
|
||
<el-table-column label="处理时间" width="150">
|
||
<template #default="{row}">{{ row.handledAt ? fmtTime(row.handledAt) : '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="210" fixed="right" align="center">
|
||
<template #default="{row}">
|
||
<template v-if="isPending(row.status) && can('produce.qty:handle')">
|
||
<el-button size="small" type="success" :disabled="row.diffQty <= 0" @click="doHandle(row, 'REPLENISH')">补发</el-button>
|
||
<el-button size="small" type="warning" :disabled="row.diffQty >= 0" @click="doHandle(row, 'RETURN')">退库</el-button>
|
||
<el-button size="small" @click="doHandle(row, 'ADJUST')">调整</el-button>
|
||
</template>
|
||
<span v-else class="muted">{{ isPending(row.status) ? '无处理权限' : '已处理' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<el-pagination style="margin-top:12px; justify-content:flex-end"
|
||
background layout="total, sizes, prev, pager, next"
|
||
:total="total" :current-page="query.page" :page-size="query.pageSize"
|
||
:page-sizes="[20, 50, 100]" @current-change="onPage" @size-change="onSize" />
|
||
</el-card>
|
||
<PageHelp :data="helpQtyReport" page="QtyReport" />
|
||
</template>
|
||
|
||
<script setup>
|
||
import { reactive, ref, onMounted } from 'vue'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import request from '../utils/request'
|
||
import { can } from '../utils/perm'
|
||
import { fmtTime } from '../utils/format'
|
||
import PageHelp from '../components/PageHelp.vue'
|
||
import { helpQtyReport } from '../help'
|
||
|
||
const query = reactive({ status: '', stationNo: '', orderNo: '', materialCode: '', page: 1, pageSize: 20 })
|
||
const list = ref([])
|
||
const total = ref(0)
|
||
const loading = ref(false)
|
||
const stats = reactive({ pending: 0, replenished: 0, returned: 0, closed: 0 })
|
||
|
||
const statusText = (s) => ({ PENDING: '待处理', PROCESSING: '处理中', REPLENISHED: '已补发', RETURNED: '已退库', ADJUSTED: '已关闭', CLOSED: '已关闭', RESOLVED: '已关闭' }[s] || s)
|
||
const statusTag = (s) => (s === 'PENDING' || s === 'PROCESSING' ? 'warning' : s === 'REPLENISHED' ? 'success' : s === 'RETURNED' ? 'primary' : 'info')
|
||
const handleText = (t) => ({ REPLENISH: '补发', RETURN: '退库', ADJUST: '调整关闭' }[t] || '-')
|
||
const isPending = (s) => s === 'PENDING' || s === 'PROCESSING'
|
||
|
||
async function load() {
|
||
loading.value = true
|
||
try {
|
||
const data = await request.get('/qty-reports', { params: query })
|
||
list.value = data?.list || []
|
||
total.value = data?.total || 0
|
||
} catch {
|
||
// 拦截器已提示
|
||
}
|
||
loading.value = false
|
||
}
|
||
async function loadStats() {
|
||
try {
|
||
const data = await request.get('/qty-reports/stats')
|
||
Object.assign(stats, data || {})
|
||
} catch {
|
||
// 统计失败不影响列表
|
||
}
|
||
}
|
||
function search() { query.page = 1; load() }
|
||
function reset() {
|
||
Object.assign(query, { status: '', stationNo: '', orderNo: '', materialCode: '', page: 1 })
|
||
load()
|
||
}
|
||
function onPage(p) { query.page = p; load() }
|
||
function onSize(s) { query.pageSize = s; query.page = 1; load() }
|
||
|
||
// 处理差异:补发(少发)/ 退库(多发)/ 调整关闭(不补不退,必须填说明)
|
||
async function doHandle(row, type) {
|
||
if (type === 'REPLENISH' && row.diffQty <= 0) return ElMessage.warning('本单为「多发」,不能补发;请选择退库或调整')
|
||
if (type === 'RETURN' && row.diffQty >= 0) return ElMessage.warning('本单为「少发」,不能退库;请选择补发或调整')
|
||
const title = type === 'REPLENISH' ? '补发' : type === 'RETURN' ? '退库' : '调整关闭'
|
||
const tip = type === 'REPLENISH'
|
||
? `将为「${row.materialCode}」生成补料单(数量 ${row.diffQty}),随后自动出库并叫 AGV 送到工位 ${row.stationNo}。`
|
||
: type === 'RETURN'
|
||
? `将为「${row.materialCode}」生成退库单(数量 ${-row.diffQty}),WMS 确认收货后库存加回。`
|
||
: '调整关闭:不补发也不退库,仅登记双方确认结果(必填说明)。'
|
||
let note = ''
|
||
try {
|
||
const { value } = await ElMessageBox.prompt(tip, title, {
|
||
inputPlaceholder: type === 'ADJUST' ? '必填:说明双方确认结果' : '选填:处理说明',
|
||
inputValidator: (v) => (type !== 'ADJUST' || (v && v.trim().length > 0)) || '调整关闭必须填写处理说明',
|
||
confirmButtonText: '确定处理',
|
||
type: 'warning'
|
||
})
|
||
note = (value || '').trim()
|
||
} catch {
|
||
return
|
||
}
|
||
try {
|
||
const data = await request.post('/qty-reports/handle', { id: row.id, handleType: type, note })
|
||
ElMessage.success(`处理完成:${statusText(data?.status)}${data?.handleDocNo ? '(' + data.handleDocNo + ')' : ''}`)
|
||
load()
|
||
loadStats()
|
||
} catch {
|
||
// 拦截器已提示
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
load()
|
||
loadStats()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.stat { text-align: center; }
|
||
.stat-num { font-size: 22px; font-weight: 600; }
|
||
.stat-num.warn { color: #E6A23C; }
|
||
.stat-num.ok { color: #67C23A; }
|
||
.stat-num.info { color: #409EFF; }
|
||
.stat-label { font-size: 12px; color: #909399; margin-top: 4px; }
|
||
.diff-short { color: #E6A23C; font-weight: 600; }
|
||
.diff-over { color: #F56C6C; font-weight: 600; }
|
||
.muted { color: #909399; font-size: 12px; }
|
||
</style>
|