2026-05-26 09:15:14 +08:00
|
|
|
|
import { buildApplicationFieldsFromOntology } from './expenseApplicationOntology.js'
|
2026-06-01 17:07:14 +08:00
|
|
|
|
import {
|
|
|
|
|
|
buildMockApplicationTransportEstimate,
|
|
|
|
|
|
buildSystemApplicationEstimate
|
|
|
|
|
|
} from './expenseApplicationEstimate.js'
|
2026-05-30 15:46:51 +08:00
|
|
|
|
import { getTodayDateValue } from './workbenchComposerDate.js'
|
2026-05-26 09:15:14 +08:00
|
|
|
|
|
|
|
|
|
|
const APPLICATION_SESSION_TYPE = 'application'
|
|
|
|
|
|
const APPLICATION_CREATE_PATTERN = /申请|事前|前置|预算|出差|差旅|采购|会务|会议|培训|办公用品|交通|住宿/
|
|
|
|
|
|
const APPLICATION_QUERY_PATTERN = /查询|状态|进度|列表|有哪些|材料清单|需要哪些|制度|标准|规则|怎么规定/
|
|
|
|
|
|
|
|
|
|
|
|
export const APPLICATION_PREVIEW_FIELD_DEFINITIONS = [
|
|
|
|
|
|
{ key: 'applicationType', label: '申请类型' },
|
2026-06-01 17:07:14 +08:00
|
|
|
|
{ key: 'applicant', label: '姓名', editable: false, required: false },
|
2026-05-27 10:32:08 +08:00
|
|
|
|
{ key: 'grade', label: '职级', highlight: true, editable: false, required: false },
|
2026-06-01 17:07:14 +08:00
|
|
|
|
{ key: 'department', label: '部门', editable: false, required: false },
|
|
|
|
|
|
{ key: 'position', label: '岗位', editable: false, required: false },
|
|
|
|
|
|
{ key: 'managerName', label: '直属领导', editable: false, required: false },
|
2026-05-26 09:15:14 +08:00
|
|
|
|
{ key: 'time', label: '发生时间' },
|
|
|
|
|
|
{ key: 'location', label: '地点' },
|
|
|
|
|
|
{ key: 'reason', label: '事由' },
|
|
|
|
|
|
{ key: 'days', label: '天数' },
|
|
|
|
|
|
{ key: 'transportMode', label: '出行方式' },
|
|
|
|
|
|
{ key: 'lodgingDailyCap', label: '住宿上限/天', highlight: true, editable: false, required: false },
|
|
|
|
|
|
{ key: 'subsidyDailyCap', label: '补贴标准/天', highlight: true, editable: false, required: false },
|
|
|
|
|
|
{ key: 'transportPolicy', label: '交通费用口径', highlight: true, editable: false, required: false },
|
|
|
|
|
|
{ key: 'policyEstimate', label: '规则测算参考', highlight: true, editable: false, required: false },
|
2026-06-01 17:07:14 +08:00
|
|
|
|
{ key: 'amount', label: '系统预估费用', highlight: true, editable: false, required: false }
|
2026-05-26 09:15:14 +08:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
export const APPLICATION_TRANSPORT_MODE_OPTIONS = ['火车', '飞机', '轮船']
|
|
|
|
|
|
|
|
|
|
|
|
const APPLICATION_POLICY_PENDING_TEXT = '填写地点和天数后自动测算'
|
2026-06-01 17:07:14 +08:00
|
|
|
|
const APPLICATION_TRANSPORT_REIMBURSEMENT_TEXT = '选择火车、飞机或轮船后自动生成交通参考票价,报销阶段按真实票据复核'
|
2026-05-26 09:15:14 +08:00
|
|
|
|
|
|
|
|
|
|
function compactText(value) {
|
|
|
|
|
|
return String(value || '').replace(/\s+/g, '')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveFirstMatch(text, patterns = []) {
|
|
|
|
|
|
for (const pattern of patterns) {
|
|
|
|
|
|
const match = text.match(pattern)
|
|
|
|
|
|
const value = String(match?.groups?.value || match?.[1] || '').trim()
|
|
|
|
|
|
if (value) return value.replace(/[,。;;]$/, '')
|
|
|
|
|
|
}
|
|
|
|
|
|
return ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function normalizeDateText(value) {
|
|
|
|
|
|
return String(value || '').replace(/[/.]/g, '-').replace(/\s+/g, '')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function parseIsoDate(value) {
|
|
|
|
|
|
const match = normalizeDateText(value).match(/^(20\d{2})-(\d{1,2})-(\d{1,2})$/)
|
|
|
|
|
|
if (!match) return null
|
|
|
|
|
|
const [, year, month, day] = match
|
|
|
|
|
|
const date = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day)))
|
|
|
|
|
|
return Number.isNaN(date.getTime()) ? null : date
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatIsoDate(date) {
|
|
|
|
|
|
return date.toISOString().slice(0, 10)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildEndDateFromDays(startText, daysText = '') {
|
2026-05-30 15:46:51 +08:00
|
|
|
|
const days = parseApplicationDaysValue(daysText)
|
2026-05-26 09:15:14 +08:00
|
|
|
|
const start = parseIsoDate(startText)
|
|
|
|
|
|
if (!days || !start) return ''
|
|
|
|
|
|
const end = new Date(start.getTime())
|
2026-05-30 15:46:51 +08:00
|
|
|
|
end.setUTCDate(end.getUTCDate() + Math.max(days - 1, 0))
|
2026-05-26 09:15:14 +08:00
|
|
|
|
return formatIsoDate(end)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveDaysFromDateRange(rangeText) {
|
|
|
|
|
|
const match = String(rangeText || '').match(/(20\d{2}[-/.]\d{1,2}[-/.]\d{1,2})\s*至\s*(20\d{2}[-/.]\d{1,2}[-/.]\d{1,2})/)
|
|
|
|
|
|
if (!match) return ''
|
|
|
|
|
|
const start = parseIsoDate(match[1])
|
|
|
|
|
|
const end = parseIsoDate(match[2])
|
|
|
|
|
|
if (!start || !end) return ''
|
|
|
|
|
|
const diffDays = Math.round((end.getTime() - start.getTime()) / 86400000)
|
2026-05-30 15:46:51 +08:00
|
|
|
|
return diffDays >= 0 ? `${diffDays + 1}天` : ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolvePreviewToday(options = {}) {
|
|
|
|
|
|
const explicitToday = String(options.today || options.currentDate || '').trim()
|
|
|
|
|
|
if (parseIsoDate(explicitToday)) return normalizeDateText(explicitToday)
|
|
|
|
|
|
if (options.now instanceof Date && !Number.isNaN(options.now.getTime())) {
|
|
|
|
|
|
return getTodayDateValue(options.now)
|
|
|
|
|
|
}
|
|
|
|
|
|
return getTodayDateValue()
|
2026-05-26 09:15:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveApplicationType(text) {
|
|
|
|
|
|
const compact = compactText(text)
|
|
|
|
|
|
if (/差旅|出差|高铁|动车|火车|飞机|机票|航班|酒店|住宿/.test(compact)) return '差旅费用申请'
|
|
|
|
|
|
if (/交通|出租车|的士|网约车|打车|通勤/.test(compact)) return '交通费用申请'
|
|
|
|
|
|
if (/住宿|酒店/.test(compact)) return '住宿费用申请'
|
|
|
|
|
|
if (/会务|会议|发布会|展会/.test(compact)) return '会务费用申请'
|
|
|
|
|
|
if (/采购|办公用品|文具|设备|耗材/.test(compact)) return '采购费用申请'
|
|
|
|
|
|
if (/培训|课程|学习/.test(compact)) return '培训费用申请'
|
|
|
|
|
|
return '费用申请'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveApplicationAmount(text) {
|
|
|
|
|
|
const compact = compactText(text)
|
|
|
|
|
|
const labeled = resolveFirstMatch(text, [
|
|
|
|
|
|
/(?:用户预估费用|预估费用|申请金额|预计金额|预计费用|预计总费用|预算|金额|费用)\s*[::]?\s*(?<value>\d+(?:\.\d+)?)\s*(?:元|块|人民币)?/u,
|
|
|
|
|
|
/(?<value>\d+(?:\.\d+)?)\s*(?:元|块|人民币)/u
|
|
|
|
|
|
])
|
|
|
|
|
|
if (labeled) return `${labeled}元`
|
|
|
|
|
|
if (/不知道预算|预算不清楚|预算待定|待测算/.test(compact)) return '待测算'
|
|
|
|
|
|
return ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveCurrentUserGrade(currentUser = {}) {
|
|
|
|
|
|
return String(
|
|
|
|
|
|
currentUser.grade
|
|
|
|
|
|
|| currentUser.employeeGrade
|
|
|
|
|
|
|| currentUser.employee_grade
|
|
|
|
|
|
|| currentUser.profileGrade
|
|
|
|
|
|
|| ''
|
|
|
|
|
|
).trim()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-01 17:07:14 +08:00
|
|
|
|
function resolveCurrentUserDepartment(currentUser = {}) {
|
|
|
|
|
|
return String(
|
|
|
|
|
|
currentUser.department
|
|
|
|
|
|
|| currentUser.departmentName
|
|
|
|
|
|
|| currentUser.department_name
|
|
|
|
|
|
|| ''
|
|
|
|
|
|
).trim()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveCurrentUserPosition(currentUser = {}) {
|
|
|
|
|
|
return String(
|
|
|
|
|
|
currentUser.position
|
|
|
|
|
|
|| currentUser.employeePosition
|
|
|
|
|
|
|| currentUser.employee_position
|
|
|
|
|
|
|| currentUser.jobTitle
|
|
|
|
|
|
|| currentUser.job_title
|
|
|
|
|
|
|| ''
|
|
|
|
|
|
).trim()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveCurrentUserManagerName(currentUser = {}) {
|
|
|
|
|
|
return String(
|
|
|
|
|
|
currentUser.managerName
|
|
|
|
|
|
|| currentUser.manager_name
|
|
|
|
|
|
|| currentUser.directManagerName
|
|
|
|
|
|
|| currentUser.direct_manager_name
|
|
|
|
|
|
|| currentUser.leaderName
|
|
|
|
|
|
|| currentUser.leader_name
|
|
|
|
|
|
|| ''
|
|
|
|
|
|
).trim()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 09:15:14 +08:00
|
|
|
|
function parseApplicationDaysValue(value) {
|
|
|
|
|
|
const match = String(value || '').match(/\d+/)
|
2026-05-30 15:46:51 +08:00
|
|
|
|
const days = match ? Number(match[0]) : parseChineseNumber(value)
|
2026-05-26 09:15:14 +08:00
|
|
|
|
return Number.isFinite(days) && days > 0 ? Math.max(1, Math.floor(days)) : 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-30 15:46:51 +08:00
|
|
|
|
function parseChineseNumber(value) {
|
|
|
|
|
|
const digits = {
|
|
|
|
|
|
一: 1,
|
|
|
|
|
|
二: 2,
|
|
|
|
|
|
两: 2,
|
|
|
|
|
|
三: 3,
|
|
|
|
|
|
四: 4,
|
|
|
|
|
|
五: 5,
|
|
|
|
|
|
六: 6,
|
|
|
|
|
|
七: 7,
|
|
|
|
|
|
八: 8,
|
|
|
|
|
|
九: 9
|
|
|
|
|
|
}
|
|
|
|
|
|
const text = String(value || '').match(/[一二两三四五六七八九十]{1,3}/)?.[0] || ''
|
|
|
|
|
|
if (!text) return 0
|
|
|
|
|
|
if (text === '十') return 10
|
|
|
|
|
|
if (text.includes('十')) {
|
|
|
|
|
|
const [left, right] = text.split('十')
|
|
|
|
|
|
const tens = left ? digits[left] || 0 : 1
|
|
|
|
|
|
const ones = right ? digits[right] || 0 : 0
|
|
|
|
|
|
return tens * 10 + ones
|
|
|
|
|
|
}
|
|
|
|
|
|
return digits[text] || 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 09:15:14 +08:00
|
|
|
|
function parseMoneyNumber(value) {
|
|
|
|
|
|
const normalized = String(value ?? '').replace(/[^\d.-]/g, '')
|
|
|
|
|
|
const amount = Number(normalized)
|
|
|
|
|
|
return Number.isFinite(amount) ? amount : null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatPolicyMoney(value) {
|
|
|
|
|
|
const amount = parseMoneyNumber(value)
|
|
|
|
|
|
if (amount === null) return String(value || '').trim()
|
|
|
|
|
|
return new Intl.NumberFormat('zh-CN', {
|
|
|
|
|
|
minimumFractionDigits: 0,
|
|
|
|
|
|
maximumFractionDigits: Number.isInteger(amount) ? 0 : 2
|
|
|
|
|
|
}).format(amount)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatDailyPolicyMoney(value) {
|
|
|
|
|
|
const display = formatPolicyMoney(value)
|
|
|
|
|
|
return display ? `${display}元/天` : APPLICATION_POLICY_PENDING_TEXT
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-01 17:07:14 +08:00
|
|
|
|
function buildTransportPolicyText(transportMode, location = '', transportEstimate = null, time = '') {
|
2026-05-26 09:15:14 +08:00
|
|
|
|
const mode = String(transportMode || '').trim()
|
|
|
|
|
|
if (!mode) return APPLICATION_TRANSPORT_REIMBURSEMENT_TEXT
|
2026-06-01 17:07:14 +08:00
|
|
|
|
const estimate = transportEstimate || buildMockApplicationTransportEstimate({ transportMode: mode, location, time })
|
|
|
|
|
|
if (!estimate) return APPLICATION_TRANSPORT_REIMBURSEMENT_TEXT
|
|
|
|
|
|
return `${estimate.basisText},报销阶段按真实票据复核`
|
2026-05-26 09:15:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function ensureApplicationPolicyFields(fields = {}) {
|
|
|
|
|
|
const nextFields = { ...fields }
|
|
|
|
|
|
if (!String(nextFields.lodgingDailyCap || '').trim()) {
|
|
|
|
|
|
nextFields.lodgingDailyCap = APPLICATION_POLICY_PENDING_TEXT
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!String(nextFields.subsidyDailyCap || '').trim()) {
|
|
|
|
|
|
nextFields.subsidyDailyCap = APPLICATION_POLICY_PENDING_TEXT
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!String(nextFields.transportPolicy || '').trim() || /实报实销/.test(String(nextFields.transportPolicy || ''))) {
|
2026-06-01 17:07:14 +08:00
|
|
|
|
nextFields.transportPolicy = buildTransportPolicyText(nextFields.transportMode, nextFields.location, null, nextFields.time)
|
2026-05-26 09:15:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (!String(nextFields.policyEstimate || '').trim()) {
|
|
|
|
|
|
nextFields.policyEstimate = APPLICATION_POLICY_PENDING_TEXT
|
|
|
|
|
|
}
|
|
|
|
|
|
return nextFields
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveApplicationDays(text) {
|
|
|
|
|
|
const value = resolveFirstMatch(text, [
|
|
|
|
|
|
/(?:出差|申请)?(?<value>\d+)\s*天/u,
|
|
|
|
|
|
/(?<value>\d+)\s*(?:个)?工作日/u
|
|
|
|
|
|
])
|
|
|
|
|
|
return value ? `${value}天` : ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-30 15:46:51 +08:00
|
|
|
|
function resolveApplicationTime(text, daysText = '', options = {}) {
|
2026-05-26 09:15:14 +08:00
|
|
|
|
const range = text.match(
|
|
|
|
|
|
/(20\d{2}[-/.]\d{1,2}[-/.]\d{1,2})\s*(?:至|到|~|—|–|--)\s*(20\d{2}[-/.]\d{1,2}[-/.]\d{1,2})/u
|
|
|
|
|
|
)
|
|
|
|
|
|
if (range) {
|
|
|
|
|
|
return `${normalizeDateText(range[1])} 至 ${normalizeDateText(range[2])}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const single = resolveFirstMatch(text, [
|
|
|
|
|
|
/(?:发生时间|业务发生时间|申请时间|时间)\s*[::]\s*(?<value>20\d{2}[-/.]\d{1,2}[-/.]\d{1,2})/u,
|
|
|
|
|
|
/(?<value>20\d{2}[-/.]\d{1,2}[-/.]\d{1,2})/u
|
|
|
|
|
|
])
|
|
|
|
|
|
if (!single) return ''
|
|
|
|
|
|
const normalized = normalizeDateText(single)
|
|
|
|
|
|
const endDate = buildEndDateFromDays(normalized, daysText)
|
2026-05-30 15:46:51 +08:00
|
|
|
|
return endDate && endDate !== normalized ? `${normalized} 至 ${endDate}` : normalized
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveApplicationTimeWithDefault(text, daysText = '', options = {}) {
|
|
|
|
|
|
const resolvedTime = resolveApplicationTime(text, daysText)
|
|
|
|
|
|
if (resolvedTime || !parseApplicationDaysValue(daysText)) {
|
|
|
|
|
|
return resolvedTime
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const startDate = resolvePreviewToday(options)
|
|
|
|
|
|
const endDate = buildEndDateFromDays(startDate, daysText)
|
|
|
|
|
|
return endDate && endDate !== startDate ? `${startDate} 至 ${endDate}` : startDate
|
2026-05-26 09:15:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveApplicationLocation(text) {
|
|
|
|
|
|
return resolveFirstMatch(text, [
|
|
|
|
|
|
/(?:地点|业务地点|发生地点|目的地)\s*[::]\s*(?<value>[^。;;\n]+)/u,
|
|
|
|
|
|
/(?:去|到|前往)(?<value>[\u4e00-\u9fa5,,、]{2,24}?)(?:出差|支撑|支持|部署|开会|培训|拜访|验收|项目|客户|。|\s|$)/u
|
|
|
|
|
|
])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveApplicationTransportMode(text) {
|
|
|
|
|
|
const compact = compactText(text)
|
|
|
|
|
|
if (/高铁|动车|火车|铁路/.test(compact)) return '火车'
|
|
|
|
|
|
if (/飞机|机票|航班/.test(compact)) return '飞机'
|
|
|
|
|
|
if (/轮船|船票|客轮|渡轮/.test(compact)) return '轮船'
|
|
|
|
|
|
return ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function stripKnownContextFromReason(value, context = {}) {
|
|
|
|
|
|
const location = String(context.location || '').trim()
|
|
|
|
|
|
let cleaned = String(value || '')
|
|
|
|
|
|
.replace(/(?:发生时间|业务发生时间|申请时间|时间)\s*[::]\s*(?=[,,、。;;\s]|$)/gu, '')
|
|
|
|
|
|
.replace(/(?:地点|业务地点|发生地点|目的地)\s*[::]\s*(?=[,,、。;;\s]|$)/gu, '')
|
|
|
|
|
|
.replace(/20\d{2}[-/.]\d{1,2}[-/.]\d{1,2}\s*(?:至|到|~|—|–|--)\s*20\d{2}[-/.]\d{1,2}[-/.]\d{1,2}/gu, '')
|
|
|
|
|
|
.replace(/20\d{2}[-/.]\d{1,2}[-/.]\d{1,2}/gu, '')
|
|
|
|
|
|
.replace(/(?:出差|申请)?\d+\s*天/gu, '')
|
|
|
|
|
|
.replace(/(?:用户预估费用|预估费用|预计总费用|预计费用|预计金额|申请金额|预算|金额|费用)\s*[::]?\s*\d+(?:\.\d+)?\s*(?:元|块|人民币)?/gu, '')
|
|
|
|
|
|
.replace(/(?:高铁|动车|火车|铁路|飞机|机票|航班|轮船|船票|客轮|渡轮|出租车|的士|网约车|打车|自驾)/gu, '')
|
|
|
|
|
|
.replace(/[,,、。;;]+/g, ',')
|
|
|
|
|
|
.replace(/^\s*(去|到|前往)/u, '')
|
|
|
|
|
|
.replace(/^[,\s]+|[,\s]+$/g, '')
|
|
|
|
|
|
.trim()
|
|
|
|
|
|
|
|
|
|
|
|
if (location) {
|
|
|
|
|
|
const escapedLocation = location.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
|
|
|
|
cleaned = cleaned
|
|
|
|
|
|
.replace(new RegExp(`^${escapedLocation}(?:出差)?(?:,|,|、)?`, 'u'), '')
|
|
|
|
|
|
.replace(new RegExp(`^(?:去|到|前往)${escapedLocation}(?:出差)?(?:,|,|、)?`, 'u'), '')
|
|
|
|
|
|
.trim()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return cleaned
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function pickBusinessReasonSegment(text) {
|
|
|
|
|
|
const segments = String(text || '')
|
|
|
|
|
|
.split(/[,,、。;;\n]+/u)
|
|
|
|
|
|
.map((item) => item.trim())
|
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
|
return segments.find((item) => /服务|支撑|支持|部署|实施|验收|拜访|对接|沟通|培训|会议|采购|安装|维护|上线|调试|项目/.test(item)) || ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveApplicationReason(text, context = {}) {
|
|
|
|
|
|
const labeled = resolveFirstMatch(text, [
|
|
|
|
|
|
/(?:事由|申请事由|出差事由|原因|用途)\s*[::]\s*(?<value>[^,。;;\n]+)/u
|
|
|
|
|
|
])
|
|
|
|
|
|
if (labeled) return stripKnownContextFromReason(labeled, context)
|
|
|
|
|
|
const cleaned = String(text || '')
|
|
|
|
|
|
.replace(/^\s*(我想|我要|帮我)?\s*(先)?\s*(发起|提交|申请)?\s*(一笔)?\s*(费用申请|申请)\s*/u, '')
|
|
|
|
|
|
const withoutContext = stripKnownContextFromReason(cleaned, context)
|
|
|
|
|
|
const businessSegment = pickBusinessReasonSegment(withoutContext)
|
|
|
|
|
|
if (businessSegment) return stripKnownContextFromReason(businessSegment, context)
|
|
|
|
|
|
return withoutContext
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isApplicationPreviewValueProvided(value) {
|
|
|
|
|
|
const normalized = String(value || '').trim()
|
|
|
|
|
|
return Boolean(normalized) && !['待测算', '待补充', '未知'].includes(normalized)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveProvidedValue(value, fallback = '') {
|
|
|
|
|
|
return isApplicationPreviewValueProvided(value) ? String(value).trim() : fallback
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function normalizeApplicationTypeLabel(value, fallback = '') {
|
|
|
|
|
|
const label = String(value || '').trim()
|
|
|
|
|
|
if (!label || label === '其他费用') return fallback || '费用申请'
|
|
|
|
|
|
if (label.endsWith('费用申请') || label.endsWith('申请')) return label
|
|
|
|
|
|
if (label.endsWith('费用')) return `${label}申请`
|
|
|
|
|
|
if (label.endsWith('费')) return `${label.slice(0, -1)}费用申请`
|
|
|
|
|
|
return `${label}申请`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function normalizeTransportModeOption(value, fallback = '') {
|
|
|
|
|
|
const text = String(value || '').trim()
|
|
|
|
|
|
if (/飞机|机票|航班/.test(text)) return '飞机'
|
|
|
|
|
|
if (/轮船|船票|客轮|渡轮|邮轮/.test(text)) return '轮船'
|
|
|
|
|
|
if (/火车|高铁|动车|铁路|列车/.test(text)) return '火车'
|
|
|
|
|
|
return APPLICATION_TRANSPORT_MODE_OPTIONS.includes(text) ? text : fallback
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function normalizeAmountFromOntology(fields = {}, fallback = '') {
|
|
|
|
|
|
const numericAmount = Number(fields.amount || 0)
|
|
|
|
|
|
if (Number.isFinite(numericAmount) && numericAmount > 0) {
|
|
|
|
|
|
return `${numericAmount}元`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const display = String(fields.amountDisplay || '').trim()
|
|
|
|
|
|
if (display && display !== '待补充') {
|
|
|
|
|
|
const normalized = display.replace(/^¥\s*/, '').replace(/,/g, '').trim()
|
|
|
|
|
|
return normalized.endsWith('元') ? normalized : `${normalized}元`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return fallback
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildMissingFields(fields) {
|
|
|
|
|
|
return APPLICATION_PREVIEW_FIELD_DEFINITIONS
|
|
|
|
|
|
.filter((item) => item.key !== 'applicationType' && item.required !== false)
|
|
|
|
|
|
.filter((item) => !isApplicationPreviewValueProvided(fields?.[item.key]))
|
|
|
|
|
|
.map((item) => item.label)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function buildApplicationPolicyEstimateRequest(preview = {}, currentUser = {}) {
|
|
|
|
|
|
const normalized = normalizeApplicationPreview(preview)
|
|
|
|
|
|
const fields = normalized.fields || {}
|
|
|
|
|
|
const days = parseApplicationDaysValue(fields.days)
|
|
|
|
|
|
const location = String(fields.location || '').trim()
|
|
|
|
|
|
const grade = String(fields.grade || resolveCurrentUserGrade(currentUser)).trim()
|
|
|
|
|
|
const applicationType = String(fields.applicationType || '').trim()
|
|
|
|
|
|
const transportMode = String(fields.transportMode || '').trim()
|
|
|
|
|
|
const shouldEstimate = /差旅|住宿|交通/.test(applicationType) || Boolean(transportMode)
|
|
|
|
|
|
|
|
|
|
|
|
if (!shouldEstimate || !days || !location) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
canCalculate: false,
|
|
|
|
|
|
reason: '缺少地点或天数',
|
|
|
|
|
|
payload: null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
canCalculate: true,
|
|
|
|
|
|
reason: '',
|
|
|
|
|
|
payload: {
|
|
|
|
|
|
days,
|
|
|
|
|
|
location,
|
|
|
|
|
|
grade
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function applyApplicationPolicyEstimateResult(preview = {}, result = {}, currentUser = {}) {
|
|
|
|
|
|
const fields = { ...(preview?.fields || {}) }
|
|
|
|
|
|
const days = Number(result?.days) || parseApplicationDaysValue(fields.days) || 1
|
|
|
|
|
|
const hotelRate = formatPolicyMoney(result?.hotel_rate)
|
|
|
|
|
|
const hotelAmount = formatPolicyMoney(result?.hotel_amount)
|
|
|
|
|
|
const allowanceRate = formatPolicyMoney(result?.total_allowance_rate)
|
|
|
|
|
|
const allowanceAmount = formatPolicyMoney(result?.allowance_amount)
|
|
|
|
|
|
const matchedCity = String(result?.matched_city || fields.location || '').trim()
|
|
|
|
|
|
const grade = String(result?.grade || fields.grade || resolveCurrentUserGrade(currentUser)).trim()
|
2026-06-01 17:07:14 +08:00
|
|
|
|
const systemEstimate = buildSystemApplicationEstimate({
|
|
|
|
|
|
transportMode: fields.transportMode,
|
|
|
|
|
|
location: matchedCity || fields.location,
|
|
|
|
|
|
time: fields.time,
|
|
|
|
|
|
lodgingAmount: result?.hotel_amount,
|
|
|
|
|
|
allowanceAmount: result?.allowance_amount
|
|
|
|
|
|
})
|
|
|
|
|
|
const transportEstimate = systemEstimate.transportEstimate
|
|
|
|
|
|
const queryLabel = transportEstimate?.queryDate || '出行日期待确认'
|
|
|
|
|
|
const transportText = transportEstimate
|
|
|
|
|
|
? `交通 ${systemEstimate.transportAmountDisplay}元(按 ${queryLabel} 参考票价) + `
|
|
|
|
|
|
: ''
|
|
|
|
|
|
const totalAmount = systemEstimate.totalAmountDisplay
|
|
|
|
|
|
const amount = totalAmount ? `${totalAmount}元` : fields.amount
|
2026-05-26 09:15:14 +08:00
|
|
|
|
|
|
|
|
|
|
return normalizeApplicationPreview({
|
|
|
|
|
|
...preview,
|
|
|
|
|
|
fields: {
|
|
|
|
|
|
...fields,
|
|
|
|
|
|
grade,
|
|
|
|
|
|
lodgingDailyCap: formatDailyPolicyMoney(result?.hotel_rate),
|
|
|
|
|
|
subsidyDailyCap: formatDailyPolicyMoney(result?.total_allowance_rate),
|
2026-06-01 17:07:14 +08:00
|
|
|
|
transportPolicy: buildTransportPolicyText(fields.transportMode, matchedCity || fields.location, transportEstimate, fields.time),
|
|
|
|
|
|
policyEstimate: `${transportText}住宿 ${hotelAmount}元 + 补贴 ${allowanceAmount}元 = ${totalAmount}元(${days}天)`,
|
|
|
|
|
|
amount,
|
2026-05-26 09:15:14 +08:00
|
|
|
|
matchedCity,
|
|
|
|
|
|
ruleName: String(result?.rule_name || '').trim(),
|
|
|
|
|
|
ruleVersion: String(result?.rule_version || '').trim(),
|
|
|
|
|
|
hotelAmount: hotelAmount ? `${hotelAmount}元` : '',
|
|
|
|
|
|
allowanceAmount: allowanceAmount ? `${allowanceAmount}元` : '',
|
2026-06-01 17:07:14 +08:00
|
|
|
|
transportEstimatedAmount: systemEstimate.transportAmountDisplay ? `${systemEstimate.transportAmountDisplay}元` : '',
|
|
|
|
|
|
transportEstimateDate: transportEstimate?.queryDate || '',
|
|
|
|
|
|
transportQueryLatencyMs: transportEstimate?.simulatedLatencyMs ? `${transportEstimate.simulatedLatencyMs}ms` : '',
|
|
|
|
|
|
transportEstimateSource: transportEstimate?.source || '',
|
|
|
|
|
|
transportEstimateConfidence: transportEstimate?.confidence || '',
|
2026-05-26 09:15:14 +08:00
|
|
|
|
policyTotalAmount: totalAmount ? `${totalAmount}元` : ''
|
|
|
|
|
|
},
|
|
|
|
|
|
policyEstimate: {
|
|
|
|
|
|
...result,
|
|
|
|
|
|
grade,
|
2026-06-01 17:07:14 +08:00
|
|
|
|
matchedCity,
|
|
|
|
|
|
transport_estimate: transportEstimate,
|
|
|
|
|
|
system_total_amount: systemEstimate.totalAmount
|
2026-05-26 09:15:14 +08:00
|
|
|
|
},
|
|
|
|
|
|
policyEstimateStatus: 'completed'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-01 17:07:14 +08:00
|
|
|
|
export function refreshApplicationPreviewTransportEstimate(preview = {}) {
|
|
|
|
|
|
const normalized = normalizeApplicationPreview(preview)
|
|
|
|
|
|
const fields = { ...(normalized.fields || {}) }
|
|
|
|
|
|
const policyResult = normalized.policyEstimate && typeof normalized.policyEstimate === 'object'
|
|
|
|
|
|
? normalized.policyEstimate
|
|
|
|
|
|
: {}
|
|
|
|
|
|
const location = String(fields.matchedCity || policyResult.matched_city || fields.location || '').trim()
|
|
|
|
|
|
const hotelAmountSource = fields.hotelAmount || policyResult.hotel_amount || 0
|
|
|
|
|
|
const allowanceAmountSource = fields.allowanceAmount || policyResult.allowance_amount || 0
|
|
|
|
|
|
const systemEstimate = buildSystemApplicationEstimate({
|
|
|
|
|
|
transportMode: fields.transportMode,
|
|
|
|
|
|
location,
|
|
|
|
|
|
time: fields.time,
|
|
|
|
|
|
lodgingAmount: hotelAmountSource,
|
|
|
|
|
|
allowanceAmount: allowanceAmountSource
|
|
|
|
|
|
})
|
|
|
|
|
|
const transportEstimate = systemEstimate.transportEstimate
|
|
|
|
|
|
if (!transportEstimate) return normalized
|
|
|
|
|
|
|
|
|
|
|
|
const hotelAmount = formatPolicyMoney(hotelAmountSource)
|
|
|
|
|
|
const allowanceAmount = formatPolicyMoney(allowanceAmountSource)
|
|
|
|
|
|
const hasPolicyAmounts = parseMoneyNumber(hotelAmountSource) > 0 || parseMoneyNumber(allowanceAmountSource) > 0
|
|
|
|
|
|
const queryLabel = transportEstimate.queryDate || '出行日期待确认'
|
|
|
|
|
|
const nextFields = {
|
|
|
|
|
|
...fields,
|
|
|
|
|
|
transportPolicy: buildTransportPolicyText(fields.transportMode, location, transportEstimate, fields.time),
|
|
|
|
|
|
transportEstimatedAmount: systemEstimate.transportAmountDisplay ? `${systemEstimate.transportAmountDisplay}元` : '',
|
|
|
|
|
|
transportEstimateDate: transportEstimate.queryDate || '',
|
|
|
|
|
|
transportQueryLatencyMs: transportEstimate.simulatedLatencyMs ? `${transportEstimate.simulatedLatencyMs}ms` : '',
|
|
|
|
|
|
transportEstimateSource: transportEstimate.source || '',
|
|
|
|
|
|
transportEstimateConfidence: transportEstimate.confidence || ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (hasPolicyAmounts) {
|
|
|
|
|
|
const days = Number(policyResult.days) || parseApplicationDaysValue(fields.days) || 1
|
|
|
|
|
|
const totalAmount = systemEstimate.totalAmountDisplay
|
|
|
|
|
|
nextFields.policyEstimate = `交通 ${systemEstimate.transportAmountDisplay}元(按 ${queryLabel} 参考票价) + 住宿 ${hotelAmount}元 + 补贴 ${allowanceAmount}元 = ${totalAmount}元(${days}天)`
|
|
|
|
|
|
nextFields.amount = totalAmount ? `${totalAmount}元` : nextFields.amount
|
|
|
|
|
|
nextFields.policyTotalAmount = totalAmount ? `${totalAmount}元` : ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return normalizeApplicationPreview({
|
|
|
|
|
|
...normalized,
|
|
|
|
|
|
fields: nextFields,
|
|
|
|
|
|
policyEstimate: {
|
|
|
|
|
|
...policyResult,
|
|
|
|
|
|
matchedCity: location,
|
|
|
|
|
|
transport_estimate: transportEstimate,
|
|
|
|
|
|
system_total_amount: systemEstimate.totalAmount
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 09:15:14 +08:00
|
|
|
|
export function applyApplicationPolicyEstimateError(preview = {}, error = null, currentUser = {}) {
|
|
|
|
|
|
const fields = { ...(preview?.fields || {}) }
|
|
|
|
|
|
const message = String(error?.message || error || '').trim()
|
|
|
|
|
|
return normalizeApplicationPreview({
|
|
|
|
|
|
...preview,
|
|
|
|
|
|
fields: {
|
|
|
|
|
|
...fields,
|
|
|
|
|
|
grade: fields.grade || resolveCurrentUserGrade(currentUser),
|
2026-06-01 17:07:14 +08:00
|
|
|
|
transportPolicy: buildTransportPolicyText(fields.transportMode, fields.location, null, fields.time),
|
2026-05-26 09:15:14 +08:00
|
|
|
|
policyEstimate: message ? `规则中心暂未完成测算:${message}` : APPLICATION_POLICY_PENDING_TEXT
|
|
|
|
|
|
},
|
|
|
|
|
|
policyEstimateStatus: message ? 'failed' : 'pending'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function shouldUseLocalApplicationPreview(rawText, options = {}) {
|
|
|
|
|
|
if (String(options.sessionType || '').trim() !== APPLICATION_SESSION_TYPE) return false
|
|
|
|
|
|
if (options.systemGenerated || options.reviewAction || Number(options.attachmentCount || 0) > 0) return false
|
|
|
|
|
|
|
|
|
|
|
|
const compact = compactText(rawText)
|
|
|
|
|
|
if (!compact || APPLICATION_QUERY_PATTERN.test(compact)) return false
|
|
|
|
|
|
return APPLICATION_CREATE_PATTERN.test(compact)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function normalizeApplicationPreview(preview = {}) {
|
|
|
|
|
|
const fields = ensureApplicationPolicyFields(preview?.fields || {})
|
|
|
|
|
|
const missingFields = buildMissingFields(fields)
|
|
|
|
|
|
return {
|
|
|
|
|
|
...preview,
|
|
|
|
|
|
fields,
|
|
|
|
|
|
missingFields,
|
|
|
|
|
|
readyToSubmit: missingFields.length === 0
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function buildModelRefinedApplicationPreview(localPreview = {}, ontology = {}, rawText = '', currentUser = {}) {
|
|
|
|
|
|
const currentFields = localPreview?.fields || {}
|
|
|
|
|
|
const ontologyFields = buildApplicationFieldsFromOntology(ontology || {}, rawText, currentUser)
|
|
|
|
|
|
const parseStrategy = String(ontology?.parse_strategy || '').trim()
|
|
|
|
|
|
const refinedFields = {
|
|
|
|
|
|
...currentFields,
|
|
|
|
|
|
applicationType: normalizeApplicationTypeLabel(
|
|
|
|
|
|
ontologyFields.expenseTypeLabel,
|
|
|
|
|
|
currentFields.applicationType
|
|
|
|
|
|
),
|
|
|
|
|
|
time: resolveProvidedValue(ontologyFields.timeRange, currentFields.time),
|
|
|
|
|
|
location: resolveProvidedValue(ontologyFields.location, currentFields.location),
|
|
|
|
|
|
reason: resolveProvidedValue(ontologyFields.reason, currentFields.reason),
|
|
|
|
|
|
days: resolveProvidedValue(ontologyFields.days, currentFields.days),
|
|
|
|
|
|
transportMode: normalizeTransportModeOption(
|
|
|
|
|
|
ontologyFields.transportMode,
|
|
|
|
|
|
currentFields.transportMode
|
|
|
|
|
|
),
|
|
|
|
|
|
amount: normalizeAmountFromOntology(ontologyFields, currentFields.amount),
|
|
|
|
|
|
grade: resolveProvidedValue(currentFields.grade, resolveCurrentUserGrade(currentUser)),
|
|
|
|
|
|
applicant: resolveProvidedValue(ontologyFields.applicant, currentFields.applicant),
|
2026-06-01 17:07:14 +08:00
|
|
|
|
department: resolveProvidedValue(ontologyFields.department, currentFields.department || resolveCurrentUserDepartment(currentUser)),
|
|
|
|
|
|
position: resolveProvidedValue(currentFields.position, resolveCurrentUserPosition(currentUser)),
|
|
|
|
|
|
managerName: resolveProvidedValue(
|
|
|
|
|
|
ontologyFields.managerName,
|
|
|
|
|
|
currentFields.managerName || resolveCurrentUserManagerName(currentUser)
|
|
|
|
|
|
)
|
2026-05-26 09:15:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return normalizeApplicationPreview({
|
|
|
|
|
|
...localPreview,
|
|
|
|
|
|
sourceText: String(rawText || localPreview.sourceText || '').trim(),
|
|
|
|
|
|
fields: refinedFields,
|
|
|
|
|
|
modelRefined: true,
|
|
|
|
|
|
parseStrategy,
|
|
|
|
|
|
modelReviewStatus: parseStrategy === 'llm_primary' ? 'completed' : 'fallback'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function buildApplicationPreviewRows(preview = {}) {
|
|
|
|
|
|
const normalized = normalizeApplicationPreview(preview)
|
|
|
|
|
|
const fields = normalized.fields || {}
|
|
|
|
|
|
return APPLICATION_PREVIEW_FIELD_DEFINITIONS.map((item) => {
|
|
|
|
|
|
const rawValue = fields[item.key]
|
|
|
|
|
|
const value = String(rawValue || '').trim() || '待补充'
|
|
|
|
|
|
return {
|
|
|
|
|
|
...item,
|
|
|
|
|
|
value,
|
|
|
|
|
|
editable: item.editable !== false,
|
|
|
|
|
|
highlight: Boolean(item.highlight),
|
|
|
|
|
|
missing: item.required !== false && !isApplicationPreviewValueProvided(rawValue)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function buildApplicationPreviewSubmitText(preview = {}) {
|
|
|
|
|
|
const rows = buildApplicationPreviewRows(preview)
|
|
|
|
|
|
return [
|
|
|
|
|
|
'费用申请确认提交',
|
|
|
|
|
|
...rows.map((row) => `${row.label}:${row.value}`),
|
|
|
|
|
|
'',
|
|
|
|
|
|
'确认提交'
|
|
|
|
|
|
].join('\n')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-30 15:46:51 +08:00
|
|
|
|
export function buildLocalApplicationPreview(rawText, currentUser = {}, options = {}) {
|
2026-05-26 09:15:14 +08:00
|
|
|
|
const sourceText = String(rawText || '').trim()
|
|
|
|
|
|
const explicitDays = resolveApplicationDays(sourceText)
|
2026-05-30 15:46:51 +08:00
|
|
|
|
const time = resolveApplicationTimeWithDefault(sourceText, explicitDays, options)
|
2026-05-26 09:15:14 +08:00
|
|
|
|
const days = explicitDays || resolveDaysFromDateRange(time)
|
|
|
|
|
|
const location = resolveApplicationLocation(sourceText)
|
|
|
|
|
|
const fields = {
|
|
|
|
|
|
applicationType: resolveApplicationType(sourceText),
|
|
|
|
|
|
time,
|
|
|
|
|
|
location,
|
|
|
|
|
|
reason: resolveApplicationReason(sourceText, { location }),
|
|
|
|
|
|
days,
|
|
|
|
|
|
transportMode: resolveApplicationTransportMode(sourceText),
|
|
|
|
|
|
amount: resolveApplicationAmount(sourceText),
|
|
|
|
|
|
grade: resolveCurrentUserGrade(currentUser),
|
|
|
|
|
|
applicant: currentUser.name || currentUser.username || '当前用户',
|
2026-06-01 17:07:14 +08:00
|
|
|
|
department: resolveCurrentUserDepartment(currentUser) || '待补充',
|
|
|
|
|
|
position: resolveCurrentUserPosition(currentUser) || '待补充',
|
|
|
|
|
|
managerName: resolveCurrentUserManagerName(currentUser) || '待补充'
|
2026-05-26 09:15:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return normalizeApplicationPreview({
|
|
|
|
|
|
sourceText,
|
|
|
|
|
|
fields,
|
|
|
|
|
|
modelReviewStatus: 'local'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function buildApplicationTemplatePreview(currentUser = {}) {
|
|
|
|
|
|
return normalizeApplicationPreview({
|
|
|
|
|
|
sourceText: '快速发起申请',
|
|
|
|
|
|
fields: {
|
|
|
|
|
|
applicationType: '费用申请',
|
|
|
|
|
|
time: '',
|
|
|
|
|
|
location: '',
|
|
|
|
|
|
reason: '',
|
|
|
|
|
|
days: '',
|
|
|
|
|
|
transportMode: '',
|
|
|
|
|
|
amount: '',
|
|
|
|
|
|
grade: resolveCurrentUserGrade(currentUser),
|
|
|
|
|
|
applicant: currentUser.name || currentUser.username || '当前用户',
|
2026-06-01 17:07:14 +08:00
|
|
|
|
department: resolveCurrentUserDepartment(currentUser) || '待补充',
|
|
|
|
|
|
position: resolveCurrentUserPosition(currentUser) || '待补充',
|
|
|
|
|
|
managerName: resolveCurrentUserManagerName(currentUser) || '待补充'
|
2026-05-26 09:15:14 +08:00
|
|
|
|
},
|
|
|
|
|
|
modelReviewStatus: 'template'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function buildLocalApplicationPreviewMessage(preview) {
|
|
|
|
|
|
const normalized = normalizeApplicationPreview(preview)
|
|
|
|
|
|
const modelReviewStatus = String(normalized.modelReviewStatus || '').trim()
|
|
|
|
|
|
return [
|
|
|
|
|
|
modelReviewStatus === 'completed'
|
|
|
|
|
|
? '我已完成模型复核,并整理成下方表格。请核查识别结果;点击对应行即可直接编辑。'
|
|
|
|
|
|
: modelReviewStatus === 'fallback'
|
|
|
|
|
|
? '模型复核没有返回稳定结果,我已先按规则兜底整理成下方表格。请重点核查识别结果;点击对应行即可直接编辑。'
|
|
|
|
|
|
: modelReviewStatus === 'failed'
|
|
|
|
|
|
? '模型复核暂时失败,我先保留一份临时核对表,方便您核查和补充信息。点击对应行即可直接编辑。'
|
|
|
|
|
|
: modelReviewStatus === 'template'
|
|
|
|
|
|
? '我已为你准备好费用申请模板。本步骤不调用大模型,也不会保存草稿;请点击对应行直接填写。'
|
|
|
|
|
|
: '我先整理出下方表格,请核查识别结果。点击对应行即可直接编辑。'
|
|
|
|
|
|
].join('\n')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function buildApplicationPreviewFooterMessage(preview) {
|
|
|
|
|
|
const normalized = normalizeApplicationPreview(preview)
|
|
|
|
|
|
const missingFields = Array.isArray(normalized.missingFields) ? normalized.missingFields : []
|
|
|
|
|
|
if (missingFields.length) {
|
|
|
|
|
|
return `当前还需要补充:${missingFields.join('、')}。补齐后我再帮您提交申请。`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-27 12:27:17 +08:00
|
|
|
|
return '请确认上述的信息是否填写正确?如果准确无误,点击 [确认](#application-submit) 进入审批环节。'
|
2026-05-26 09:15:14 +08:00
|
|
|
|
}
|