feat(borrow,outbound): 申请预检按需显示审批人——默认不显示,命中需审批才出现并必选

- 后端新增 /borrow/request/check-approval 与 /outbound/request/check-approval 预检接口
- 申请弹窗默认隐藏审批人;提交前预检:命中需审批→显示审批人并红字列物料、未选拦截;未命中→隐藏并直接提交(approver=null)
- 申请 items 补传 base_id 供后端 ID 精确判定
This commit is contained in:
yueli
2026-09-09 10:47:29 +08:00
parent d06ce45a72
commit 6bdc8a82f2
6 changed files with 138 additions and 22 deletions

View File

@ -429,6 +429,28 @@ def create_outbound_request():
return jsonify({'code': 500, 'msg': f'服务器内部错误: {str(e)}'}), 500 return jsonify({'code': 500, 'msg': f'服务器内部错误: {str(e)}'}), 500
# --------------------------------------------------------
# 4.1 出库申请预检(判断所选物料是否需审批,驱动前端是否显示审批人)
# POST /api/v1/outbound/request/check-approval
# --------------------------------------------------------
@outbound_bp.route('/request/check-approval', methods=['POST'])
@jwt_required()
@permission_required('outbound_selection')
def check_outbound_approval():
try:
data = request.get_json() or {}
items = data.get('items', []) or []
from app.services.approval_control import resolve_approval_control
need_approval, flagged = resolve_approval_control(items)
return jsonify({
"code": 200, "msg": "success",
"data": {"need_approval": need_approval, "materials": flagged}
}), 200
except Exception as e:
traceback.print_exc()
return jsonify({"code": 500, "msg": f"预检失败: {str(e)}"}), 500
# -------------------------------------------------------- # --------------------------------------------------------
# 5. 审批出库申请 # 5. 审批出库申请
# PATCH /api/v1/outbound/request/<id>/approve # PATCH /api/v1/outbound/request/<id>/approve

View File

@ -311,6 +311,25 @@ def close_borrow_request(request_id):
return jsonify({'code': 500, 'msg': f'服务器内部错误: {str(e)}'}), 500 return jsonify({'code': 500, 'msg': f'服务器内部错误: {str(e)}'}), 500
# --- 借库申请预检(判断所选物料是否需审批,驱动前端是否显示审批人) ---
@trans_bp.route('/borrow/request/check-approval', methods=['POST'])
@jwt_required()
@permission_required('op_borrow_apply')
def check_borrow_approval():
try:
data = request.get_json() or {}
items = data.get('items', []) or []
from app.services.approval_control import resolve_approval_control
need_approval, flagged = resolve_approval_control(items)
return jsonify({
"code": 200, "msg": "success",
"data": {"need_approval": need_approval, "materials": flagged}
}), 200
except Exception as e:
import traceback; traceback.print_exc()
return jsonify({"code": 500, "msg": f"预检失败: {str(e)}"}), 500
# --- 获取借库审批单列表 --- # --- 获取借库审批单列表 ---
@trans_bp.route('/borrow/request', methods=['GET']) @trans_bp.route('/borrow/request', methods=['GET'])
@jwt_required() @jwt_required()

View File

@ -99,6 +99,15 @@ export function submitOutboundRequest(data: {
}) })
} }
// 出库申请预检:判断所选物料是否需审批
export function checkOutboundApproval(data: { items: Array<{ name?: string; spec_model?: string }> }) {
return request({
url: '/v1/outbound/request/check-approval',
method: 'post',
data
})
}
/** /**
* 获取出库审批申请单列表 * 获取出库审批申请单列表
* @param params 支持 status, page, limit * @param params 支持 status, page, limit

View File

@ -149,6 +149,15 @@ export function submitBorrowRequest(data: {
}) })
} }
// 借库申请预检:判断所选物料是否需审批
export function checkBorrowApproval(data: { items: Array<{ name?: string; spec_model?: string }> }) {
return request({
url: '/v1/transactions/borrow/request/check-approval',
method: 'post',
data
})
}
/** /**
* 获取借库审批申请单列表 * 获取借库审批申请单列表
* @param params 支持 status, page, limit * @param params 支持 status, page, limit

View File

@ -394,7 +394,10 @@
</el-table> </el-table>
<el-form label-width="100px"> <el-form label-width="100px">
<el-form-item label="* 指定审批人" required> <el-form-item v-if="approvalNeedVisible" label="指定审批人">
<div v-if="approvalNeedText" style="color:#E6A23C; font-size:12px; line-height:1.4; margin-bottom:4px;">
以下物料需审批出库/借库{{ approvalNeedText }}请选择审批人
</div>
<el-select <el-select
v-model="requestApproverId" v-model="requestApproverId"
placeholder="请选择审批人" placeholder="请选择审批人"
@ -518,7 +521,7 @@ import { Printer, Search, Plus, Download, List } from '@element-plus/icons-vue'
import { ElMessage, ElTable, ElMessageBox } from 'element-plus' import { ElMessage, ElTable, ElMessageBox } from 'element-plus'
import { printSelectionList } from '@/api/inbound/stock' import { printSelectionList } from '@/api/inbound/stock'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { submitBorrowRequest, getBorrowStockList } from '@/api/transaction' import { submitBorrowRequest, getBorrowStockList, checkBorrowApproval } from '@/api/transaction'
import { bomMatchStock } from '@/api/outbound' import { bomMatchStock } from '@/api/outbound'
import { getApproversList } from '@/api/auth' import { getApproversList } from '@/api/auth'
import { getBomList, getBomDetail, getBomWithStock } from '@/api/bom' import { getBomList, getBomDetail, getBomWithStock } from '@/api/bom'
@ -554,6 +557,9 @@ const requestIndefinite = ref(false)
const requestApproverId = ref<number | null>(null) const requestApproverId = ref<number | null>(null)
const approvers = ref<any[]>([]) const approvers = ref<any[]>([])
const requestSubmitting = ref(false) const requestSubmitting = ref(false)
// 是否需审批(由提交前预检驱动,需审批才显示审批人输入框)
const approvalNeedVisible = ref(false)
const approvalNeedText = ref('')
const stockList = ref<any[]>([]) const stockList = ref<any[]>([])
const stockTotal = ref(0) const stockTotal = ref(0)
@ -1052,6 +1058,8 @@ const openRequestDialog = () => {
requestApproverId.value = null requestApproverId.value = null
requestExpectedReturn.value = '' requestExpectedReturn.value = ''
requestIndefinite.value = false requestIndefinite.value = false
approvalNeedVisible.value = false // 打开时默认隐藏审批人,命中需审批时由预检显示
approvalNeedText.value = ''
loadApprovers() loadApprovers()
requestDialogVisible.value = true requestDialogVisible.value = true
} }
@ -1096,23 +1104,44 @@ const buildRemarkWithShortage = (baseRemark: string): string => {
const confirmSubmitRequest = async () => { const confirmSubmitRequest = async () => {
requestSubmitting.value = true requestSubmitting.value = true
try { try {
const items = validSelectedItems.value.map((item: any) => ({
base_id: item.base_id ?? null,
name: item.name || '',
spec_model: item.standard || '',
warehouse_location: item.warehouse_location || '',
quantity: item.export_quantity || 0,
expected_return_time: requestIndefinite.value ? null : requestExpectedReturn.value,
is_indefinite: requestIndefinite.value
}))
// ★ 提交前预检:判断所选物料是否需审批 → 只有需审批才显示并必选审批人
let needApproval = false
let flagged: any[] = []
try {
const chk: any = await checkBorrowApproval({ items })
needApproval = !!chk?.data?.need_approval
flagged = chk?.data?.materials || []
} catch (e) { needApproval = false; flagged = [] }
approvalNeedVisible.value = needApproval
approvalNeedText.value = flagged.map((m: any) => `${m.name}${m.spec_model || '-'}`).join('')
if (needApproval && !requestApproverId.value) {
ElMessage.warning(`以下物料需审批出库/借库:${approvalNeedText.value}。请先选择审批人`)
return
}
const payload = { const payload = {
items: validSelectedItems.value.map((item: any) => ({ items,
name: item.name || '',
spec_model: item.standard || '',
warehouse_location: item.warehouse_location || '',
quantity: item.export_quantity || 0,
expected_return_time: requestIndefinite.value ? null : requestExpectedReturn.value,
is_indefinite: requestIndefinite.value
})),
remark: buildRemarkWithShortage(requestRemark.value.trim()), remark: buildRemarkWithShortage(requestRemark.value.trim()),
approver_id: requestApproverId.value approver_id: needApproval ? requestApproverId.value : null
} }
await submitBorrowRequest(payload) await submitBorrowRequest(payload)
requestDialogVisible.value = false requestDialogVisible.value = false
selectedItems.value = [] selectedItems.value = []
approvalNeedVisible.value = false
approvalNeedText.value = ''
ElMessage.success('借库申请已提交(含需审批物料时将进入审批,否则直接交库管执行)') ElMessage.success('借库申请已提交(含需审批物料时将进入审批,否则直接交库管执行)')
} catch (err: any) { } catch (err: any) {
ElMessage.error(err?.message || err?.msg || '提交申请失败,请重试') ElMessage.error(err?.message || err?.msg || '提交申请失败,请重试')

View File

@ -409,7 +409,10 @@
<el-option label="维修出库" value="REPAIR" /> <el-option label="维修出库" value="REPAIR" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="* 指定审批人" required> <el-form-item v-if="approvalNeedVisible" label="指定审批人">
<div v-if="approvalNeedText" style="color:#E6A23C; font-size:12px; line-height:1.4; margin-bottom:4px;">
以下物料需审批出库/借库{{ approvalNeedText }}请选择审批人
</div>
<el-select <el-select
v-model="requestApproverId" v-model="requestApproverId"
placeholder="请选择审批人" placeholder="请选择审批人"
@ -519,7 +522,7 @@ import { ElMessage, ElTable, ElMessageBox } from 'element-plus'
import { getStockList, printSelectionList } from '@/api/inbound/stock' import { getStockList, printSelectionList } from '@/api/inbound/stock'
import { getBomList, getBomDetail, getBomWithStock } from '@/api/bom' import { getBomList, getBomDetail, getBomWithStock } from '@/api/bom'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { submitOutboundRequest, bomMatchStock } from '@/api/outbound' import { submitOutboundRequest, bomMatchStock, checkOutboundApproval } from '@/api/outbound'
import { getApproversList } from '@/api/auth' import { getApproversList } from '@/api/auth'
const userStore = useUserStore() const userStore = useUserStore()
@ -551,6 +554,9 @@ const requestOutboundType = ref('') // 申请时必选出库类型,扫码出
const requestApproverId = ref<number | null>(null) const requestApproverId = ref<number | null>(null)
const approvers = ref<any[]>([]) const approvers = ref<any[]>([])
const requestSubmitting = ref(false) const requestSubmitting = ref(false)
// 是否需审批(由提交前预检驱动,需审批才显示审批人输入框)
const approvalNeedVisible = ref(false)
const approvalNeedText = ref('')
const stockList = ref<any[]>([]) // 服务端分页数据 const stockList = ref<any[]>([]) // 服务端分页数据
const stockTotal = ref(0) const stockTotal = ref(0)
@ -1079,6 +1085,8 @@ const openRequestDialog = () => {
} }
requestRemark.value = '' requestRemark.value = ''
requestApproverId.value = null requestApproverId.value = null
approvalNeedVisible.value = false // 打开时默认隐藏审批人,命中需审批时由预检显示
approvalNeedText.value = ''
loadApprovers() loadApprovers()
requestDialogVisible.value = true requestDialogVisible.value = true
} }
@ -1100,7 +1108,6 @@ const confirmSubmitRequest = async () => {
ElMessage.warning('请填写申请原因') ElMessage.warning('请填写申请原因')
return return
} }
// 审批人改为可选:默认不审批时无需选择;所选物料命中“需审批”但未选审批人时,后端会提示补选
if (!requestOutboundType.value) { if (!requestOutboundType.value) {
ElMessage.warning('请选择出库类型') ElMessage.warning('请选择出库类型')
return return
@ -1108,17 +1115,36 @@ const confirmSubmitRequest = async () => {
requestSubmitting.value = true requestSubmitting.value = true
try { try {
const items = validSelectedItems.value.map(item => ({
material_type: item.typeLabel || item.type || '',
base_id: item.base_id ?? null,
name: item.name || '',
spec_model: item.standard || '',
warehouse_location: item.warehouse_location || '',
quantity: item.export_quantity || 0
}))
// ★ 提交前预检:判断所选物料是否需审批 → 只有需审批才显示并必选审批人
let needApproval = false
let flagged: any[] = []
try {
const chk: any = await checkOutboundApproval({ items })
needApproval = !!chk?.data?.need_approval
flagged = chk?.data?.materials || []
} catch (e) { needApproval = false; flagged = [] }
approvalNeedVisible.value = needApproval
approvalNeedText.value = flagged.map((m: any) => `${m.name}${m.spec_model || '-'}`).join('')
if (needApproval && !requestApproverId.value) {
ElMessage.warning(`以下物料需审批出库/借库:${approvalNeedText.value}。请先选择审批人`)
return
}
const payload: any = { const payload: any = {
items: validSelectedItems.value.map(item => ({ items,
material_type: item.typeLabel || item.type || '',
name: item.name || '',
spec_model: item.standard || '',
warehouse_location: item.warehouse_location || '',
quantity: item.export_quantity || 0
})),
remark: buildRemarkWithShortage(trimmed), remark: buildRemarkWithShortage(trimmed),
outbound_type: requestOutboundType.value, outbound_type: requestOutboundType.value,
approver_id: requestApproverId.value approver_id: needApproval ? requestApproverId.value : null
} }
await submitOutboundRequest(payload) await submitOutboundRequest(payload)
@ -1126,6 +1152,8 @@ const confirmSubmitRequest = async () => {
// 成功:关闭弹窗、清空列表、提示 // 成功:关闭弹窗、清空列表、提示
requestDialogVisible.value = false requestDialogVisible.value = false
selectedItems.value = [] selectedItems.value = []
approvalNeedVisible.value = false
approvalNeedText.value = ''
ElMessage.success('出库申请已提交(含需审批物料时将进入审批,否则直接交库管执行)') ElMessage.success('出库申请已提交(含需审批物料时将进入审批,否则直接交库管执行)')
} catch (err: any) { } catch (err: any) {
ElMessage.error(err?.message || err?.msg || '提交申请失败,请重试') ElMessage.error(err?.message || err?.msg || '提交申请失败,请重试')