2026-05-26 09:15:14 +08:00
|
|
|
|
import { buildApplicationFieldsFromOntology } from './expenseApplicationOntology.js'
|
2026-06-13 14:52:26 +00:00
|
|
|
|
import { evaluateLocalApplicationIntentGate } from './expenseApplicationIntentGate.js'
|
2026-06-01 17:07:14 +08:00
|
|
|
|
import {
|
2026-06-13 14:52:26 +00:00
|
|
|
|
formatApplicationEstimateMoney,
|
|
|
|
|
|
parseApplicationEstimateMoney,
|
2026-06-01 17:07:14 +08:00
|
|
|
|
buildSystemApplicationEstimate
|
|
|
|
|
|
} from './expenseApplicationEstimate.js'
|
2026-06-22 11:58:53 +08:00
|
|
|
|
import {
|
|
|
|
|
|
APPLICATION_POLICY_PENDING_TEXT,
|
|
|
|
|
|
APPLICATION_PREVIEW_FIELD_DEFINITIONS,
|
|
|
|
|
|
APPLICATION_TRANSPORT_REIMBURSEMENT_TEXT,
|
|
|
|
|
|
buildMissingFields,
|
|
|
|
|
|
buildTransportEstimateFromPolicyResult,
|
|
|
|
|
|
buildTransportPolicyText,
|
|
|
|
|
|
ensureApplicationPolicyFields,
|
|
|
|
|
|
formatDailyPolicyMoney,
|
|
|
|
|
|
formatPolicyMoney,
|
|
|
|
|
|
isApplicationPreviewValueProvided,
|
|
|
|
|
|
isTravelApplicationType,
|
|
|
|
|
|
normalizeAmountFromOntology,
|
|
|
|
|
|
normalizeApplicationTypeLabel,
|
|
|
|
|
|
normalizeTypedOntologyAmount,
|
|
|
|
|
|
parseApplicationDaysValue,
|
|
|
|
|
|
parseMoneyNumber,
|
|
|
|
|
|
resolveApplicationAmount,
|
|
|
|
|
|
resolveApplicationDays,
|
|
|
|
|
|
resolveApplicationFieldLabel,
|
|
|
|
|
|
resolveApplicationLocation,
|
|
|
|
|
|
resolveApplicationReason,
|
|
|
|
|
|
resolveApplicationSourceValidationIssues,
|
|
|
|
|
|
resolveApplicationTimeWithDefault,
|
|
|
|
|
|
resolveApplicationTransportMode,
|
|
|
|
|
|
resolveApplicationTripDateParts,
|
|
|
|
|
|
resolveApplicationType,
|
|
|
|
|
|
resolveApplicationValidationIssues,
|
|
|
|
|
|
resolveCurrentUserDepartment,
|
|
|
|
|
|
resolveCurrentUserGrade,
|
|
|
|
|
|
resolveCurrentUserManagerName,
|
|
|
|
|
|
resolveCurrentUserPosition,
|
|
|
|
|
|
resolveDaysFromDateRange,
|
|
|
|
|
|
resolveModelRefinedTransportMode,
|
|
|
|
|
|
resolveProvidedValue
|
|
|
|
|
|
} from './expenseApplicationPreviewParsing.js'
|
|
|
|
|
|
export {
|
|
|
|
|
|
APPLICATION_PREVIEW_FIELD_DEFINITIONS,
|
|
|
|
|
|
APPLICATION_TRANSPORT_MODE_OPTIONS,
|
|
|
|
|
|
applicationDateRangesOverlap,
|
|
|
|
|
|
normalizeTransportModeOption,
|
|
|
|
|
|
resolveApplicationDateRange,
|
|
|
|
|
|
resolveApplicationDaysFromDateRange,
|
|
|
|
|
|
resolveApplicationTimeLabel,
|
|
|
|
|
|
shouldRequireApplicationModelReview
|
|
|
|
|
|
} from './expenseApplicationPreviewParsing.js'
|
2026-05-26 09:15:14 +08:00
|
|
|
|
|
|
|
|
|
|
export function buildApplicationPolicyEstimateRequest(preview = {}, currentUser = {}) {
|
|
|
|
|
|
const normalized = normalizeApplicationPreview(preview)
|
|
|
|
|
|
const fields = normalized.fields || {}
|
2026-06-22 15:56:06 +08:00
|
|
|
|
const days = parseApplicationDaysValue(fields.days) || parseApplicationDaysValue(resolveDaysFromDateRange(fields.time))
|
2026-05-26 09:15:14 +08:00
|
|
|
|
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,
|
2026-06-13 14:52:26 +00:00
|
|
|
|
grade,
|
|
|
|
|
|
transport_mode: transportMode || null,
|
|
|
|
|
|
origin_location: String(
|
|
|
|
|
|
currentUser.location
|
|
|
|
|
|
|| currentUser.officeLocation
|
|
|
|
|
|
|| currentUser.office_location
|
|
|
|
|
|
|| currentUser.baseCity
|
|
|
|
|
|
|| currentUser.base_city
|
|
|
|
|
|
|| ''
|
|
|
|
|
|
).trim() || null,
|
|
|
|
|
|
travel_date: resolveApplicationTripDateParts(fields).startDate || null
|
2026-05-26 09:15:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function applyApplicationPolicyEstimateResult(preview = {}, result = {}, currentUser = {}) {
|
2026-06-13 14:52:26 +00:00
|
|
|
|
const fields = {
|
2026-06-20 10:17:37 +08:00
|
|
|
|
...(preview?.fields || {})
|
2026-06-13 14:52:26 +00:00
|
|
|
|
}
|
2026-05-26 09:15:14 +08:00
|
|
|
|
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-13 14:52:26 +00:00
|
|
|
|
if (isTravelApplicationType(fields.applicationType) && !String(fields.transportMode || '').trim()) {
|
2026-06-20 10:17:37 +08:00
|
|
|
|
const days = Number(result?.days) || parseApplicationDaysValue(fields.days) || 1
|
|
|
|
|
|
const baseTotalAmount = parseMoneyNumber(result?.hotel_amount) + parseMoneyNumber(result?.allowance_amount)
|
|
|
|
|
|
const baseTotalDisplay = Number.isFinite(baseTotalAmount) && baseTotalAmount > 0
|
|
|
|
|
|
? formatPolicyMoney(baseTotalAmount)
|
|
|
|
|
|
: ''
|
2026-06-13 14:52:26 +00:00
|
|
|
|
return normalizeApplicationPreview({
|
|
|
|
|
|
...preview,
|
|
|
|
|
|
fields: {
|
|
|
|
|
|
...fields,
|
|
|
|
|
|
grade,
|
2026-06-22 15:56:06 +08:00
|
|
|
|
days: parseApplicationDaysValue(fields.days) ? fields.days : `${days}天`,
|
2026-06-13 14:52:26 +00:00
|
|
|
|
lodgingDailyCap: formatDailyPolicyMoney(result?.hotel_rate),
|
|
|
|
|
|
subsidyDailyCap: formatDailyPolicyMoney(result?.total_allowance_rate),
|
|
|
|
|
|
transportPolicy: APPLICATION_TRANSPORT_REIMBURSEMENT_TEXT,
|
2026-06-20 10:17:37 +08:00
|
|
|
|
policyEstimate: baseTotalDisplay
|
|
|
|
|
|
? `交通待补充 + 住宿 ${hotelAmount}元 + 补贴 ${allowanceAmount}元 = ${baseTotalDisplay}元(${days}天,不含交通)`
|
|
|
|
|
|
: APPLICATION_POLICY_PENDING_TEXT,
|
|
|
|
|
|
amount: baseTotalDisplay ? `${baseTotalDisplay}元(不含交通)` : fields.amount,
|
2026-06-13 14:52:26 +00:00
|
|
|
|
matchedCity,
|
|
|
|
|
|
ruleName: String(result?.rule_name || '').trim(),
|
|
|
|
|
|
ruleVersion: String(result?.rule_version || '').trim(),
|
|
|
|
|
|
hotelAmount: hotelAmount ? `${hotelAmount}元` : '',
|
|
|
|
|
|
allowanceAmount: allowanceAmount ? `${allowanceAmount}元` : '',
|
|
|
|
|
|
transportEstimatedAmount: '',
|
|
|
|
|
|
transportEstimateDate: '',
|
|
|
|
|
|
transportQueryLatencyMs: '',
|
|
|
|
|
|
transportEstimateSource: '',
|
|
|
|
|
|
transportEstimateConfidence: '',
|
2026-06-20 10:17:37 +08:00
|
|
|
|
policyTotalAmount: baseTotalDisplay ? `${baseTotalDisplay}元(不含交通)` : ''
|
2026-06-13 14:52:26 +00:00
|
|
|
|
},
|
|
|
|
|
|
policyEstimateStatus: 'pending'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
const days = Number(result?.days) || parseApplicationDaysValue(fields.days) || 1
|
|
|
|
|
|
let systemEstimate = buildSystemApplicationEstimate({
|
2026-06-01 17:07:14 +08:00
|
|
|
|
transportMode: fields.transportMode,
|
|
|
|
|
|
location: matchedCity || fields.location,
|
|
|
|
|
|
time: fields.time,
|
|
|
|
|
|
lodgingAmount: result?.hotel_amount,
|
|
|
|
|
|
allowanceAmount: result?.allowance_amount
|
|
|
|
|
|
})
|
2026-06-13 14:52:26 +00:00
|
|
|
|
const policyTransportEstimate = buildTransportEstimateFromPolicyResult(result, fields)
|
|
|
|
|
|
if (policyTransportEstimate) {
|
|
|
|
|
|
const lodging = parseApplicationEstimateMoney(result?.hotel_amount)
|
|
|
|
|
|
const allowance = parseApplicationEstimateMoney(result?.allowance_amount)
|
|
|
|
|
|
const backendTotal = parseApplicationEstimateMoney(result?.total_amount)
|
|
|
|
|
|
const totalAmount = backendTotal > 0
|
|
|
|
|
|
? backendTotal
|
|
|
|
|
|
: policyTransportEstimate.amount + lodging + allowance
|
|
|
|
|
|
systemEstimate = {
|
|
|
|
|
|
transportEstimate: policyTransportEstimate,
|
|
|
|
|
|
transportAmount: policyTransportEstimate.amount,
|
|
|
|
|
|
lodgingAmount: lodging,
|
|
|
|
|
|
allowanceAmount: allowance,
|
|
|
|
|
|
totalAmount,
|
|
|
|
|
|
transportAmountDisplay: policyTransportEstimate.amountDisplay,
|
|
|
|
|
|
lodgingAmountDisplay: formatApplicationEstimateMoney(lodging),
|
|
|
|
|
|
allowanceAmountDisplay: formatApplicationEstimateMoney(allowance),
|
|
|
|
|
|
totalAmountDisplay: formatApplicationEstimateMoney(totalAmount)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-01 17:07:14 +08:00
|
|
|
|
const transportEstimate = systemEstimate.transportEstimate
|
|
|
|
|
|
const transportText = transportEstimate
|
2026-06-02 16:22:59 +08:00
|
|
|
|
? `交通 ${systemEstimate.transportAmountDisplay}元 + `
|
2026-06-01 17:07:14 +08:00
|
|
|
|
: ''
|
|
|
|
|
|
const totalAmount = systemEstimate.totalAmountDisplay
|
|
|
|
|
|
const amount = totalAmount ? `${totalAmount}元` : fields.amount
|
2026-05-26 09:15:14 +08:00
|
|
|
|
|
|
|
|
|
|
return normalizeApplicationPreview({
|
|
|
|
|
|
...preview,
|
|
|
|
|
|
fields: {
|
|
|
|
|
|
...fields,
|
|
|
|
|
|
grade,
|
2026-06-22 15:56:06 +08:00
|
|
|
|
days: parseApplicationDaysValue(fields.days) ? fields.days : `${days}天`,
|
2026-05-26 09:15:14 +08:00
|
|
|
|
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 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
|
2026-06-02 16:22:59 +08:00
|
|
|
|
nextFields.policyEstimate = `交通 ${systemEstimate.transportAmountDisplay}元 + 住宿 ${hotelAmount}元 + 补贴 ${allowanceAmount}元 = ${totalAmount}元(${days}天)`
|
2026-06-01 17:07:14 +08:00
|
|
|
|
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 = {}) {
|
2026-06-13 14:52:26 +00:00
|
|
|
|
return evaluateLocalApplicationIntentGate(rawText, options).allowed
|
2026-05-26 09:15:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function normalizeApplicationPreview(preview = {}) {
|
|
|
|
|
|
const fields = ensureApplicationPolicyFields(preview?.fields || {})
|
|
|
|
|
|
const missingFields = buildMissingFields(fields)
|
2026-06-13 14:52:26 +00:00
|
|
|
|
const validationIssues = [
|
|
|
|
|
|
...resolveApplicationValidationIssues(fields),
|
|
|
|
|
|
...resolveApplicationSourceValidationIssues(preview?.sourceText, fields, preview)
|
|
|
|
|
|
]
|
2026-05-26 09:15:14 +08:00
|
|
|
|
return {
|
|
|
|
|
|
...preview,
|
|
|
|
|
|
fields,
|
|
|
|
|
|
missingFields,
|
2026-06-13 14:52:26 +00:00
|
|
|
|
validationIssues,
|
|
|
|
|
|
readyToSubmit: missingFields.length === 0 && validationIssues.length === 0
|
2026-05-26 09:15:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-02 14:01:51 +08:00
|
|
|
|
export function applyApplicationBusinessTimeContext(preview = {}, businessTimeContext = null) {
|
|
|
|
|
|
if (!businessTimeContext || typeof businessTimeContext !== 'object') {
|
|
|
|
|
|
return normalizeApplicationPreview(preview)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const startDate = String(businessTimeContext.start_date || '').trim()
|
|
|
|
|
|
const endDate = String(businessTimeContext.end_date || startDate).trim()
|
|
|
|
|
|
const displayValue = String(
|
|
|
|
|
|
businessTimeContext.business_time ||
|
|
|
|
|
|
businessTimeContext.time_range ||
|
|
|
|
|
|
businessTimeContext.display_value ||
|
|
|
|
|
|
''
|
|
|
|
|
|
).trim()
|
|
|
|
|
|
const time = startDate && endDate
|
|
|
|
|
|
? (startDate === endDate ? startDate : `${startDate} 至 ${endDate}`)
|
|
|
|
|
|
: displayValue
|
|
|
|
|
|
if (!time) {
|
|
|
|
|
|
return normalizeApplicationPreview(preview)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const normalized = normalizeApplicationPreview(preview)
|
|
|
|
|
|
const fields = normalized.fields || {}
|
|
|
|
|
|
return normalizeApplicationPreview({
|
|
|
|
|
|
...normalized,
|
|
|
|
|
|
fields: {
|
|
|
|
|
|
...fields,
|
|
|
|
|
|
time,
|
|
|
|
|
|
days: resolveDaysFromDateRange(time) || fields.days
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 09:15:14 +08:00
|
|
|
|
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),
|
2026-06-03 16:36:02 +08:00
|
|
|
|
transportMode: resolveModelRefinedTransportMode(ontologyFields, rawText, currentFields),
|
2026-05-26 09:15:14 +08:00
|
|
|
|
amount: normalizeAmountFromOntology(ontologyFields, currentFields.amount),
|
2026-06-13 14:52:26 +00:00
|
|
|
|
transportEstimatedAmount: normalizeTypedOntologyAmount(
|
|
|
|
|
|
ontologyFields.transportEstimatedAmount || ontologyFields.trainEstimatedAmount || ontologyFields.flightEstimatedAmount,
|
|
|
|
|
|
currentFields.transportEstimatedAmount
|
|
|
|
|
|
),
|
|
|
|
|
|
trainEstimatedAmount: normalizeTypedOntologyAmount(ontologyFields.trainEstimatedAmount, currentFields.trainEstimatedAmount),
|
|
|
|
|
|
flightEstimatedAmount: normalizeTypedOntologyAmount(ontologyFields.flightEstimatedAmount, currentFields.flightEstimatedAmount),
|
|
|
|
|
|
hotelAmount: normalizeTypedOntologyAmount(ontologyFields.hotelAmount, currentFields.hotelAmount),
|
|
|
|
|
|
allowanceAmount: normalizeTypedOntologyAmount(ontologyFields.allowanceAmount, currentFields.allowanceAmount),
|
|
|
|
|
|
policyTotalAmount: normalizeTypedOntologyAmount(ontologyFields.policyTotalAmount, currentFields.policyTotalAmount),
|
|
|
|
|
|
reimbursementAmount: normalizeTypedOntologyAmount(ontologyFields.reimbursementAmount, currentFields.reimbursementAmount),
|
2026-05-26 09:15:14 +08:00
|
|
|
|
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 || {}
|
2026-06-02 16:22:59 +08:00
|
|
|
|
return APPLICATION_PREVIEW_FIELD_DEFINITIONS.flatMap((item) => {
|
|
|
|
|
|
if (item.key === 'time' && isTravelApplicationType(fields.applicationType)) {
|
|
|
|
|
|
const tripDates = resolveApplicationTripDateParts(fields)
|
|
|
|
|
|
const rawValue = fields[item.key]
|
|
|
|
|
|
const missing = item.required !== false && !isApplicationPreviewValueProvided(rawValue)
|
|
|
|
|
|
return [
|
|
|
|
|
|
{
|
|
|
|
|
|
...item,
|
|
|
|
|
|
label: '出发时间',
|
|
|
|
|
|
value: tripDates.startDate || '待补充',
|
|
|
|
|
|
editable: item.editable !== false,
|
|
|
|
|
|
highlight: Boolean(item.highlight),
|
|
|
|
|
|
missing
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'time_return',
|
|
|
|
|
|
label: '返回时间',
|
|
|
|
|
|
value: tripDates.endDate || '待补充',
|
2026-06-22 15:56:06 +08:00
|
|
|
|
editable: item.editable !== false,
|
2026-06-02 16:22:59 +08:00
|
|
|
|
highlight: Boolean(item.highlight),
|
|
|
|
|
|
missing
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 09:15:14 +08:00
|
|
|
|
const rawValue = fields[item.key]
|
|
|
|
|
|
const value = String(rawValue || '').trim() || '待补充'
|
2026-06-02 16:22:59 +08:00
|
|
|
|
return [{
|
2026-05-26 09:15:14 +08:00
|
|
|
|
...item,
|
2026-06-02 14:01:51 +08:00
|
|
|
|
label: resolveApplicationFieldLabel(item, fields),
|
2026-05-26 09:15:14 +08:00
|
|
|
|
value,
|
|
|
|
|
|
editable: item.editable !== false,
|
|
|
|
|
|
highlight: Boolean(item.highlight),
|
|
|
|
|
|
missing: item.required !== false && !isApplicationPreviewValueProvided(rawValue)
|
2026-06-02 16:22:59 +08:00
|
|
|
|
}]
|
2026-05-26 09:15:14 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 : []
|
2026-06-13 14:52:26 +00:00
|
|
|
|
const validationIssues = Array.isArray(normalized.validationIssues) ? normalized.validationIssues : []
|
|
|
|
|
|
if (validationIssues.length) {
|
|
|
|
|
|
return `${validationIssues[0].message} 请先修正后再提交申请。`
|
|
|
|
|
|
}
|
2026-05-26 09:15:14 +08:00
|
|
|
|
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
|
|
|
|
}
|