fix(scrap): 报废申请页搜索/选择解耦,审批人常驻必填
一、搜索丢失已选(核心 bug) 主表格 :data="stockList" 同时充当搜索结果与选择容器。Element Plus 的 setData 在 reserveSelection 为假时走 clearSelection(),进而 emit selection-change([]),故每次重新搜索都会清空选中态、「已选 N 条」归零。 (源码路径:table/src/store/index.mjs:34-52 → store/watcher.mjs:145-152) 改为「购物车 + 选择弹窗」结构,对齐出库选单页: · 主表格数据源改为 cart(已选清单),删除复选列,改「移除」按钮; · 新增「选择库存物品」弹窗,搜索框移入其中; · 表格 row-key + :reserve-selection="true",跨搜索/翻页保留勾选; · 点行勾选、改数量自动勾选、数字框 @click.stop 防冒泡反转勾选; · 服务端搜索(350ms 防抖)+ 分页 20/50/100/200; · uniqueKey 取 source_table_id(三表主键各自独立,必须带表名前缀)。 二、审批人字段不显示 原为 v-if="approvalVisible",仅库管代建或命中需审批物料时才渲染。 因全库仅 1 个物料标记 is_approval_required,该条件几乎从不成立。 现改为常驻,并用 el-form rules 声明 approver_id / remark 双必填; 打开弹窗即调用 checkScrapApproval 预检并展示提示文案。 三、顺带修复 库位列此前恒为空:三张库存表 to_dict 只输出 warehouse_loc,而模板绑定 warehouse_location。已在 loadStockList 中归一化。 SFC 编译与 vue-tsc 通过,无新增类型错误。
This commit is contained in:
@ -3,44 +3,180 @@
|
||||
<el-card shadow="always">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="title">报废申请</span>
|
||||
<div class="header-left">
|
||||
<span class="title">报废申请</span>
|
||||
<span class="subtitle">(请添加需要报废的物品)</span>
|
||||
</div>
|
||||
<div>
|
||||
<el-input
|
||||
v-model="keyword"
|
||||
placeholder="按名称/规格/SKU 搜索库存"
|
||||
style="width: 260px; margin-right: 10px;"
|
||||
clearable
|
||||
@keyup.enter="loadStock"
|
||||
@clear="loadStock"
|
||||
/>
|
||||
<el-button type="primary" @click="loadStock">查询</el-button>
|
||||
<el-button type="danger" :disabled="cart.length === 0" @click="clearCart">清空列表</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" :icon="Plus" @click="openManualSelect">手动添加库存</el-button>
|
||||
<el-button type="success" :icon="Select" :disabled="cart.length === 0" @click="openSubmitDialog">
|
||||
提交报废申请
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table :data="stockList" border height="480" v-loading="loading" @selection-change="onSelect">
|
||||
<el-table-column type="selection" width="50" />
|
||||
<el-alert
|
||||
v-if="cart.length === 0"
|
||||
title="清单为空,请点击右上角【手动添加库存】选择要报废的物品"
|
||||
type="info"
|
||||
center
|
||||
show-icon
|
||||
:closable="false"
|
||||
style="margin-bottom: 20px"
|
||||
/>
|
||||
|
||||
<!-- ★ 主表格 = 已选清单(购物车),不再承载搜索结果,避免搜索重置选中态 -->
|
||||
<el-table
|
||||
v-else
|
||||
:data="cart"
|
||||
border
|
||||
row-key="uniqueKey"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="typeLabel" label="类型" width="90" align="center" />
|
||||
<el-table-column prop="name" label="物料名称" min-width="170" show-overflow-tooltip />
|
||||
<el-table-column label="规格型号" min-width="150" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.standard || row.spec_model || '-' }}</template>
|
||||
<template #default="{ row }">{{ row.spec_model || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sku" label="SKU" width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="warehouse_location" label="库位" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="库位" width="120" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span style="color:#409EFF;">{{ row.location || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="批次/序列号" width="140" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.batch_number || row.serial_number || '-' }}</template>
|
||||
<template #default="{ row }">{{ row.batch_number || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="可用库存" width="110" align="right">
|
||||
<template #default="{ row }">{{ Number(row.available_quantity || 0) }}</template>
|
||||
<template #default="{ row }">{{ row.available_qty }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="本次报废" width="150" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.scrap_qty"
|
||||
:min="1"
|
||||
:max="row.available_qty"
|
||||
:precision="0"
|
||||
:controls="false"
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="danger" link @click="removeCartRow(row)">移除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div style="margin-top:12px; text-align:right;">
|
||||
<span style="margin-right:12px;color:#909399;">已选 {{ selected.length }} 条</span>
|
||||
<el-button type="primary" :disabled="selected.length===0" @click="openSubmitDialog">提交报废申请</el-button>
|
||||
<div v-if="cart.length > 0" class="cart-summary">
|
||||
共 <span class="num">{{ cart.length }}</span> 种物品,
|
||||
合计报废 <span class="num">{{ totalScrapCount }}</span> 件
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- ============================================================
|
||||
★ 选择库存物品(独立弹窗:搜索与已选解耦)
|
||||
reserve-selection + row-key 保证跨搜索/翻页保留勾选
|
||||
============================================================ -->
|
||||
<el-dialog
|
||||
v-model="manualDialogVisible"
|
||||
title="选择库存物品"
|
||||
width="85%"
|
||||
top="5vh"
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="filter-container">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="请输入物料名称 / 规格型号 / SKU 进行搜索"
|
||||
style="width: 320px"
|
||||
:prefix-icon="Search"
|
||||
clearable
|
||||
@input="filterStock"
|
||||
/>
|
||||
<span class="filter-tip">
|
||||
提示:点击表格行可勾选;<span style="color:#F56C6C;font-weight:bold;">修改数量会自动勾选</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
ref="manualTableRef"
|
||||
:data="stockList"
|
||||
v-loading="stockLoading"
|
||||
height="500"
|
||||
border
|
||||
row-key="uniqueKey"
|
||||
@selection-change="handleStockSelection"
|
||||
@row-click="handleRowClick"
|
||||
style="cursor: pointer"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" :reserve-selection="true" />
|
||||
<el-table-column label="类型" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" type="info">{{ row.typeLabel }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="名称" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="规格" min-width="140" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.standard || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sku" label="SKU" width="130" show-overflow-tooltip />
|
||||
<el-table-column label="库位" width="120" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.warehouse_location || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="批次/序列号" width="140" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.batch_number || row.serial_number || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="available_quantity" label="可用库存" width="110" align="right" />
|
||||
<el-table-column label="本次报废" width="150" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<!-- ★ @click.stop:防止点击数字框冒泡触发 @row-click 反转勾选 -->
|
||||
<el-input-number
|
||||
v-model="row.scrap_qty"
|
||||
:min="1"
|
||||
:max="Number(row.available_quantity || 0)"
|
||||
:precision="0"
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
placeholder="0"
|
||||
@click.stop
|
||||
@change="(val: any) => handleManualQuantityChange(val, row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
style="margin-top: 12px; justify-content: flex-end; display: flex;"
|
||||
v-model:current-page="stockPage"
|
||||
v-model:page-size="stockPageSize"
|
||||
:total="stockTotal"
|
||||
:page-sizes="[20, 50, 100, 200]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
background
|
||||
@size-change="handleStockSizeChange"
|
||||
@current-change="handleStockPageChange"
|
||||
/>
|
||||
|
||||
<template #footer>
|
||||
<span style="float:left; line-height:32px; color:#909399;">
|
||||
已勾选 <b style="color:#409EFF;">{{ tempSelection.length }}</b> 项
|
||||
<span v-if="alreadyInCartCount > 0" style="margin-left:6px;">
|
||||
(其中 {{ alreadyInCartCount }} 项已在清单中)
|
||||
</span>
|
||||
</span>
|
||||
<el-button @click="manualDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmManualAdd">确认添加</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 提交报废申请 -->
|
||||
<el-dialog v-model="dialogVisible" title="提交报废申请" width="760px" destroy-on-close :close-on-click-modal="false">
|
||||
<el-table :data="cart" border size="small" max-height="300">
|
||||
@ -62,15 +198,16 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-form label-width="100px" style="margin-top:14px;">
|
||||
<el-form-item v-if="approvalVisible" label="指定审批人">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px" style="margin-top:14px;">
|
||||
<!-- ★ 业务规则:所有报废申请一律需审批,审批人恒为必填项 -->
|
||||
<el-form-item label="指定审批人" prop="approver_id">
|
||||
<div v-if="approvalText" style="color:#E6A23C; font-size:12px; line-height:1.4; margin-bottom:4px;">{{ approvalText }}</div>
|
||||
<el-select v-model="approverId" placeholder="请选择审批人" style="width:100%" filterable>
|
||||
<el-select v-model="form.approver_id" placeholder="请选择审批人" style="width:100%" filterable>
|
||||
<el-option v-for="u in approvers" :key="u.id" :label="u.username" :value="u.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="报废原因" required>
|
||||
<el-input v-model="remark" type="textarea" :rows="2" placeholder="请填写报废原因(必填)" maxlength="200" show-word-limit />
|
||||
<el-form-item label="报废原因" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请填写报废原因(必填)" maxlength="200" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
@ -83,46 +220,188 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getStockList } from '@/api/inbound/stock'
|
||||
import { ref, reactive, computed, onBeforeUnmount } from 'vue'
|
||||
import { ElMessage, ElMessageBox, ElTable, ElForm } from 'element-plus'
|
||||
import { Plus, Search, Select } from '@element-plus/icons-vue'
|
||||
import { getScrapStockList } from '@/api/scrap'
|
||||
import { getApproversList } from '@/api/auth'
|
||||
import { submitScrapRequest, checkScrapApproval } from '@/api/scrap'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const loading = ref(false)
|
||||
const keyword = ref('')
|
||||
const stockList = ref<any[]>([])
|
||||
const selected = ref<any[]>([])
|
||||
// --- 已选清单(唯一数据源,主表格 / 选择弹窗 / 提交弹窗共享同一份对象) ---
|
||||
const cart = ref<any[]>([])
|
||||
|
||||
// --- 选择库存物品弹窗 ---
|
||||
const manualDialogVisible = ref(false)
|
||||
const manualTableRef = ref<InstanceType<typeof ElTable>>()
|
||||
const stockList = ref<any[]>([]) // 弹窗内搜索结果(服务端分页)
|
||||
const stockTotal = ref(0)
|
||||
const stockPage = ref(1)
|
||||
const stockPageSize = ref(20)
|
||||
const stockLoading = ref(false)
|
||||
const searchKeyword = ref('')
|
||||
const tempSelection = ref<any[]>([]) // 弹窗内暂存勾选
|
||||
let stockSearchTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
// --- 提交对话框 ---
|
||||
const dialogVisible = ref(false)
|
||||
const submitting = ref(false)
|
||||
const cart = ref<any[]>([])
|
||||
const remark = ref('')
|
||||
const approverId = ref<number | null>(null)
|
||||
const formRef = ref<InstanceType<typeof ElForm>>()
|
||||
const approvers = ref<any[]>([])
|
||||
const approvalVisible = ref(false)
|
||||
const approvalText = ref('')
|
||||
// ★ 预检结果:提交前由 checkScrapApproval 写入,供文案提示使用
|
||||
const isApprovalRequired = ref(false)
|
||||
|
||||
const isKeeper = () => (userStore.role || '').toUpperCase() === 'WAREHOUSE_MGR'
|
||||
const form = reactive({
|
||||
approver_id: null as number | null,
|
||||
remark: '',
|
||||
})
|
||||
|
||||
const loadStock = async () => {
|
||||
loading.value = true
|
||||
// ★ 业务规则:所有报废申请一律需审批 → 审批人与报废原因恒为必填
|
||||
const rules = {
|
||||
approver_id: [{ required: true, message: '请选择审批人', trigger: 'change' }],
|
||||
remark: [{ required: true, message: '请填写报废原因', trigger: 'blur' }],
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 库存行的唯一键:三张库存表主键各自独立,必须带表名前缀防跨表碰撞。
|
||||
// source_table 由后端稳定注入,取值恰好就是提交报废申请所需的 source_table。
|
||||
// ============================================================
|
||||
const stockKey = (row: any): string => `${row.source_table}_${row.id}`
|
||||
|
||||
// 库存行 → 购物车项
|
||||
const toCartItem = (row: any) => ({
|
||||
uniqueKey: stockKey(row),
|
||||
source_table: row.source_table,
|
||||
stock_id: row.id,
|
||||
base_id: row.base_id,
|
||||
name: row.name || row.material_name || '',
|
||||
spec_model: row.standard || row.spec_model || '',
|
||||
sku: row.sku || '',
|
||||
location: row.warehouse_location || row.warehouse_loc || '',
|
||||
batch_number: row.batch_number || row.serial_number || '',
|
||||
available_qty: Number(row.available_quantity || 0),
|
||||
scrap_qty: Number(row.scrap_qty) || 1,
|
||||
})
|
||||
|
||||
// --- 计算属性 ---
|
||||
const totalScrapCount = computed(() =>
|
||||
cart.value.reduce((sum, it) => sum + (Number(it.scrap_qty) || 0), 0)
|
||||
)
|
||||
const alreadyInCartCount = computed(() => {
|
||||
const keys = new Set(cart.value.map(it => it.uniqueKey))
|
||||
return tempSelection.value.filter(it => keys.has(stockKey(it))).length
|
||||
})
|
||||
|
||||
// --- 加载库存(服务端搜索 + 分页)---
|
||||
// 注意:不传 is_aggregated —— 报废要求 stock_id 精确到单条库存行,
|
||||
// 聚合后 id 会退化为组内代表项,导致「申请扣 A 行、实际报废 B 行」。
|
||||
const loadStockList = async () => {
|
||||
stockLoading.value = true
|
||||
try {
|
||||
const res: any = await getStockList({ page: 1, pageSize: 200, keyword: keyword.value.trim() })
|
||||
const items = res?.data?.items || res?.data?.list || res?.data || []
|
||||
stockList.value = items
|
||||
const res: any = await getScrapStockList({
|
||||
page: stockPage.value,
|
||||
pageSize: stockPageSize.value,
|
||||
keyword: searchKeyword.value.trim(),
|
||||
})
|
||||
// ★ uniqueKey 必须物化到行对象上:reserve-selection 依赖 row-key 真实存在
|
||||
stockList.value = (res.data?.list || []).map((item: any) => ({
|
||||
...item,
|
||||
uniqueKey: stockKey(item),
|
||||
// 后端 to_dict 只输出 warehouse_loc,统一归一化,否则库位列恒为空
|
||||
warehouse_location: item.warehouse_location || item.warehouse_loc || item.full_path || '',
|
||||
}))
|
||||
stockTotal.value = res.data?.total || 0
|
||||
} catch (e) {
|
||||
ElMessage.error('加载库存失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
stockLoading.value = false
|
||||
}
|
||||
}
|
||||
loadStock()
|
||||
|
||||
const onSelect = (rows: any[]) => { selected.value = rows }
|
||||
const openManualSelect = async () => {
|
||||
manualDialogVisible.value = true
|
||||
stockPage.value = 1
|
||||
searchKeyword.value = ''
|
||||
tempSelection.value = []
|
||||
await loadStockList()
|
||||
}
|
||||
|
||||
// 搜索防抖 → 服务端过滤
|
||||
const filterStock = () => {
|
||||
if (stockSearchTimer) clearTimeout(stockSearchTimer)
|
||||
stockSearchTimer = setTimeout(() => {
|
||||
stockPage.value = 1
|
||||
loadStockList()
|
||||
}, 350)
|
||||
}
|
||||
|
||||
const handleStockPageChange = (page: number) => {
|
||||
stockPage.value = page
|
||||
loadStockList()
|
||||
}
|
||||
|
||||
const handleStockSizeChange = (size: number) => {
|
||||
stockPageSize.value = size
|
||||
stockPage.value = 1
|
||||
loadStockList()
|
||||
}
|
||||
|
||||
const handleStockSelection = (val: any[]) => { tempSelection.value = val }
|
||||
|
||||
// 点击行任意位置切换勾选(undefined = 取反)
|
||||
const handleRowClick = (row: any) => {
|
||||
manualTableRef.value?.toggleRowSelection(row, undefined)
|
||||
}
|
||||
|
||||
// 弹窗内数量变化联动勾选
|
||||
const handleManualQuantityChange = (val: number | undefined, row: any) => {
|
||||
if (val && val > 0) manualTableRef.value?.toggleRowSelection(row, true)
|
||||
else manualTableRef.value?.toggleRowSelection(row, false)
|
||||
}
|
||||
|
||||
// 确认添加:按 uniqueKey 回查当前页行取权威 scrap_qty(tempSelection 可能是旧对象快照)
|
||||
const confirmManualAdd = () => {
|
||||
if (tempSelection.value.length === 0) return ElMessage.warning('请先勾选需要添加的物品')
|
||||
|
||||
const existing = new Set(cart.value.map(it => it.uniqueKey))
|
||||
const toAdd: any[] = []
|
||||
for (const it of tempSelection.value) {
|
||||
const key = stockKey(it)
|
||||
if (existing.has(key)) continue
|
||||
// 优先用当前 stockList 里的同名行,保证拿到的是最新数量
|
||||
const fresh = stockList.value.find(s => stockKey(s) === key) || it
|
||||
toAdd.push(toCartItem(fresh))
|
||||
existing.add(key)
|
||||
}
|
||||
|
||||
if (toAdd.length === 0) {
|
||||
manualDialogVisible.value = false
|
||||
return ElMessage.warning('选中的物品已全部在清单中')
|
||||
}
|
||||
|
||||
cart.value.push(...toAdd)
|
||||
manualDialogVisible.value = false
|
||||
tempSelection.value = []
|
||||
ElMessage.success(`成功添加 ${toAdd.length} 项物品`)
|
||||
}
|
||||
|
||||
// --- 清单维护 ---
|
||||
const removeCartRow = (row: any) => {
|
||||
const idx = cart.value.findIndex(it => it.uniqueKey === row.uniqueKey)
|
||||
if (idx > -1) cart.value.splice(idx, 1)
|
||||
}
|
||||
|
||||
const clearCart = () => {
|
||||
ElMessageBox.confirm('确定要清空已选清单吗?', '提示', { type: 'warning' })
|
||||
.then(() => { cart.value = [] })
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (stockSearchTimer) clearTimeout(stockSearchTimer)
|
||||
})
|
||||
|
||||
// --- 提交 ---
|
||||
const loadApprovers = async () => {
|
||||
try {
|
||||
const res: any = await getApproversList()
|
||||
@ -130,85 +409,86 @@ const loadApprovers = async () => {
|
||||
} catch (e) { approvers.value = [] }
|
||||
}
|
||||
|
||||
const openSubmitDialog = async () => {
|
||||
// 构造购物车(保留 source_table + stock_id 精准定位 + 快照字段)
|
||||
cart.value = selected.value.map((r: any) => ({
|
||||
source_table: r.source_table,
|
||||
stock_id: r.id,
|
||||
base_id: r.base_id,
|
||||
name: r.name || r.material_name || '',
|
||||
spec_model: r.standard || r.spec_model || '',
|
||||
sku: r.sku || '',
|
||||
location: r.warehouse_location || '',
|
||||
batch_number: r.batch_number || r.serial_number || '',
|
||||
available_qty: Number(r.available_quantity || 0),
|
||||
scrap_qty: 1,
|
||||
}))
|
||||
const buildItems = () => cart.value.map(it => ({
|
||||
source_table: it.source_table,
|
||||
stock_id: it.stock_id,
|
||||
scrap_qty: Number(it.scrap_qty || 0),
|
||||
name: it.name,
|
||||
spec_model: it.spec_model,
|
||||
sku: it.sku,
|
||||
location: it.location,
|
||||
batch_number: it.batch_number,
|
||||
}))
|
||||
|
||||
remark.value = ''
|
||||
approverId.value = null
|
||||
approvalText.value = ''
|
||||
// 库管代建默认显示审批人
|
||||
approvalVisible.value = isKeeper()
|
||||
if (approvalVisible.value) approvalText.value = '库管代建报废申请需审批,请选择审批人'
|
||||
loadApprovers()
|
||||
// ★ 预检:调用 checkScrapApproval,把结果写入 isApprovalRequired 并生成提示文案。
|
||||
// 所有报废申请一律需审批,此处主要用于向用户展示「哪些物料触发了审批」。
|
||||
const runApprovalPrecheck = async (items: any[]) => {
|
||||
try {
|
||||
const chk: any = await checkScrapApproval({ items })
|
||||
isApprovalRequired.value = true // 业务上恒需审批
|
||||
const flagged: any[] = chk?.data?.materials || []
|
||||
approvalText.value = flagged.length > 0
|
||||
? `以下物料需审批报废:${flagged.map((m: any) => `${m.name}(${m.spec_model || '-'})`).join(';')}`
|
||||
: '所有报废申请均需审批,请选择审批人'
|
||||
} catch (e) {
|
||||
isApprovalRequired.value = true
|
||||
approvalText.value = '所有报废申请均需审批,请选择审批人'
|
||||
}
|
||||
}
|
||||
|
||||
const openSubmitDialog = async () => {
|
||||
if (cart.value.length === 0) return ElMessage.warning('请先添加要报废的物品')
|
||||
|
||||
form.approver_id = null
|
||||
form.remark = ''
|
||||
isApprovalRequired.value = true
|
||||
approvalText.value = '所有报废申请均需审批,请选择审批人'
|
||||
formRef.value?.clearValidate()
|
||||
|
||||
await loadApprovers()
|
||||
// 打开弹窗即预检,让用户在填写前就看到需审批提示
|
||||
await runApprovalPrecheck(buildItems())
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const submit = async () => {
|
||||
if (!remark.value.trim()) {
|
||||
return ElMessage.warning('请填写报废原因')
|
||||
}
|
||||
const items = cart.value.map(it => ({
|
||||
source_table: it.source_table,
|
||||
stock_id: it.stock_id,
|
||||
scrap_qty: Number(it.scrap_qty || 0),
|
||||
name: it.name,
|
||||
spec_model: it.spec_model,
|
||||
sku: it.sku,
|
||||
location: it.location,
|
||||
batch_number: it.batch_number,
|
||||
}))
|
||||
if (!formRef.value) return
|
||||
// ★ 表单校验:审批人与报废原因均为必填
|
||||
await formRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
// ★ 预检:库管代建一律需审批;否则命中需审批物料才需审批
|
||||
const isK = isKeeper()
|
||||
let needApproval = isK
|
||||
let flagged: any[] = []
|
||||
submitting.value = true
|
||||
try {
|
||||
const chk: any = await checkScrapApproval({ items })
|
||||
needApproval = needApproval || !!chk?.data?.need_approval
|
||||
flagged = chk?.data?.materials || []
|
||||
} catch (e) { flagged = [] }
|
||||
approvalVisible.value = needApproval
|
||||
approvalText.value = isK && flagged.length === 0
|
||||
? '库管代建报废申请需审批,请选择审批人'
|
||||
: flagged.map((m: any) => `${m.name}(${m.spec_model || '-'})`).join(';')
|
||||
const items = buildItems()
|
||||
await runApprovalPrecheck(items)
|
||||
|
||||
if (needApproval && !approverId.value) {
|
||||
return ElMessage.warning(`${approvalText.value || '该申请需审批'}。请先选择审批人`)
|
||||
await submitScrapRequest({
|
||||
items,
|
||||
remark: form.remark.trim(),
|
||||
approver_id: form.approver_id,
|
||||
})
|
||||
ElMessage.success('报废申请已提交,待审批人审批')
|
||||
dialogVisible.value = false
|
||||
cart.value = []
|
||||
} catch (err: any) {
|
||||
// 拦截器已按后端 data.msg 弹业务错误,HTTP 错误不重复
|
||||
if (!err?.response) ElMessage.error('网络异常,请重试')
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
|
||||
await submitScrapRequest({
|
||||
items,
|
||||
remark: remark.value.trim(),
|
||||
approver_id: needApproval ? approverId.value : null,
|
||||
})
|
||||
ElMessage.success('报废申请已提交(含需审批物料时将进入审批,否则直接待库管执行)')
|
||||
dialogVisible.value = false
|
||||
selected.value = []
|
||||
loadStock()
|
||||
} catch (err: any) {
|
||||
// 拦截器已按后端 data.msg 弹业务错误,HTTP 错误不重复
|
||||
if (!err?.response) ElMessage.error('网络异常,请重试')
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-header { display: flex; justify-content: space-between; align-items: center; }
|
||||
.header-left { display: flex; align-items: baseline; gap: 8px; }
|
||||
.title { font-size: 18px; font-weight: bold; }
|
||||
.subtitle { font-size: 12px; color: #909399; }
|
||||
|
||||
.filter-container { display: flex; align-items: center; gap: 15px; margin-bottom: 12px; }
|
||||
.filter-tip { color: #909399; font-size: 12px; }
|
||||
|
||||
.cart-summary { margin-top: 15px; text-align: right; color: #606266; font-size: 14px; }
|
||||
.cart-summary .num { color: #F56C6C; font-weight: bold; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user