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
}