From d1dd3dd404304cd45b8f482adbb2a130039f5dc8 Mon Sep 17 00:00:00 2001 From: yueli Date: Thu, 10 Sep 2026 15:08:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(approval):=20=E4=B8=89=E7=AB=AF=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E9=A1=B5=E3=80=8C=E6=92=A4=E5=9B=9E=E3=80=8D=E5=85=A5?= =?UTF-8?q?=E5=8F=A3=EF=BC=8C=E6=98=8E=E7=A1=AE=E5=91=8A=E7=9F=A5=E4=BC=9A?= =?UTF-8?q?=E9=87=8A=E6=94=BE=E5=BA=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端已支持撤回时释放预占(见上一提交),前端同步: 一、文案与语义 按钮 完结 → 撤回,颜色 danger → warning(语义从「危险操作」变为 「可逆的库存释放」)。确认弹窗明确告知会释放多少项库存: 确定撤回申请单【APR-OUT-...】吗? 撤回后该单将被作废,其预占的 5 项物料库存会立即释放, 可供其它申请使用。此操作不可恢复。 用户看到的「1 件货」背后其实是一批被锁定的库存,不写清楚会让人 以为撤回只是「关掉一张单」。 二、报废新增撤回入口 报废审批页原先只有「执行报废」,没有撤回。补上按钮 + handleWithdraw(), 并新增 API 封装 withdrawScrapRequest()。 状态映射同步补充 4: '已撤回'(statusText / statusTagType)。 三、成功提示改为透传后端消息 后端会返回「申请单已撤回,释放 N 项预占库存」,比前端写死的文案 更有信息量,故改为 res?.msg 优先、前端文案兜底。 按钮可见性沿用 row.status === 1(已通过但未执行),与后端的执行守卫 (执行成功后 status 置 3)一致,故已执行或已撤回的单不会出现该按钮。 --- inventory-web/src/api/scrap.ts | 8 ++++ .../src/views/borrow/approval/index.vue | 26 ++++++++----- .../views/operation/scrap/approval/index.vue | 39 +++++++++++++++++-- .../src/views/outbound/approval/index.vue | 29 ++++++++------ 4 files changed, 76 insertions(+), 26 deletions(-) diff --git a/inventory-web/src/api/scrap.ts b/inventory-web/src/api/scrap.ts index 369736c..8e1b1fe 100644 --- a/inventory-web/src/api/scrap.ts +++ b/inventory-web/src/api/scrap.ts @@ -118,3 +118,11 @@ export function executeScrapByRequest(id: number, items: Array<{ data: { items } }) } + +// 10. 撤回已通过但尚未执行的报废申请(作废该单;若已接入预占则一并释放库存) +export function withdrawScrapRequest(id: number) { + return request({ + url: `/v1/scrap/request/${id}/withdraw`, + method: 'post' + }) +} diff --git a/inventory-web/src/views/borrow/approval/index.vue b/inventory-web/src/views/borrow/approval/index.vue index c310f2a..6eb8788 100644 --- a/inventory-web/src/views/borrow/approval/index.vue +++ b/inventory-web/src/views/borrow/approval/index.vue @@ -176,17 +176,17 @@ 驳回 - + - @@ -251,21 +251,27 @@ const canCompleteRequest = computed(() => userStore.username === 'IRIS' ) -// ★ 手动完结已通过的借库审批单(status 1 → 3,仿出库审批) +// ★ 撤回已通过但尚未执行的借库审批单(status 1 → 4) +// +// 撤回会把申请阶段预占的库存全部还回可用池(后端 release_reserved), +// 因此确认文案需明确告知 —— 用户看到的「1 件货」背后是一批被锁定的库存。 const handleComplete = (row: any) => { + const itemCount = row.items?.length || 0 ElMessageBox.confirm( - `确定强制完结审批单【${row.request_no}】吗?\n\n完结后该单标记为「已完成」,不可再扫码借出。`, - '⚠️ 强制完结确认', - { confirmButtonText: '确认完结', cancelButtonText: '取消', type: 'warning' } + `确定撤回借库申请单【${row.request_no}】吗?\n\n` + + `撤回后该单将作废,其预占的 ${itemCount} 项物料库存会立即释放,` + + `可供其它申请使用。\n此操作不可恢复。`, + '⚠️ 撤回确认', + { confirmButtonText: '确认撤回', cancelButtonText: '取消', type: 'warning' } ) .then(async () => { row._closing = true try { - await closeBorrowRequest(row.id) - ElMessage.success('审批单已完结') + const res: any = await closeBorrowRequest(row.id) + ElMessage.success(res?.msg || `申请单 ${row.request_no} 已撤回`) fetchData() } catch (e: any) { - ElMessage.error(e?.response?.data?.msg || e?.msg || e?.message || '完结失败') + ElMessage.error(e?.response?.data?.msg || e?.msg || e?.message || '撤回失败') } finally { row._closing = false } diff --git a/inventory-web/src/views/operation/scrap/approval/index.vue b/inventory-web/src/views/operation/scrap/approval/index.vue index cc8e85b..15854b3 100644 --- a/inventory-web/src/views/operation/scrap/approval/index.vue +++ b/inventory-web/src/views/operation/scrap/approval/index.vue @@ -118,12 +118,16 @@ type="danger" size="small" :loading="row._approving" @click="openRejectDialog(row)" >驳回 - + - @@ -167,7 +171,7 @@ import { ref, computed, onMounted } from 'vue' import { useRouter } from 'vue-router' import { Refresh, Warning } from '@element-plus/icons-vue' import { ElMessage, ElMessageBox } from 'element-plus' -import { getScrapApprovals, approveScrapRequest } from '@/api/scrap' +import { getScrapApprovals, approveScrapRequest, withdrawScrapRequest } from '@/api/scrap' import { useUserStore } from '@/stores/user' const router = useRouter() @@ -199,11 +203,11 @@ const canExecute = computed(() => // --- 工具 --- const statusText = (status: number) => { - const map: Record = { 0: '待审批', 1: '已通过', 2: '已驳回', 3: '已执行' } + const map: Record = { 0: '待审批', 1: '已通过', 2: '已驳回', 3: '已执行', 4: '已撤回' } return map[status] ?? '-' } const statusTagType = (status: number) => { - const map: Record = { 0: 'warning', 1: 'success', 2: 'danger', 3: 'info' } + const map: Record = { 0: 'warning', 1: 'success', 2: 'danger', 3: 'info', 4: 'info' } return map[status] ?? 'info' } 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 r._approving = false r._executing = false + r._withdrawing = false }) list.value = records 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 } }) } +// ★ 撤回已通过但尚未执行的报废申请 +// 作废该单;若报废已接入库存预占,后端会一并释放预占量 +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() }) diff --git a/inventory-web/src/views/outbound/approval/index.vue b/inventory-web/src/views/outbound/approval/index.vue index fe003b1..f25f5ac 100644 --- a/inventory-web/src/views/outbound/approval/index.vue +++ b/inventory-web/src/views/outbound/approval/index.vue @@ -134,17 +134,17 @@ 驳回 - + - @@ -246,15 +246,20 @@ const canCloseRequest = computed(() => userStore.username === 'IRIS' ) -// ★ 完结已通过的审批单 +// ★ 撤回已通过但尚未执行的审批单 +// +// 撤回会把申请阶段**预占**的库存全部还回可用池(后端 release_reserved), +// 因此确认文案需要明确告知这一点 —— 用户看到的「1 件货」背后其实是 +// 一批被锁定的库存,撤回后别人就能申请了。 const handleCloseRequest = async (row: any) => { + const itemCount = row.items?.length || 0 try { await ElMessageBox.confirm( - `确定强制完结审批单【${row.request_no}】吗?\n\n` + - `此操作将使该单从「已通过」列表中移除,且不可恢复。\n` + - `若该单已部分出库,请确认无需再关联此单。`, - '⚠️ 强制完结确认', - { confirmButtonText: '确认完结', cancelButtonText: '取消', type: 'warning' } + `确定撤回申请单【${row.request_no}】吗?\n\n` + + `撤回后该单将被作废,其预占的 ${itemCount} 项物料库存会立即释放,` + + `可供其它申请使用。\n此操作不可恢复。`, + '⚠️ 撤回确认', + { confirmButtonText: '确认撤回', cancelButtonText: '取消', type: 'warning' } ) } catch (e) { return // 用户取消 @@ -262,11 +267,11 @@ const handleCloseRequest = async (row: any) => { row._closing = true try { - await closeRequest(row.id) - ElMessage.success(`审批单 ${row.request_no} 已完结`) + const res: any = await closeRequest(row.id) + ElMessage.success(res?.msg || `申请单 ${row.request_no} 已撤回`) fetchData() } catch (err: any) { - ElMessage.error(err?.msg || err?.message || '完结失败') + ElMessage.error(err?.msg || err?.message || '撤回失败') } finally { row._closing = false }