feat: 新增归档中心页面并完善知识库与报销查询能力
新增前端归档中心视图及相关工具函数,扩充知识库文档分类和 提取器支持多种格式,增强编排器报销查询的多维度检索,优 化本体规则和用户代理审核消息,前端完善报销创建和审批详 情交互细节,补充单元测试覆盖。
This commit is contained in:
130
web/tests/assistant-session-draft-delete.test.mjs
Normal file
130
web/tests/assistant-session-draft-delete.test.mjs
Normal file
@@ -0,0 +1,130 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import test from 'node:test'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import {
|
||||
clearAssistantSessionSnapshotForDraftClaim,
|
||||
readAssistantSessionSnapshot,
|
||||
writeAssistantSessionSnapshot
|
||||
} from '../src/utils/assistantSessionSnapshot.js'
|
||||
|
||||
function installWindowStub() {
|
||||
const store = new Map()
|
||||
const events = []
|
||||
|
||||
globalThis.CustomEvent = class CustomEvent {
|
||||
constructor(type, options = {}) {
|
||||
this.type = type
|
||||
this.detail = options.detail || {}
|
||||
}
|
||||
}
|
||||
globalThis.window = {
|
||||
localStorage: {
|
||||
getItem(key) {
|
||||
return store.has(key) ? store.get(key) : null
|
||||
},
|
||||
setItem(key, value) {
|
||||
store.set(key, String(value))
|
||||
},
|
||||
removeItem(key) {
|
||||
store.delete(key)
|
||||
}
|
||||
},
|
||||
dispatchEvent(event) {
|
||||
events.push(event)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return { events }
|
||||
}
|
||||
|
||||
test('assistant snapshot is cleared only when it belongs to the deleted draft claim', () => {
|
||||
const { events } = installWindowStub()
|
||||
|
||||
writeAssistantSessionSnapshot('emp-1', 'expense', {
|
||||
draftClaimId: 'claim-1',
|
||||
messages: [{ role: 'assistant', text: '已保存草稿 EXP-001' }]
|
||||
})
|
||||
|
||||
assert.equal(clearAssistantSessionSnapshotForDraftClaim('emp-1', 'claim-2', 'expense'), false)
|
||||
assert.equal(readAssistantSessionSnapshot('emp-1', 'expense')?.state?.draftClaimId, 'claim-1')
|
||||
|
||||
assert.equal(clearAssistantSessionSnapshotForDraftClaim('emp-1', 'claim-1', 'expense'), true)
|
||||
assert.equal(readAssistantSessionSnapshot('emp-1', 'expense'), null)
|
||||
assert.equal(events.at(-1)?.detail?.action, 'clear')
|
||||
})
|
||||
|
||||
test('claim delete flow invalidates the matching financial assistant session', () => {
|
||||
const appShellScript = readFileSync(
|
||||
fileURLToPath(new URL('../src/composables/useAppShell.js', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
const appShellRouteView = readFileSync(
|
||||
fileURLToPath(new URL('../src/views/AppShellRouteView.vue', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
const createViewScript = readFileSync(
|
||||
fileURLToPath(new URL('../src/views/scripts/TravelReimbursementCreateView.js', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
|
||||
assert.match(appShellScript, /clearAssistantSessionSnapshotForDraftClaim/)
|
||||
assert.match(appShellScript, /async function handleRequestDeleted\(payload = \{\}\)/)
|
||||
assert.match(appShellScript, /smartEntryInvalidatedDraftClaimId\.value = deletedClaimId/)
|
||||
assert.match(appShellRouteView, /:invalidated-draft-claim-id="smartEntryInvalidatedDraftClaimId"/)
|
||||
assert.match(createViewScript, /invalidatedDraftClaimId/)
|
||||
assert.match(createViewScript, /function clearExpenseSessionForDeletedClaim\(claimId\)/)
|
||||
assert.match(createViewScript, /toast\('该草稿单据已删除,相关财务助手会话已清空。'\)/)
|
||||
})
|
||||
|
||||
test('saving a draft keeps the financial assistant open for continued work', () => {
|
||||
const appShellScript = readFileSync(
|
||||
fileURLToPath(new URL('../src/composables/useAppShell.js', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
const handleDraftSavedBlock = appShellScript.match(
|
||||
/async function handleDraftSaved\(payload = \{\}\) \{[\s\S]*?\r?\n \}\r?\n\r?\n function openRequestDetail/
|
||||
)?.[0]
|
||||
|
||||
assert.ok(handleDraftSavedBlock)
|
||||
assert.match(handleDraftSavedBlock, /if \(status === 'submitted'\) \{[\s\S]*smartEntryOpen\.value = false/)
|
||||
assert.match(handleDraftSavedBlock, /if \(status === 'submitted'\) \{[\s\S]*router\.push\(\{ name: 'app-requests' \}\)/)
|
||||
assert.match(handleDraftSavedBlock, /return[\s\S]*单据已保存为草稿,可继续上传票据或补充信息。/)
|
||||
|
||||
const draftSuccessIndex = handleDraftSavedBlock.indexOf('单据已保存为草稿,可继续上传票据或补充信息。')
|
||||
assert.equal(handleDraftSavedBlock.indexOf('smartEntryOpen.value = false', draftSuccessIndex), -1)
|
||||
assert.equal(handleDraftSavedBlock.indexOf("router.push({ name: 'app-requests' })", draftSuccessIndex), -1)
|
||||
})
|
||||
|
||||
test('detail smart entry is scoped to the current claim instead of the latest conversation', () => {
|
||||
const detailViewScript = readFileSync(
|
||||
fileURLToPath(new URL('../src/views/scripts/TravelRequestDetailView.js', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
const appShellScript = readFileSync(
|
||||
fileURLToPath(new URL('../src/composables/useAppShell.js', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
const sessionStateScript = readFileSync(
|
||||
fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementSessionState.js', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
const submitComposerScript = readFileSync(
|
||||
fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementSubmitComposer.js', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
|
||||
assert.match(detailViewScript, /restoreLatestConversation:\s*false/)
|
||||
assert.match(detailViewScript, /scope:\s*claimId[\s\S]*type:\s*'claim'[\s\S]*claimId/)
|
||||
assert.match(appShellScript, /function isDetailClaimScopedPayload\(payload = \{\}\)/)
|
||||
assert.match(appShellScript, /if \(isDetailClaimScopedPayload\(payload\)\) \{[\s\S]*return null[\s\S]*\}/)
|
||||
assert.match(sessionStateScript, /const shouldPersistLocalSnapshot = props\.entrySource !== 'detail'/)
|
||||
assert.match(sessionStateScript, /if \(!shouldPersistLocalSnapshot\) \{[\s\S]*return[\s\S]*\}/)
|
||||
assert.match(submitComposerScript, /function resolveDetailScopedClaimId\(\)/)
|
||||
assert.match(submitComposerScript, /const detailScopedUpload = Boolean\(detailScopedClaimId && files\.length\)/)
|
||||
assert.match(submitComposerScript, /draft_claim_id: detailScopedClaimId/)
|
||||
assert.match(submitComposerScript, /detail_scope_claim_id: detailScopedClaimId/)
|
||||
assert.match(submitComposerScript, /detailScopedUpload/)
|
||||
})
|
||||
Reference in New Issue
Block a user