|
|
|
|
@ -2092,6 +2092,10 @@ const formData = ref({})
|
|
|
|
|
// 将 CSV 字符串转换为数组(用于组件)
|
|
|
|
|
const getFlowArrayValue = (fieldKey) => {
|
|
|
|
|
const value = formData.value[fieldKey]
|
|
|
|
|
return parseFlowIdsFromValue(value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const parseFlowIdsFromValue = (value) => {
|
|
|
|
|
if (!value) return null
|
|
|
|
|
if (Array.isArray(value)) return value
|
|
|
|
|
if (typeof value === 'string' && value.includes(',')) {
|
|
|
|
|
@ -2108,23 +2112,26 @@ const getFlowArrayValue = (fieldKey) => {
|
|
|
|
|
// 处理流程值变化(数组转 CSV)
|
|
|
|
|
const handleFlowValueChange = async (fieldKey, flowIds) => {
|
|
|
|
|
console.log('handleFlowValueChange 被调用:', { fieldKey, flowIds })
|
|
|
|
|
const prevFlowIds = parseFlowIdsFromValue(formData.value[fieldKey]) || []
|
|
|
|
|
const nextFlowIds = Array.isArray(flowIds) ? dedupeFlowIds(flowIds) : []
|
|
|
|
|
const removedFlowIds = diffFlowIds(prevFlowIds, nextFlowIds)
|
|
|
|
|
|
|
|
|
|
if (!flowIds || flowIds.length === 0) {
|
|
|
|
|
if (nextFlowIds.length === 0) {
|
|
|
|
|
formData.value[fieldKey] = null
|
|
|
|
|
// 清空relation数据
|
|
|
|
|
clearRelationDataForField(fieldKey)
|
|
|
|
|
clearRelationDataForFlowIds(removedFlowIds.length > 0 ? removedFlowIds : prevFlowIds)
|
|
|
|
|
} else {
|
|
|
|
|
// 转换为 CSV 字符串
|
|
|
|
|
formData.value[fieldKey] = flowIds.map(id => String(id)).join(',')
|
|
|
|
|
formData.value[fieldKey] = nextFlowIds.map(id => String(id)).join(',')
|
|
|
|
|
clearRelationDataForFlowIds(removedFlowIds)
|
|
|
|
|
|
|
|
|
|
console.log('流程ID已设置:', formData.value[fieldKey])
|
|
|
|
|
|
|
|
|
|
// 智能填充:从第一个流程实例中提取项目名称
|
|
|
|
|
await smartFillProjectName(flowIds[0])
|
|
|
|
|
await smartFillProjectName(nextFlowIds[0])
|
|
|
|
|
|
|
|
|
|
// 先加载流程详情到 flowDetailsMap(用于显示流程信息)
|
|
|
|
|
try {
|
|
|
|
|
const flowDetailsResponse = await plannedExpenditureAPI.getOaFlowDetails(flowIds)
|
|
|
|
|
const flowDetailsResponse = await plannedExpenditureAPI.getOaFlowDetails(nextFlowIds)
|
|
|
|
|
if (flowDetailsResponse.code === 0 && flowDetailsResponse.data) {
|
|
|
|
|
Object.keys(flowDetailsResponse.data).forEach(flowId => {
|
|
|
|
|
flowDetailsMap.value[flowId] = flowDetailsResponse.data[flowId]
|
|
|
|
|
@ -2136,11 +2143,33 @@ const handleFlowValueChange = async (fieldKey, flowIds) => {
|
|
|
|
|
|
|
|
|
|
// 加载relation字段数据
|
|
|
|
|
console.log('开始加载relation字段数据...')
|
|
|
|
|
await loadRelationFieldsData(flowIds, fieldKey)
|
|
|
|
|
await loadRelationFieldsData(nextFlowIds, fieldKey)
|
|
|
|
|
console.log('relation字段数据加载完成,当前数据:', relationFieldsData.value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const dedupeFlowIds = (flowIds) => {
|
|
|
|
|
const seen = new Set()
|
|
|
|
|
const result = []
|
|
|
|
|
flowIds.forEach(id => {
|
|
|
|
|
if (id === null || id === undefined || id === '') {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
const key = String(id)
|
|
|
|
|
if (seen.has(key)) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
seen.add(key)
|
|
|
|
|
result.push(id)
|
|
|
|
|
})
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const diffFlowIds = (oldIds, newIds) => {
|
|
|
|
|
const newIdSet = new Set((newIds || []).map(id => String(id)))
|
|
|
|
|
return (oldIds || []).filter(id => !newIdSet.has(String(id)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 清空指定字段的relation数据
|
|
|
|
|
const clearRelationDataForField = (fieldKey) => {
|
|
|
|
|
// 找到对应的流程ID(从formData中获取)
|
|
|
|
|
@ -2148,7 +2177,14 @@ const clearRelationDataForField = (fieldKey) => {
|
|
|
|
|
if (!flowIdsStr) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
const flowIds = flowIdsStr.split(',').map(id => id.trim()).filter(id => id)
|
|
|
|
|
const flowIds = parseFlowIdsFromValue(flowIdsStr) || []
|
|
|
|
|
clearRelationDataForFlowIds(flowIds)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const clearRelationDataForFlowIds = (flowIds) => {
|
|
|
|
|
if (!flowIds || flowIds.length === 0) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
flowIds.forEach(flowId => {
|
|
|
|
|
Object.keys(relationFieldsData.value).forEach(key => {
|
|
|
|
|
if (key.startsWith(`${flowId}_`)) {
|
|
|
|
|
@ -4360,6 +4396,64 @@ const loadFlowDetailsForPreview = async () => {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const buildDraftPayload = () => ({
|
|
|
|
|
category_id: selectedCategory.value?.id,
|
|
|
|
|
template_id: selectedTemplate.value?.id,
|
|
|
|
|
form_data: formData.value,
|
|
|
|
|
save_action: 'draft',
|
|
|
|
|
contract_id: allowsContractSelection(selectedCategory.value) && selectedExpenditureContract.value
|
|
|
|
|
? selectedExpenditureContract.value.id
|
|
|
|
|
: null
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const syncDraftIdToRoute = async () => {
|
|
|
|
|
if (!draftId.value || String(route.query.draftId || '') === String(draftId.value)) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
await router.replace({
|
|
|
|
|
path: route.path,
|
|
|
|
|
query: {
|
|
|
|
|
...route.query,
|
|
|
|
|
draftId: draftId.value
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const saveDraftSilently = async () => {
|
|
|
|
|
const submitData = buildDraftPayload()
|
|
|
|
|
let response
|
|
|
|
|
if (draftId.value) {
|
|
|
|
|
response = await plannedExpenditureAPI.update(draftId.value, submitData)
|
|
|
|
|
} else {
|
|
|
|
|
response = await plannedExpenditureAPI.create(submitData)
|
|
|
|
|
if (response.code === 0 && response.data?.id) {
|
|
|
|
|
draftId.value = response.data.id
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (response.code !== 0) {
|
|
|
|
|
throw new Error(response.msg || '暂存失败')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await syncDraftIdToRoute()
|
|
|
|
|
return response
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ensureDraftBeforeMainFlowStep = async () => {
|
|
|
|
|
saving.value = true
|
|
|
|
|
try {
|
|
|
|
|
const wasCreated = !draftId.value
|
|
|
|
|
await saveDraftSilently()
|
|
|
|
|
ElMessage.success(wasCreated ? '已自动暂存,进入发起主流程' : '草稿已更新,进入发起主流程')
|
|
|
|
|
return true
|
|
|
|
|
} catch (error) {
|
|
|
|
|
ElMessage.error('进入发起主流程前自动暂存失败:' + (error.message || '未知错误'))
|
|
|
|
|
return false
|
|
|
|
|
} finally {
|
|
|
|
|
saving.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 下一步
|
|
|
|
|
const nextStep = async () => {
|
|
|
|
|
// 步骤1:验证是否选择了第一层分类
|
|
|
|
|
@ -4638,6 +4732,11 @@ const nextStep = async () => {
|
|
|
|
|
|
|
|
|
|
// 步骤6:总体预览,进入下一步
|
|
|
|
|
if (currentStep.value === 6) {
|
|
|
|
|
const draftReady = await ensureDraftBeforeMainFlowStep()
|
|
|
|
|
if (!draftReady) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 查找主审批流程
|
|
|
|
|
paymentMainApprovalFlow.value = paymentTemplateElements.value.find(
|
|
|
|
|
el => el.type === 'approval_flow' && el.is_main_approval_process
|
|
|
|
|
@ -5826,6 +5925,9 @@ const paymentOaIframeUrl = computed(() => {
|
|
|
|
|
if (token) {
|
|
|
|
|
params.set('auth_token', token)
|
|
|
|
|
}
|
|
|
|
|
if (draftId.value) {
|
|
|
|
|
params.set('budget_draft_id', String(draftId.value))
|
|
|
|
|
}
|
|
|
|
|
return `${baseUrl}?${params.toString()}`
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
@ -6018,6 +6120,12 @@ const autoFinalSubmit = async () => {
|
|
|
|
|
|
|
|
|
|
// 重置失败状态
|
|
|
|
|
autoSubmitFailed.value = false
|
|
|
|
|
|
|
|
|
|
if (!draftId.value) {
|
|
|
|
|
ElMessage.warning('请先从总体预览进入发起主流程,系统会自动暂存后再创建付款')
|
|
|
|
|
autoSubmitFailed.value = true
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 验证流程ID
|
|
|
|
|
if (!paymentOaFlowId.value) {
|
|
|
|
|
@ -6100,6 +6208,7 @@ const autoFinalSubmit = async () => {
|
|
|
|
|
// 构建统一提交数据
|
|
|
|
|
const submitData = {
|
|
|
|
|
expenditure: {
|
|
|
|
|
id: draftId.value || undefined,
|
|
|
|
|
category_id: selectedCategory.value?.id,
|
|
|
|
|
template_id: selectedTemplate.value?.id,
|
|
|
|
|
form_data: formData.value,
|
|
|
|
|
@ -6115,59 +6224,17 @@ const autoFinalSubmit = async () => {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果有草稿ID,需要先更新草稿,然后再创建支付
|
|
|
|
|
if (draftId.value) {
|
|
|
|
|
const updateResponse = await plannedExpenditureAPI.update(draftId.value, {
|
|
|
|
|
category_id: selectedCategory.value?.id,
|
|
|
|
|
template_id: selectedTemplate.value?.id,
|
|
|
|
|
form_data: formData.value,
|
|
|
|
|
save_action: 'submit',
|
|
|
|
|
contract_id: expenditureContractId
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (updateResponse.code !== 0) {
|
|
|
|
|
ElMessage.error(`已创建OA流程(流程ID: ${paymentOaFlowId.value}),但更新支出记录失败:${updateResponse.msg || '未知错误'}`)
|
|
|
|
|
autoSubmitFailed.value = true
|
|
|
|
|
lastAutoFlowId.value = null
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const paymentData = {
|
|
|
|
|
payment_category_id: selectedPaymentCategoryId.value,
|
|
|
|
|
amount: amountNumber,
|
|
|
|
|
fields: paymentFormData.fields,
|
|
|
|
|
oa_flow_id: paymentOaFlowId.value,
|
|
|
|
|
// Step2:后端已切换 1 Payment = 1 明细,直接写 Payment 级字段(不再传 details)
|
|
|
|
|
expenditure_type: 'indirect',
|
|
|
|
|
expenditure_id: draftId.value,
|
|
|
|
|
contract_id: paymentDetailContractId,
|
|
|
|
|
writeoff_details: writeoffDetails
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const paymentResponse = await paymentAPI.create(paymentData)
|
|
|
|
|
|
|
|
|
|
if (paymentResponse.code === 0) {
|
|
|
|
|
// 成功:停留本页,不弹窗提示,不自动跳转(避免影响抽屉)
|
|
|
|
|
submitSuccess.value = true
|
|
|
|
|
submitSuccessPayload.value = paymentResponse.data || null
|
|
|
|
|
} else {
|
|
|
|
|
ElMessage.error(`已创建OA流程(流程ID: ${paymentOaFlowId.value}),但创建支付单失败:${paymentResponse.msg || '未知错误'}`)
|
|
|
|
|
autoSubmitFailed.value = true
|
|
|
|
|
lastAutoFlowId.value = null
|
|
|
|
|
}
|
|
|
|
|
// 使用统一接口创建非直接支付;有草稿时由后端在同一事务内更新草稿并创建 Payment。
|
|
|
|
|
const response = await indirectPaymentAPI.create(submitData)
|
|
|
|
|
|
|
|
|
|
if (response.code === 0) {
|
|
|
|
|
// 成功:停留本页,不弹窗提示,不自动跳转(避免影响抽屉)
|
|
|
|
|
submitSuccess.value = true
|
|
|
|
|
submitSuccessPayload.value = response.data || null
|
|
|
|
|
} else {
|
|
|
|
|
// 新创建:使用统一接口(事务保证)
|
|
|
|
|
const response = await indirectPaymentAPI.create(submitData)
|
|
|
|
|
|
|
|
|
|
if (response.code === 0) {
|
|
|
|
|
// 成功:停留本页,不弹窗提示,不自动跳转(避免影响抽屉)
|
|
|
|
|
submitSuccess.value = true
|
|
|
|
|
submitSuccessPayload.value = response.data || null
|
|
|
|
|
} else {
|
|
|
|
|
ElMessage.error(`已创建OA流程(流程ID: ${paymentOaFlowId.value}),但创建支付单失败:${response.msg || '未知错误'}`)
|
|
|
|
|
autoSubmitFailed.value = true
|
|
|
|
|
lastAutoFlowId.value = null
|
|
|
|
|
}
|
|
|
|
|
ElMessage.error(`已创建OA流程(流程ID: ${paymentOaFlowId.value}),但创建支付单失败:${response.msg || '未知错误'}`)
|
|
|
|
|
autoSubmitFailed.value = true
|
|
|
|
|
lastAutoFlowId.value = null
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
ElMessage.error(`已创建OA流程(流程ID: ${paymentOaFlowId.value}),但创建支付单失败:${error.message}`)
|
|
|
|
|
@ -6180,6 +6247,11 @@ const autoFinalSubmit = async () => {
|
|
|
|
|
|
|
|
|
|
// 最终提交(第7步)
|
|
|
|
|
const handleFinalSubmit = async () => {
|
|
|
|
|
if (!draftId.value) {
|
|
|
|
|
ElMessage.warning('请先从总体预览进入发起主流程,系统会自动暂存后再创建付款')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 无论是否配置主流程,都必须提供流程ID
|
|
|
|
|
if (!paymentOaFlowId.value) {
|
|
|
|
|
ElMessage.warning('请先在OA页面完成主审批流程的创建')
|
|
|
|
|
@ -6281,6 +6353,7 @@ const handleFinalSubmit = async () => {
|
|
|
|
|
// 构建统一提交数据
|
|
|
|
|
const submitData = {
|
|
|
|
|
expenditure: {
|
|
|
|
|
id: draftId.value || undefined,
|
|
|
|
|
category_id: selectedCategory.value?.id,
|
|
|
|
|
template_id: selectedTemplate.value?.id,
|
|
|
|
|
form_data: formData.value,
|
|
|
|
|
@ -6296,62 +6369,15 @@ const handleFinalSubmit = async () => {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果有草稿ID,需要先更新草稿,然后再创建支付
|
|
|
|
|
// 但为了保持事务一致性,我们仍然使用统一接口
|
|
|
|
|
// 注意:这里需要后端支持更新草稿的逻辑,或者前端先更新草稿再调用统一接口
|
|
|
|
|
// 暂时先处理新创建的情况
|
|
|
|
|
if (draftId.value) {
|
|
|
|
|
// 如果有草稿,先更新草稿为提交状态
|
|
|
|
|
const updateResponse = await plannedExpenditureAPI.update(draftId.value, {
|
|
|
|
|
category_id: selectedCategory.value?.id,
|
|
|
|
|
template_id: selectedTemplate.value?.id,
|
|
|
|
|
form_data: formData.value,
|
|
|
|
|
save_action: 'submit',
|
|
|
|
|
contract_id: expenditureContractId
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (updateResponse.code !== 0) {
|
|
|
|
|
ElMessage.error('更新支出记录失败:' + (updateResponse.msg || '未知错误'))
|
|
|
|
|
submitting.value = false
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 然后创建支付流程(单独创建,因为支出记录已经存在)
|
|
|
|
|
const paymentData = {
|
|
|
|
|
payment_category_id: selectedPaymentCategoryId.value,
|
|
|
|
|
amount: amountNumber,
|
|
|
|
|
fields: paymentFormData.fields,
|
|
|
|
|
oa_flow_id: paymentOaFlowId.value,
|
|
|
|
|
// Step2:后端已切换 1 Payment = 1 明细,直接写 Payment 级字段(不再传 details)
|
|
|
|
|
expenditure_type: 'indirect',
|
|
|
|
|
expenditure_id: draftId.value,
|
|
|
|
|
contract_id: paymentDetailContractId,
|
|
|
|
|
// StepX:统一关联字段(后端:related_type/related_id)
|
|
|
|
|
related_type: 'planned_expenditure',
|
|
|
|
|
related_id: draftId.value,
|
|
|
|
|
writeoff_details: writeoffDetails
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const paymentResponse = await paymentAPI.create(paymentData)
|
|
|
|
|
|
|
|
|
|
if (paymentResponse.code === 0) {
|
|
|
|
|
// 成功:停留本页,不弹窗提示,不自动跳转(避免影响抽屉)
|
|
|
|
|
submitSuccess.value = true
|
|
|
|
|
submitSuccessPayload.value = paymentResponse.data || null
|
|
|
|
|
} else {
|
|
|
|
|
ElMessage.error(paymentResponse.msg || '创建支付流程失败')
|
|
|
|
|
}
|
|
|
|
|
// 使用统一接口创建非直接支付;有草稿时由后端在同一事务内更新草稿并创建 Payment。
|
|
|
|
|
const response = await indirectPaymentAPI.create(submitData)
|
|
|
|
|
|
|
|
|
|
if (response.code === 0) {
|
|
|
|
|
// 成功:停留本页,不弹窗提示,不自动跳转(避免影响抽屉)
|
|
|
|
|
submitSuccess.value = true
|
|
|
|
|
submitSuccessPayload.value = response.data || null
|
|
|
|
|
} else {
|
|
|
|
|
// 新创建:使用统一接口(事务保证)
|
|
|
|
|
const response = await indirectPaymentAPI.create(submitData)
|
|
|
|
|
|
|
|
|
|
if (response.code === 0) {
|
|
|
|
|
// 成功:停留本页,不弹窗提示,不自动跳转(避免影响抽屉)
|
|
|
|
|
submitSuccess.value = true
|
|
|
|
|
submitSuccessPayload.value = response.data || null
|
|
|
|
|
} else {
|
|
|
|
|
ElMessage.error(response.msg || '创建失败')
|
|
|
|
|
}
|
|
|
|
|
ElMessage.error(response.msg || '创建失败')
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (error !== 'cancel') {
|
|
|
|
|
@ -6368,30 +6394,8 @@ const handleFinalSubmit = async () => {
|
|
|
|
|
const handleSaveDraft = async () => {
|
|
|
|
|
savingDraft.value = true
|
|
|
|
|
try {
|
|
|
|
|
const submitData = {
|
|
|
|
|
category_id: selectedCategory.value?.id,
|
|
|
|
|
template_id: selectedTemplate.value?.id,
|
|
|
|
|
form_data: formData.value,
|
|
|
|
|
save_action: 'draft'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let response
|
|
|
|
|
if (draftId.value) {
|
|
|
|
|
// 更新草稿
|
|
|
|
|
response = await plannedExpenditureAPI.update(draftId.value, submitData)
|
|
|
|
|
} else {
|
|
|
|
|
// 创建新草稿
|
|
|
|
|
response = await plannedExpenditureAPI.create(submitData)
|
|
|
|
|
if (response.code === 0 && response.data?.id) {
|
|
|
|
|
draftId.value = response.data.id
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (response.code === 0) {
|
|
|
|
|
ElMessage.success('已暂存')
|
|
|
|
|
} else {
|
|
|
|
|
ElMessage.error('暂存失败:' + (response.msg || '未知错误'))
|
|
|
|
|
}
|
|
|
|
|
await saveDraftSilently()
|
|
|
|
|
ElMessage.success('已暂存')
|
|
|
|
|
} catch (error) {
|
|
|
|
|
ElMessage.error('暂存失败:' + error.message)
|
|
|
|
|
} finally {
|
|
|
|
|
|