feat(approval): 三端审批页「撤回」入口,明确告知会释放库存
后端已支持撤回时释放预占(见上一提交),前端同步:
一、文案与语义
按钮 完结 → 撤回,颜色 danger → warning(语义从「危险操作」变为
「可逆的库存释放」)。确认弹窗明确告知会释放多少项库存:
确定撤回申请单【APR-OUT-...】吗?
撤回后该单将被作废,其预占的 5 项物料库存会立即释放,
可供其它申请使用。此操作不可恢复。
用户看到的「1 件货」背后其实是一批被锁定的库存,不写清楚会让人
以为撤回只是「关掉一张单」。
二、报废新增撤回入口
报废审批页原先只有「执行报废」,没有撤回。补上按钮 + handleWithdraw(),
并新增 API 封装 withdrawScrapRequest()。
状态映射同步补充 4: '已撤回'(statusText / statusTagType)。
三、成功提示改为透传后端消息
后端会返回「申请单已撤回,释放 N 项预占库存」,比前端写死的文案
更有信息量,故改为 res?.msg 优先、前端文案兜底。
按钮可见性沿用 row.status === 1(已通过但未执行),与后端的执行守卫
(执行成功后 status 置 3)一致,故已执行或已撤回的单不会出现该按钮。
This commit is contained in:
@ -118,3 +118,11 @@ export function executeScrapByRequest(id: number, items: Array<{
|
|||||||
data: { items }
|
data: { items }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 10. 撤回已通过但尚未执行的报废申请(作废该单;若已接入预占则一并释放库存)
|
||||||
|
export function withdrawScrapRequest(id: number) {
|
||||||
|
return request({
|
||||||
|
url: `/v1/scrap/request/${id}/withdraw`,
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -176,17 +176,17 @@
|
|||||||
驳回
|
驳回
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
<!-- ★ 已通过的申请单:库管/主管可手动完结 -->
|
<!-- ★ 已通过但未执行的申请单:可撤回(会释放预占库存) -->
|
||||||
<template v-else-if="row.status === 1">
|
<template v-else-if="row.status === 1">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="canCompleteRequest"
|
v-if="canCompleteRequest"
|
||||||
type="danger"
|
type="warning"
|
||||||
plain
|
plain
|
||||||
size="small"
|
size="small"
|
||||||
:loading="row._closing"
|
:loading="row._closing"
|
||||||
@click="handleComplete(row)"
|
@click="handleComplete(row)"
|
||||||
>
|
>
|
||||||
完结
|
撤回
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
<span v-else style="color: #c0c4cc;">-</span>
|
<span v-else style="color: #c0c4cc;">-</span>
|
||||||
@ -251,21 +251,27 @@ const canCompleteRequest = computed(() =>
|
|||||||
userStore.username === 'IRIS'
|
userStore.username === 'IRIS'
|
||||||
)
|
)
|
||||||
|
|
||||||
// ★ 手动完结已通过的借库审批单(status 1 → 3,仿出库审批)
|
// ★ 撤回已通过但尚未执行的借库审批单(status 1 → 4)
|
||||||
|
//
|
||||||
|
// 撤回会把申请阶段预占的库存全部还回可用池(后端 release_reserved),
|
||||||
|
// 因此确认文案需明确告知 —— 用户看到的「1 件货」背后是一批被锁定的库存。
|
||||||
const handleComplete = (row: any) => {
|
const handleComplete = (row: any) => {
|
||||||
|
const itemCount = row.items?.length || 0
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm(
|
||||||
`确定强制完结审批单【${row.request_no}】吗?\n\n完结后该单标记为「已完成」,不可再扫码借出。`,
|
`确定撤回借库申请单【${row.request_no}】吗?\n\n` +
|
||||||
'⚠️ 强制完结确认',
|
`撤回后该单将作废,其预占的 ${itemCount} 项物料库存会立即释放,` +
|
||||||
{ confirmButtonText: '确认完结', cancelButtonText: '取消', type: 'warning' }
|
`可供其它申请使用。\n此操作不可恢复。`,
|
||||||
|
'⚠️ 撤回确认',
|
||||||
|
{ confirmButtonText: '确认撤回', cancelButtonText: '取消', type: 'warning' }
|
||||||
)
|
)
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
row._closing = true
|
row._closing = true
|
||||||
try {
|
try {
|
||||||
await closeBorrowRequest(row.id)
|
const res: any = await closeBorrowRequest(row.id)
|
||||||
ElMessage.success('审批单已完结')
|
ElMessage.success(res?.msg || `申请单 ${row.request_no} 已撤回`)
|
||||||
fetchData()
|
fetchData()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
ElMessage.error(e?.response?.data?.msg || e?.msg || e?.message || '完结失败')
|
ElMessage.error(e?.response?.data?.msg || e?.msg || e?.message || '撤回失败')
|
||||||
} finally {
|
} finally {
|
||||||
row._closing = false
|
row._closing = false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -118,12 +118,16 @@
|
|||||||
type="danger" size="small" :loading="row._approving" @click="openRejectDialog(row)"
|
type="danger" size="small" :loading="row._approving" @click="openRejectDialog(row)"
|
||||||
>驳回</el-button>
|
>驳回</el-button>
|
||||||
</template>
|
</template>
|
||||||
<!-- ★ 已通过(待执行):持有 scrap_execute 者可执行按单报废 -->
|
<!-- ★ 已通过(待执行):执行报废 / 撤回作废 -->
|
||||||
<template v-else-if="row.status === 1">
|
<template v-else-if="row.status === 1">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="canExecute"
|
v-if="canExecute"
|
||||||
type="warning" plain size="small" :loading="row._executing" @click="openExecute(row)"
|
type="warning" plain size="small" :loading="row._executing" @click="openExecute(row)"
|
||||||
>执行报废</el-button>
|
>执行报废</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="canExecute"
|
||||||
|
type="info" plain size="small" :loading="row._withdrawing" @click="handleWithdraw(row)"
|
||||||
|
>撤回</el-button>
|
||||||
</template>
|
</template>
|
||||||
<span v-else style="color: #c0c4cc;">-</span>
|
<span v-else style="color: #c0c4cc;">-</span>
|
||||||
</template>
|
</template>
|
||||||
@ -167,7 +171,7 @@ import { ref, computed, onMounted } from 'vue'
|
|||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { Refresh, Warning } from '@element-plus/icons-vue'
|
import { Refresh, Warning } from '@element-plus/icons-vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { getScrapApprovals, approveScrapRequest } from '@/api/scrap'
|
import { getScrapApprovals, approveScrapRequest, withdrawScrapRequest } from '@/api/scrap'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -199,11 +203,11 @@ const canExecute = computed(() =>
|
|||||||
|
|
||||||
// --- 工具 ---
|
// --- 工具 ---
|
||||||
const statusText = (status: number) => {
|
const statusText = (status: number) => {
|
||||||
const map: Record<number, string> = { 0: '待审批', 1: '已通过', 2: '已驳回', 3: '已执行' }
|
const map: Record<number, string> = { 0: '待审批', 1: '已通过', 2: '已驳回', 3: '已执行', 4: '已撤回' }
|
||||||
return map[status] ?? '-'
|
return map[status] ?? '-'
|
||||||
}
|
}
|
||||||
const statusTagType = (status: number) => {
|
const statusTagType = (status: number) => {
|
||||||
const map: Record<number, string> = { 0: 'warning', 1: 'success', 2: 'danger', 3: 'info' }
|
const map: Record<number, string> = { 0: 'warning', 1: 'success', 2: 'danger', 3: 'info', 4: 'info' }
|
||||||
return map[status] ?? 'info'
|
return map[status] ?? 'info'
|
||||||
}
|
}
|
||||||
const sourceLabel = (st: string) => ({ stock_buy: '采购件', stock_semi: '半成品', stock_product: '成品' } as any)[st] || st || '-'
|
const sourceLabel = (st: string) => ({ stock_buy: '采购件', stock_semi: '半成品', stock_product: '成品' } as any)[st] || st || '-'
|
||||||
@ -231,6 +235,7 @@ const fetchData = async () => {
|
|||||||
if (r.actual_approver_id && r.actual_approver_name) userNameCache.value[r.actual_approver_id] = r.actual_approver_name
|
if (r.actual_approver_id && r.actual_approver_name) userNameCache.value[r.actual_approver_id] = r.actual_approver_name
|
||||||
r._approving = false
|
r._approving = false
|
||||||
r._executing = false
|
r._executing = false
|
||||||
|
r._withdrawing = false
|
||||||
})
|
})
|
||||||
list.value = records
|
list.value = records
|
||||||
total.value = res.data?.total || records.length || 0
|
total.value = res.data?.total || records.length || 0
|
||||||
@ -286,6 +291,32 @@ const openExecute = (row: any) => {
|
|||||||
router.push({ path: '/scrap/create', query: { requestId: row.id } })
|
router.push({ path: '/scrap/create', query: { requestId: row.id } })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ★ 撤回已通过但尚未执行的报废申请
|
||||||
|
// 作废该单;若报废已接入库存预占,后端会一并释放预占量
|
||||||
|
const handleWithdraw = async (row: any) => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
`确定撤回报废申请【${row.request_no}】吗?\n\n` +
|
||||||
|
`撤回后该单将作废,其占用的物料库存会被释放,可供其它单据使用。\n` +
|
||||||
|
`此操作不可恢复。`,
|
||||||
|
'⚠️ 撤回确认',
|
||||||
|
{ confirmButtonText: '确认撤回', cancelButtonText: '取消', type: 'warning' }
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
row._withdrawing = true
|
||||||
|
try {
|
||||||
|
await withdrawScrapRequest(row.id)
|
||||||
|
ElMessage.success(`报废申请 ${row.request_no} 已撤回`)
|
||||||
|
await fetchData()
|
||||||
|
} catch (err: any) {
|
||||||
|
ElMessage.error(err?.msg || '撤回失败')
|
||||||
|
} finally {
|
||||||
|
row._withdrawing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => { fetchData() })
|
onMounted(() => { fetchData() })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -134,17 +134,17 @@
|
|||||||
驳回
|
驳回
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
<!-- ★ 已通过的申请单:库管/主管可手动完结 -->
|
<!-- ★ 已通过但未执行的申请单:可撤回(会释放预占库存) -->
|
||||||
<template v-else-if="row.status === 1">
|
<template v-else-if="row.status === 1">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="canCloseRequest"
|
v-if="canCloseRequest"
|
||||||
type="danger"
|
type="warning"
|
||||||
plain
|
plain
|
||||||
size="small"
|
size="small"
|
||||||
:loading="row._closing"
|
:loading="row._closing"
|
||||||
@click="handleCloseRequest(row)"
|
@click="handleCloseRequest(row)"
|
||||||
>
|
>
|
||||||
完结
|
撤回
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
<span v-else style="color: #c0c4cc;">-</span>
|
<span v-else style="color: #c0c4cc;">-</span>
|
||||||
@ -246,15 +246,20 @@ const canCloseRequest = computed(() =>
|
|||||||
userStore.username === 'IRIS'
|
userStore.username === 'IRIS'
|
||||||
)
|
)
|
||||||
|
|
||||||
// ★ 完结已通过的审批单
|
// ★ 撤回已通过但尚未执行的审批单
|
||||||
|
//
|
||||||
|
// 撤回会把申请阶段**预占**的库存全部还回可用池(后端 release_reserved),
|
||||||
|
// 因此确认文案需要明确告知这一点 —— 用户看到的「1 件货」背后其实是
|
||||||
|
// 一批被锁定的库存,撤回后别人就能申请了。
|
||||||
const handleCloseRequest = async (row: any) => {
|
const handleCloseRequest = async (row: any) => {
|
||||||
|
const itemCount = row.items?.length || 0
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(
|
await ElMessageBox.confirm(
|
||||||
`确定强制完结审批单【${row.request_no}】吗?\n\n` +
|
`确定撤回申请单【${row.request_no}】吗?\n\n` +
|
||||||
`此操作将使该单从「已通过」列表中移除,且不可恢复。\n` +
|
`撤回后该单将被作废,其预占的 ${itemCount} 项物料库存会立即释放,` +
|
||||||
`若该单已部分出库,请确认无需再关联此单。`,
|
`可供其它申请使用。\n此操作不可恢复。`,
|
||||||
'⚠️ 强制完结确认',
|
'⚠️ 撤回确认',
|
||||||
{ confirmButtonText: '确认完结', cancelButtonText: '取消', type: 'warning' }
|
{ confirmButtonText: '确认撤回', cancelButtonText: '取消', type: 'warning' }
|
||||||
)
|
)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return // 用户取消
|
return // 用户取消
|
||||||
@ -262,11 +267,11 @@ const handleCloseRequest = async (row: any) => {
|
|||||||
|
|
||||||
row._closing = true
|
row._closing = true
|
||||||
try {
|
try {
|
||||||
await closeRequest(row.id)
|
const res: any = await closeRequest(row.id)
|
||||||
ElMessage.success(`审批单 ${row.request_no} 已完结`)
|
ElMessage.success(res?.msg || `申请单 ${row.request_no} 已撤回`)
|
||||||
fetchData()
|
fetchData()
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
ElMessage.error(err?.msg || err?.message || '完结失败')
|
ElMessage.error(err?.msg || err?.message || '撤回失败')
|
||||||
} finally {
|
} finally {
|
||||||
row._closing = false
|
row._closing = false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user