feat(outbound): 审批单手动完结/作废功能

- 后端: outbound_service 新增 close_request 方法(状态 1-已通过 → 4-已完结)
- 后端: 新增 POST /api/v1/outbound/request/<id>/close 接口
- 模型: status_text 数组增加「已完结」
- 前端: create.vue 审批单选择区新增「强制完结此单」按钮
  完结后刷新列表,该单从已通过下拉中移除
- 权限: 超级管理员/审批人/拥有 outbound_create:operation 的用户可完结(库管可操作)
This commit is contained in:
yueli
2026-08-31 10:15:33 +08:00
parent b6ec45a8b8
commit 39f3f8af45
5 changed files with 136 additions and 2 deletions

View File

@ -124,6 +124,17 @@ export function approveRequest(id: number, data: { action: 'approve' | 'reject';
})
}
/**
* 手动完结/作废已通过的出库申请单(状态 1-已通过 → 4-已完结)
* @param id 审批单ID
*/
export function closeRequest(id: number) {
return request({
url: `/v1/outbound/request/${id}/close`,
method: 'post'
})
}
/**
* BOM 匹配库存(替代前端 while(true) 全量加载)
* 根据 BOM 子件的 base_id 列表,服务端精确查询有库存的记录

View File

@ -46,6 +46,12 @@
</el-option>
</el-select>
<p class="select-tip">仅显示已通过(status=1)的审批单</p>
<div v-if="selectedRequest && userStore.hasPermission('outbound_create:operation')" style="margin-top: 8px; display: flex; align-items: center; gap: 8px;">
<el-button type="danger" plain size="small" :loading="closingRequest" @click="handleCloseRequest">
强制完结此单
</el-button>
<span style="color: #F56C6C; font-size: 12px;">作废后该单将从下拉列表移除,出库记录将无法关联此单</span>
</div>
</div>
<!-- ★ 按单出库:计划清单预览 -->
@ -284,7 +290,7 @@ import { ref, reactive, nextTick, onUnmounted, onMounted, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Scissor, EditPen, Delete, CameraFilled, Close, Refresh, Select } from '@element-plus/icons-vue'
import QrScanner from '@/components/QrScanner/index.vue'
import { getStockByBarcode, submitOutbound, getOutboundList, getApprovalRequestList } from '@/api/outbound'
import { getStockByBarcode, submitOutbound, getOutboundList, getApprovalRequestList, closeRequest } from '@/api/outbound'
import { uploadFile } from '@/api/common/upload'
import { useUserStore } from '@/stores/user'
@ -303,6 +309,7 @@ const formRef = ref()
const approvalRequests = ref<any[]>([])
const selectedRequest = ref<any>(null)
const requestsLoading = ref(false)
const closingRequest = ref(false)
// 签名相关
const showSignatureDialog = ref(false)
@ -380,6 +387,37 @@ const handleRequestChange = (val: number | null) => {
signaturePreviewUrl.value = ''
}
// ★ 强制完结当前选中的申请单(作废,状态 1-已通过 → 4-已完结)
const handleCloseRequest = async () => {
if (!selectedRequest.value) return ElMessage.warning('请先选择要完结的审批单')
const req = selectedRequest.value
try {
await ElMessageBox.confirm(
`确定强制完结审批单【${req.request_no}】吗?\n\n` +
`此操作将使该单从「已通过」列表中移除,且不可恢复。\n` +
`若该单已部分出库,请确认无需再关联此单。`,
'⚠️ 强制完结确认',
{ confirmButtonText: '确认完结', cancelButtonText: '取消', type: 'warning' }
)
} catch (e) {
return // 用户取消
}
closingRequest.value = true
try {
await closeRequest(req.id)
ElMessage.success(`审批单 ${req.request_no} 已完结`)
// 完结后刷新列表并清空当前选择
selectedRequest.value = null
await loadApprovalRequests()
} catch (err: any) {
ElMessage.error(err?.msg || err?.message || '完结失败')
} finally {
closingRequest.value = false
}
}
// ★ 按单出库模式:校验扫码是否在计划内
const validateAgainstPlan = (scannedName: string, scannedSpec: string, scannedQty: number): string | null => {
const normalizedName = scannedName.trim()