feat(web): 申请单预览编辑器增强与报销流程细节适配
- useApplicationPreviewEditor 扩展字段编辑与校验,useTravelReimbursementApplicationPreviewDateEditor 微调日期处理 - travelReimbursementExpenseQueryModel/reimbursements 服务/expenseApplicationPreview 适配工号/邮箱字段与关联动作 - useWorkbenchAiApplicationPreviewFlow/usePersonalWorkbenchAiMode 接入关联门控后的预览流转 - TravelReimbursementCreateView 调整入口,TravelReimbursementMessageItem 适配 - 新增 expense-application-fast-preview 测试,更新 attachment-association-confirmation、review-drawer-switch 测试
This commit is contained in:
@@ -117,8 +117,12 @@ test('attachment upload association uses conversation selection instead of legac
|
||||
fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementFlow.js', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
const flowToolSource = readFileSync(
|
||||
fileURLToPath(new URL('../src/views/scripts/travelReimbursementFlowToolModel.js', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
const conversationSource = readFileSync(
|
||||
fileURLToPath(new URL('../src/views/scripts/travelReimbursementConversationModel.js', import.meta.url)),
|
||||
fileURLToPath(new URL('../src/views/scripts/travelReimbursementConversationSessionModel.js', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
|
||||
@@ -140,7 +144,7 @@ test('attachment upload association uses conversation selection instead of legac
|
||||
assert.match(submitComposerSource, /const appendToCurrentFlow = Boolean\(options\.appendToCurrentFlow\)/)
|
||||
assert.match(submitComposerSource, /if \(!appendToCurrentFlow\) \{\s*resetFlowRun\(\)\s*\} else \{\s*clearFlowSimulationTimers\(\)/)
|
||||
assert.match(flowSource, /link_to_existing_draft:\s*\{[\s\S]*key:\s*'attachment-association'/)
|
||||
assert.match(flowSource, /responseMessage\.includes\('关联'\)[\s\S]*key:\s*'attachment-association'/)
|
||||
assert.match(flowToolSource, /responseMessage\.includes\('关联'\)[\s\S]*key:\s*'attachment-association'/)
|
||||
assert.match(flowSource, /'draft-risk-review'/)
|
||||
assert.match(flowSource, /草稿风险识别/)
|
||||
assert.match(conversationSource, /'attachment-association':\s*\{[\s\S]*title:\s*'票据关联草稿'/)
|
||||
|
||||
@@ -436,6 +436,7 @@ test('application preview uses selected date range and business-specific time la
|
||||
assert.equal(rows.find((row) => row.key === 'time')?.value, '2026-02-20')
|
||||
assert.equal(rows.find((row) => row.key === 'time_return')?.label, '返回时间')
|
||||
assert.equal(rows.find((row) => row.key === 'time_return')?.value, '2026-02-23')
|
||||
assert.equal(rows.find((row) => row.key === 'time_return')?.editable, true)
|
||||
assert.match(submitText, /出发时间:2026-02-20/)
|
||||
assert.match(submitText, /返回时间:2026-02-23/)
|
||||
assert.match(submitText, /事由:支撑国网仿生产环境部署/)
|
||||
@@ -1610,6 +1611,52 @@ test('application preview calculates base policy estimate when transport mode is
|
||||
assert.equal(staleEstimatePreview.fields.amount, '1,400元(不含交通)')
|
||||
})
|
||||
|
||||
test('application preview estimate infers days from completed date range', () => {
|
||||
const currentUser = { name: '\u674e\u6587\u9759', grade: 'P5', location: '\u6b66\u6c49' }
|
||||
const preview = normalizeApplicationPreview({
|
||||
fields: {
|
||||
applicationType: '\u5dee\u65c5\u8d39\u7528\u7533\u8bf7',
|
||||
time: '2026-06-23 \u81f3 2026-06-25',
|
||||
location: '\u5317\u4eac',
|
||||
reason: '\u652f\u6491\u5ba2\u6237\u73b0\u573a\u5b9e\u65bd',
|
||||
days: '',
|
||||
transportMode: '',
|
||||
grade: 'P5'
|
||||
}
|
||||
})
|
||||
const request = buildApplicationPolicyEstimateRequest(preview, currentUser)
|
||||
|
||||
assert.equal(request.canCalculate, true)
|
||||
assert.deepEqual(request.payload, {
|
||||
days: 3,
|
||||
location: '\u5317\u4eac',
|
||||
grade: 'P5',
|
||||
transport_mode: null,
|
||||
origin_location: '\u6b66\u6c49',
|
||||
travel_date: '2026-06-23'
|
||||
})
|
||||
|
||||
const estimatedPreview = applyApplicationPolicyEstimateResult(preview, {
|
||||
days: 3,
|
||||
location: '\u5317\u4eac',
|
||||
matched_city: '\u5317\u4eac',
|
||||
grade: 'P5',
|
||||
hotel_rate: 450,
|
||||
hotel_amount: 1350,
|
||||
total_allowance_rate: 100,
|
||||
allowance_amount: 300,
|
||||
total_amount: 1650,
|
||||
rule_name: '\u516c\u53f8\u5dee\u65c5\u8d39\u62a5\u9500\u89c4\u5219',
|
||||
rule_version: 'v1.0.0'
|
||||
}, currentUser)
|
||||
|
||||
assert.equal(estimatedPreview.fields.days, '3\u5929')
|
||||
assert.equal(estimatedPreview.fields.lodgingDailyCap, '450\u5143/\u5929')
|
||||
assert.equal(estimatedPreview.fields.subsidyDailyCap, '100\u5143/\u5929')
|
||||
assert.equal(estimatedPreview.fields.amount, '1,650\u5143\uff08\u4e0d\u542b\u4ea4\u901a\uff09')
|
||||
assert.match(estimatedPreview.fields.policyEstimate, /\u4ea4\u901a\u5f85\u8865\u5145/)
|
||||
})
|
||||
|
||||
test('application preview editor refreshes transport estimate after mode change', async () => {
|
||||
const preview = applyApplicationPolicyEstimateResult(
|
||||
buildLocalApplicationPreview('申请 2026-05-25 至 2026-05-27 去上海出差3天,服务项目部署', {
|
||||
@@ -1726,3 +1773,197 @@ test('application preview editor recalculates days and subsidy after date range
|
||||
assert.equal(message.applicationPreview.fields.subsidyDailyCap, '100\u5143/\u5929')
|
||||
assert.match(message.applicationPreview.fields.policyEstimate, /\u8865\u8d34 400\u5143/)
|
||||
})
|
||||
|
||||
test('application preview editor can edit return date from table row', async () => {
|
||||
const preview = normalizeApplicationPreview({
|
||||
fields: {
|
||||
applicationType: '\u5dee\u65c5\u8d39\u7528\u7533\u8bf7',
|
||||
time: '2026-02-20 \u81f3 2026-02-23',
|
||||
location: '\u4e0a\u6d77',
|
||||
reason: '\u670d\u52a1\u9879\u76ee\u90e8\u7f72',
|
||||
days: '4\u5929',
|
||||
transportMode: '\u706b\u8f66',
|
||||
amount: '',
|
||||
grade: 'P5',
|
||||
applicant: '\u674e\u6587\u9759',
|
||||
department: '\u6280\u672f\u90e8',
|
||||
position: '\u8d22\u52a1\u667a\u80fd\u5316\u4ea7\u54c1\u7ecf\u7406',
|
||||
managerName: '\u5411\u4e07\u7ea2'
|
||||
}
|
||||
})
|
||||
const message = {
|
||||
id: 'application-preview-editor-return-date-message',
|
||||
applicationPreview: preview,
|
||||
text: ''
|
||||
}
|
||||
const requestedPayloads = []
|
||||
const editor = useApplicationPreviewEditor({
|
||||
persistSessionState: () => {},
|
||||
toast: () => {},
|
||||
currentUser: ref({ grade: 'P5' }),
|
||||
calculateTravelReimbursement: async (payload) => {
|
||||
requestedPayloads.push(payload)
|
||||
return {
|
||||
days: payload.days,
|
||||
location: payload.location,
|
||||
matched_city: payload.location,
|
||||
grade: payload.grade,
|
||||
hotel_rate: 450,
|
||||
hotel_amount: 2250,
|
||||
total_allowance_rate: 100,
|
||||
allowance_amount: 500,
|
||||
total_amount: 2750,
|
||||
rule_name: '\u516c\u53f8\u5dee\u65c5\u8d39\u62a5\u9500\u89c4\u5219',
|
||||
rule_version: 'v1.0.0'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
editor.openApplicationPreviewEditor(message, 'time_return', '2026-02-23')
|
||||
assert.equal(editor.resolveApplicationPreviewEditorControl('time_return'), 'date')
|
||||
assert.equal(editor.applicationPreviewEditor.value.dateMode, 'range')
|
||||
assert.equal(editor.applicationPreviewEditor.value.rangeStartDate, '2026-02-20')
|
||||
editor.applicationPreviewEditor.value.rangeEndDate = '2026-02-24'
|
||||
const committed = await editor.commitApplicationPreviewDateEditor(message)
|
||||
|
||||
assert.equal(committed, true)
|
||||
assert.deepEqual(requestedPayloads.at(-1), {
|
||||
days: 5,
|
||||
location: '\u4e0a\u6d77',
|
||||
grade: 'P5',
|
||||
transport_mode: '\u706b\u8f66',
|
||||
origin_location: null,
|
||||
travel_date: '2026-02-20'
|
||||
})
|
||||
assert.equal(message.applicationPreview.fields.time, '2026-02-20 \u81f3 2026-02-24')
|
||||
assert.equal(message.applicationPreview.fields.days, '5\u5929')
|
||||
})
|
||||
|
||||
test('application preview editor can edit return date from inline table input', async () => {
|
||||
const preview = normalizeApplicationPreview({
|
||||
fields: {
|
||||
applicationType: '\u5dee\u65c5\u8d39\u7528\u7533\u8bf7',
|
||||
time: '2026-02-20 \u81f3 2026-02-23',
|
||||
location: '\u4e0a\u6d77',
|
||||
reason: '\u670d\u52a1\u9879\u76ee\u90e8\u7f72',
|
||||
days: '4\u5929',
|
||||
transportMode: '\u706b\u8f66',
|
||||
amount: '',
|
||||
grade: 'P5',
|
||||
applicant: '\u674e\u6587\u9759',
|
||||
department: '\u6280\u672f\u90e8',
|
||||
position: '\u8d22\u52a1\u667a\u80fd\u5316\u4ea7\u54c1\u7ecf\u7406',
|
||||
managerName: '\u5411\u4e07\u7ea2'
|
||||
}
|
||||
})
|
||||
const message = {
|
||||
id: 'application-preview-editor-inline-return-date-message',
|
||||
applicationPreview: preview,
|
||||
text: ''
|
||||
}
|
||||
const requestedPayloads = []
|
||||
const editor = useApplicationPreviewEditor({
|
||||
persistSessionState: () => {},
|
||||
toast: () => {},
|
||||
currentUser: ref({ grade: 'P5' }),
|
||||
calculateTravelReimbursement: async (payload) => {
|
||||
requestedPayloads.push(payload)
|
||||
return {
|
||||
days: payload.days,
|
||||
location: payload.location,
|
||||
matched_city: payload.location,
|
||||
grade: payload.grade,
|
||||
hotel_rate: 450,
|
||||
hotel_amount: 2250,
|
||||
total_allowance_rate: 100,
|
||||
allowance_amount: 500,
|
||||
total_amount: 2750,
|
||||
rule_name: '\u516c\u53f8\u5dee\u65c5\u8d39\u62a5\u9500\u89c4\u5219',
|
||||
rule_version: 'v1.0.0'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
editor.openApplicationPreviewEditor(message, 'time_return', '2026-02-23')
|
||||
editor.applicationPreviewEditor.value.draftValue = '2026-02-24'
|
||||
const committed = await editor.commitApplicationPreviewEditor(message)
|
||||
|
||||
assert.equal(committed, true)
|
||||
assert.deepEqual(requestedPayloads.at(-1), {
|
||||
days: 5,
|
||||
location: '\u4e0a\u6d77',
|
||||
grade: 'P5',
|
||||
transport_mode: '\u706b\u8f66',
|
||||
origin_location: null,
|
||||
travel_date: '2026-02-20'
|
||||
})
|
||||
assert.equal(message.applicationPreview.fields.time, '2026-02-20 \u81f3 2026-02-24')
|
||||
assert.equal(message.applicationPreview.fields.time_return, undefined)
|
||||
assert.equal(message.applicationPreview.fields.days, '5\u5929')
|
||||
})
|
||||
|
||||
test('application preview editor estimates after shorthand return date input', async () => {
|
||||
const preview = normalizeApplicationPreview({
|
||||
fields: {
|
||||
applicationType: '\u5dee\u65c5\u8d39\u7528\u7533\u8bf7',
|
||||
time: '2026-06-23',
|
||||
location: '\u5317\u4eac',
|
||||
reason: '\u652f\u6491\u5ba2\u6237\u73b0\u573a\u5b9e\u65bd',
|
||||
days: '',
|
||||
transportMode: '',
|
||||
amount: '',
|
||||
grade: 'P5',
|
||||
applicant: '\u674e\u6587\u9759',
|
||||
department: '\u6280\u672f\u90e8',
|
||||
position: '\u8d22\u52a1\u667a\u80fd\u5316\u4ea7\u54c1\u7ecf\u7406',
|
||||
managerName: '\u5411\u4e07\u7ea2'
|
||||
}
|
||||
})
|
||||
const message = {
|
||||
id: 'application-preview-editor-shorthand-return-date-message',
|
||||
applicationPreview: preview,
|
||||
text: ''
|
||||
}
|
||||
const requestedPayloads = []
|
||||
const editor = useApplicationPreviewEditor({
|
||||
persistSessionState: () => {},
|
||||
toast: () => {},
|
||||
currentUser: ref({ grade: 'P5', location: '\u6b66\u6c49' }),
|
||||
calculateTravelReimbursement: async (payload) => {
|
||||
requestedPayloads.push(payload)
|
||||
return {
|
||||
days: payload.days,
|
||||
location: payload.location,
|
||||
matched_city: payload.location,
|
||||
grade: payload.grade,
|
||||
hotel_rate: 450,
|
||||
hotel_amount: 1350,
|
||||
total_allowance_rate: 100,
|
||||
allowance_amount: 300,
|
||||
total_amount: 1650,
|
||||
rule_name: '\u516c\u53f8\u5dee\u65c5\u8d39\u62a5\u9500\u89c4\u5219',
|
||||
rule_version: 'v1.0.0'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
editor.openApplicationPreviewEditor(message, 'time_return', '\u5f85\u8865\u5145')
|
||||
editor.applicationPreviewEditor.value.draftValue = '6\u670825\u65e5'
|
||||
const committed = await editor.commitApplicationPreviewEditor(message)
|
||||
|
||||
assert.equal(committed, true)
|
||||
assert.deepEqual(requestedPayloads.at(-1), {
|
||||
days: 3,
|
||||
location: '\u5317\u4eac',
|
||||
grade: 'P5',
|
||||
transport_mode: null,
|
||||
origin_location: '\u6b66\u6c49',
|
||||
travel_date: '2026-06-23'
|
||||
})
|
||||
assert.equal(message.applicationPreview.fields.time, '2026-06-23 \u81f3 2026-06-25')
|
||||
assert.equal(message.applicationPreview.fields.days, '3\u5929')
|
||||
assert.equal(message.applicationPreview.fields.lodgingDailyCap, '450\u5143/\u5929')
|
||||
assert.equal(message.applicationPreview.fields.subsidyDailyCap, '100\u5143/\u5929')
|
||||
assert.equal(message.applicationPreview.fields.amount, '1,650\u5143\uff08\u4e0d\u542b\u4ea4\u901a\uff09')
|
||||
assert.match(message.applicationPreview.fields.policyEstimate, /\u4ea4\u901a\u5f85\u8865\u5145/)
|
||||
})
|
||||
|
||||
@@ -396,7 +396,7 @@ test('submit composer scopes the side panel to intent overview, document upload,
|
||||
|
||||
test('expense query answers keep one clear result structure with document center jump link', () => {
|
||||
assert.doesNotMatch(createViewTemplateSurface, /message\.meta\?\.length/)
|
||||
assert.match(createViewTemplateSurface, /!message\.reviewPayload && !message\.queryPayload && message\.suggestedActions\?\.length/)
|
||||
assert.match(createViewTemplateSurface, /!message\.reviewPayload && \(!message\.queryPayload \|\| message\.queryPayload\.selectionMode === 'reimbursement_application_association'\) && message\.suggestedActions\?\.length/)
|
||||
assert.match(createViewTemplateSurface, /!message\.reviewPayload && !message\.queryPayload && message\.citations\?\.length/)
|
||||
assert.match(createViewTemplateSurface, /message\.queryPayload\.title \|\| \(message\.queryPayload\.selectionMode === 'draft_association' \? '选择关联草稿' : '最近 5 条筛选结果'\)/)
|
||||
assert.match(createViewTemplateSurface, /v-html="ui\.renderMarkdown\(ui\.buildExpenseQueryHint\(message\.queryPayload\)\)"/)
|
||||
|
||||
Reference in New Issue
Block a user