Files
X-Financial/web/src/views/scripts/travelReimbursementSubmitApplicationConflicts.js
caoxiaozhu ee730aa31c feat(web): AI 工作台文件预览/附件关联任务与草稿分支
- 新增 WorkbenchAiFilePreviewDialog 附件预览对话框及 useWorkbenchAiFilePreview,附件支持点击预览
- 新增 attachmentAssociationJobs/linkedReimbursementDraftJobs 前端服务与对应 composable,接入后台任务轮询与状态展示
- 新增 travelReimbursementDraftBranchModel 草稿分支模型,报销关联门控支持跳过/选择草稿
- PersonalWorkbenchAiMode 及各 composable(expense/document/steward/application-preview/attachment-association)重构适配,WorkbenchAiComposer/FileStrip 样式与交互完善
- DocumentsCenter/ReceiptFolder/TravelReimbursementCreate 等视图及 scripts 重构,风险/差旅规划/审批等工具适配
- 新增/更新前端测试:application-result-card、reimbursement-list-preview-fetch、guided-flow、composer-components 等
2026-06-24 10:42:50 +08:00

191 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { isApplicationDocumentNo } from '../../utils/documentClassification.js'
import {
applicationDateRangesOverlap,
normalizeApplicationPreview,
resolveApplicationDateRange
} from '../../utils/expenseApplicationPreview.js'
import { APPLICATION_DUPLICATE_IGNORED_STATUSES } from './travelReimbursementSubmitConstants.js'
function normalizeClaimListPayload(payload) {
if (Array.isArray(payload)) {
return payload
}
return Array.isArray(payload?.items) ? payload.items : []
}
function normalizeClaimRiskFlags(claim) {
const flags = claim?.risk_flags_json || claim?.riskFlagsJson || claim?.riskFlags || []
if (Array.isArray(flags)) {
return flags
}
return flags && typeof flags === 'object' ? [flags] : []
}
function extractApplicationDetailFromClaim(claim) {
return normalizeClaimRiskFlags(claim).reduce((found, item) => {
if (found || !item || typeof item !== 'object') {
return found
}
const detail = item.application_detail || item.applicationDetail
return detail && typeof detail === 'object' ? detail : null
}, null)
}
function isApplicationClaimRecord(claim) {
const expenseType = String(claim?.expense_type || claim?.expenseType || '').trim().toLowerCase()
const claimNo = String(claim?.claim_no || claim?.claimNo || '').trim().toUpperCase()
return (
expenseType === 'application' ||
expenseType === 'expense_application' ||
expenseType.endsWith('_application') ||
isApplicationDocumentNo(claimNo) ||
Boolean(extractApplicationDetailFromClaim(claim))
)
}
function normalizeApplicationExpenseType(value) {
const text = String(value || '').trim().toLowerCase()
if (!text) {
return ''
}
if (text === 'travel_application' || /差旅|出差/.test(text)) {
return 'travel_application'
}
if (text === 'purchase_application' || /采购/.test(text)) {
return 'purchase_application'
}
if (text === 'meeting_application' || /会务|会议/.test(text)) {
return 'meeting_application'
}
if (text === 'expense_application' || text === 'application' || text.endsWith('_application')) {
return text === 'application' ? 'expense_application' : text
}
return 'expense_application'
}
function resolveClaimApplicationExpenseType(claim) {
const detail = extractApplicationDetailFromClaim(claim) || {}
return normalizeApplicationExpenseType(
claim?.expense_type ||
claim?.expenseType ||
detail.application_type ||
detail.applicationType ||
''
)
}
function isIgnoredApplicationDuplicateStatus(status) {
return APPLICATION_DUPLICATE_IGNORED_STATUSES.has(String(status || '').trim().toLowerCase())
}
function resolveClaimApplicationDateRange(claim) {
const detail = extractApplicationDetailFromClaim(claim) || {}
return (
resolveApplicationDateRange(
detail.time ||
detail.time_range ||
detail.timeRange ||
detail.application_time ||
detail.applicationTime ||
detail.application_business_time ||
detail.applicationBusinessTime ||
detail.application_date ||
detail.applicationDate,
detail.days || detail.application_days || detail.applicationDays
) ||
resolveApplicationDateRange(claim?.occurred_at || claim?.occurredAt || '')
)
}
function formatApplicationDateRangeLabel(range) {
if (!range?.startDate) {
return '待确认'
}
return range.startDate === range.endDate ? range.startDate : `${range.startDate}${range.endDate}`
}
function findOverlappingApplicationClaim(applicationPreview, claimsPayload) {
const preview = normalizeApplicationPreview(applicationPreview)
const fields = preview.fields || {}
const currentRange = resolveApplicationDateRange(fields.time, fields.days)
if (!currentRange) {
return null
}
const currentExpenseType = normalizeApplicationExpenseType(fields.applicationType)
const claims = normalizeClaimListPayload(claimsPayload)
for (const claim of claims) {
if (!isApplicationClaimRecord(claim) || isIgnoredApplicationDuplicateStatus(claim?.status)) {
continue
}
const existingExpenseType = resolveClaimApplicationExpenseType(claim)
if (currentExpenseType && existingExpenseType && currentExpenseType !== existingExpenseType) {
continue
}
const existingRange = resolveClaimApplicationDateRange(claim)
if (!existingRange || !applicationDateRangesOverlap(currentRange, existingRange)) {
continue
}
return {
claim,
currentRange,
existingRange,
claimId: String(claim?.id || claim?.claim_id || claim?.claimId || '').trim(),
claimNo: String(claim?.claim_no || claim?.claimNo || claim?.id || '').trim(),
status: String(claim?.approval_stage || claim?.approvalStage || claim?.status || '').trim(),
reason: String(claim?.reason || '').trim(),
location: String(claim?.location || '').trim()
}
}
return null
}
function buildApplicationDateConflictMessage(conflict) {
const claimNo = conflict?.claimNo || '已有申请'
return [
'我先检查了您的申请时间,发现同一天或重叠日期已经存在差旅申请,不能重复创建。',
'',
'已有申请:',
`- **单号**${claimNo}`,
`- **申请时间**${formatApplicationDateRangeLabel(conflict?.existingRange)}`,
conflict?.location ? `- **地点**${conflict.location}` : '',
conflict?.reason ? `- **事由**${conflict.reason}` : '',
`- **当前节点**${conflict?.status || '处理中'}`,
'',
`本次识别时间:${formatApplicationDateRangeLabel(conflict?.currentRange)}`,
'',
'请先查看已有申请,或修改本次出差时间后再继续。'
].filter(Boolean).join('\n')
}
function buildApplicationDateConflictActions(conflict) {
const actions = []
if (conflict?.claimId) {
actions.push({
action_type: 'open_application_detail',
label: '查看已有申请',
description: conflict.claimNo ? `进入 ${conflict.claimNo} 单据详情。` : '进入已有申请单据详情。',
icon: 'mdi mdi-file-search-outline',
payload: {
claim_id: conflict.claimId,
claim_no: conflict.claimNo
}
})
}
actions.push({
action_type: 'prefill_composer',
label: '修改出差时间',
description: '在输入框中补充新的出差日期后继续。',
icon: 'mdi mdi-calendar-edit-outline',
payload: {
prompt_prefill: '修改出差时间为:'
}
})
return actions
}
export {
buildApplicationDateConflictActions,
buildApplicationDateConflictMessage,
findOverlappingApplicationClaim
}