feat(outbound): 出库流程强化(缺料防漏 + 完全移除直接出库)

① BOM 清单 SKU 改为规格型号(getBomWithStock 返回 children 无 sku,只有 child_spec)
② 缺货清单自动写入出库申请备注(借鸡生蛋:历史欠料永久留存在申请单)
③ 打印单底部增加欠料待补签收区(补料人签字/日期)
④ 有缺料时阻断式二次确认,防手快漏看
⑤ 完全移除直接出库模式,所有出库必须按单出库
   - create.vue 删除 radio 切换、direct 相关逻辑
   - 提交前强制校验已选择审批申请单
This commit is contained in:
yueli
2026-08-31 09:37:14 +08:00
parent a4bd6f54d4
commit b6ec45a8b8
2 changed files with 97 additions and 47 deletions

View File

@ -15,19 +15,14 @@
</div>
</template>
<!-- ★ 出库模式切换 -->
<!-- ★ 出库模式:仅支持按单出库(已移除直接出库) -->
<div class="mode-switch-bar">
<el-radio-group v-model="outboundMode" size="default" @change="handleModeChange">
<el-radio-button value="by-request">按单出库</el-radio-button>
<el-radio-button value="direct">直接出库</el-radio-button>
</el-radio-group>
<span class="mode-hint">
{{ outboundMode === 'by-request' ? '需先选择已审批通过的申请单' : '无需审批单,自由扫码出库' }}
</span>
<el-tag type="primary" size="large" style="font-size: 14px;">按单出库</el-tag>
<span class="mode-hint">请先选择已审批通过的申请单,再扫码出库</span>
</div>
<!-- ★ 按单出库:审批单选择 -->
<div v-if="outboundMode === 'by-request'" class="approval-request-select">
<div class="approval-request-select">
<el-select
v-model="selectedRequestId"
placeholder="请选择已审批通过的出库申请单"
@ -54,7 +49,7 @@
</div>
<!-- ★ 按单出库:计划清单预览 -->
<div v-if="outboundMode === 'by-request' && selectedRequest" class="planned-items-section">
<div v-if="selectedRequest" class="planned-items-section">
<div class="planned-header">
<span class="planned-title">计划出库清单</span>
<el-tag type="success" size="small">{{ selectedRequest.items?.length || 0 }} 种</el-tag>
@ -79,7 +74,7 @@
<div class="scan-section">
<template v-if="outboundMode === 'by-request' && !selectedRequestId">
<template v-if="!selectedRequestId">
<div class="camera-placeholder" style="background-color: #f5f5f5; cursor: not-allowed;">
<el-icon :size="40" color="#909399"><CameraFilled /></el-icon>
<span class="text">请先选择审批单</span>
@ -304,7 +299,7 @@ const barcodeRef = ref()
const formRef = ref()
// ★ 双轨制模式
const outboundMode = ref<'by-request' | 'direct'>('by-request') // 'by-request' | 'direct'
// ★ 已移除直接出库模式,所有出库必须按单出库(通过 selectedRequest 校验)
const approvalRequests = ref<any[]>([])
const selectedRequest = ref<any>(null)
const requestsLoading = ref(false)
@ -355,18 +350,6 @@ const selectedRequestId = computed({
const plannedItems = computed(() => selectedRequest.value?.items ?? [])
// ★ 模式切换
const handleModeChange = () => {
selectedRequest.value = null
selectedRequestId.value = null
cartItems.value = []
form.consumer_name = ''
form.remark = ''
signatureFile.value = null
signaturePreviewUrl.value = ''
barcodeInput.value = ''
}
// ★ 加载已审批通过的申请单
const loadApprovalRequests = async () => {
requestsLoading.value = true
@ -489,7 +472,7 @@ const handleManualInput = async () => {
if (!code) return
// ★ 按单出库模式:必须先选择申请单
if (outboundMode.value === 'by-request' && !selectedRequest.value) {
if (!selectedRequest.value) {
ElMessage.warning('请先选择要出库的审批申请单')
return
}
@ -503,14 +486,12 @@ const handleManualInput = async () => {
const item = cartItems.value[existIndex]
// ★ 按单模式:追加时仍需校验计划数量
if (outboundMode.value === 'by-request') {
const err = validateAgainstPlan(item.name, item.spec_model, 1)
if (err) {
ElMessage.error(err)
if (navigator.vibrate) navigator.vibrate([200, 100, 200])
barcodeInput.value = ''
return
}
const err = validateAgainstPlan(item.name, item.spec_model, 1)
if (err) {
ElMessage.error(err)
if (navigator.vibrate) navigator.vibrate([200, 100, 200])
barcodeInput.value = ''
return
}
const maxQty = parseFloat(item.available_quantity)
@ -540,14 +521,12 @@ const handleManualInput = async () => {
}
// ★ 按单模式:扫码加入前校验是否在计划清单内
if (outboundMode.value === 'by-request') {
const err = validateAgainstPlan(item.name, item.spec_model, 1)
if (err) {
ElMessage.error(err)
if (navigator.vibrate) navigator.vibrate([200, 100, 200])
barcodeInput.value = ''
return
}
const err = validateAgainstPlan(item.name, item.spec_model, 1)
if (err) {
ElMessage.error(err)
if (navigator.vibrate) navigator.vibrate([200, 100, 200])
barcodeInput.value = ''
return
}
// 加入购物车
@ -610,6 +589,11 @@ const submitForm = async () => {
}
if (!formRef.value) return
if (cartItems.value.length === 0) return ElMessage.warning('请先添加商品')
// ★ 按单出库:提交前必须已选择审批申请单
if (!selectedRequest.value) {
ElMessage.warning('请先选择要出库的审批申请单')
return
}
await formRef.value.validate(async (valid: boolean) => {
if (!valid) return
@ -653,7 +637,7 @@ const submitForm = async () => {
// 3. 组装发给后端的包
const submitPayload: any = {
outbound_type: form.outbound_type,
request_id: outboundMode.value === 'by-request' && selectedRequest.value ? selectedRequest.value.id : null,
request_id: selectedRequest.value ? selectedRequest.value.id : null,
consumer_name: form.consumer_name,
operator_name: form.operator_name,
remark: form.remark,