feat(web): AI 文档详情引用解析与查询卡片增强
- 新增 aiDocumentDetailReference,统一解析 #ai-open-document-detail / #ai-open-application-detail 引用,兼容 A/R/D 短格式与 AP-/RE-/AD- 旧格式单号,提供 isBusinessDocumentReference 判定 - aiDocumentQueryModel 文档卡片接入详情引用,按申请单/报销单生成对应 href,HTML 渲染器识别单据记录表格并生成卡片链接 - PersonalWorkbenchAiMode 处理文档详情点击跳转,卡片样式重构为结构化布局并更新背景资源 - expenseApplicationPreview 补充事由字段,同步新增/更新 ai-document-detail-reference、document-query-model、html-renderer、workbench-ai-mode 等测试 - 更新公司通信费报销规则表
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
buildAiDocumentQueryConditionSummary,
|
||||
buildAiDocumentQueryMessage,
|
||||
filterAiDocumentQueryRecords,
|
||||
mergeAiDocumentQueryPayloads,
|
||||
resolveAiDocumentQueryIntent
|
||||
} from '../src/utils/aiDocumentQueryModel.js'
|
||||
import { renderAiConversationHtml } from '../src/utils/aiConversationHtmlRenderer.js'
|
||||
@@ -55,9 +56,9 @@ const claims = [
|
||||
test('AI document query intent detects my document list questions', () => {
|
||||
const intent = resolveAiDocumentQueryIntent('我现在有哪些单据?', { today })
|
||||
|
||||
assert.equal(intent?.source, 'mine')
|
||||
assert.equal(intent?.source, 'accessible')
|
||||
assert.equal(intent?.documentType, 'all')
|
||||
assert.equal(intent?.sourceLabel, '我的单据')
|
||||
assert.equal(intent?.sourceLabel, '我可见的单据')
|
||||
})
|
||||
|
||||
test('AI document query intent detects approval document questions', () => {
|
||||
@@ -67,6 +68,38 @@ test('AI document query intent detects approval document questions', () => {
|
||||
assert.equal(intent?.sourceLabel, '待我审核的单据')
|
||||
})
|
||||
|
||||
test('AI document query keeps explicit own-document scope separate from accessible documents', () => {
|
||||
const intent = resolveAiDocumentQueryIntent('我名下有哪些单据?', { today })
|
||||
|
||||
assert.equal(intent?.source, 'mine')
|
||||
assert.equal(intent?.sourceLabel, '我的单据')
|
||||
})
|
||||
|
||||
test('AI document query merges own and approval records for generic document list questions', () => {
|
||||
const intent = resolveAiDocumentQueryIntent('我现在有哪些单据?', { today })
|
||||
const approvalClaims = [{
|
||||
id: 'approval-1',
|
||||
claim_no: 'CL-APPROVAL-001',
|
||||
document_type_code: 'reimbursement',
|
||||
expense_type: 'meeting',
|
||||
status: 'pending',
|
||||
approval_stage: '直属领导审批',
|
||||
reason: '待我审核的会议费',
|
||||
employee_name: '李文静',
|
||||
department_name: '市场部',
|
||||
occurred_at: '2026-02-22T09:00:00Z',
|
||||
amount: 880
|
||||
}]
|
||||
const merged = mergeAiDocumentQueryPayloads(claims, { items: approvalClaims, querySource: 'approval' }, [claims[0]])
|
||||
const records = filterAiDocumentQueryRecords(merged, intent)
|
||||
|
||||
assert.equal(intent?.source, 'accessible')
|
||||
assert.deepEqual(
|
||||
records.map((record) => record.documentNo),
|
||||
['CL-20260305001', 'CL-APPROVAL-001', 'CL-20260221001', 'AP-20260220001']
|
||||
)
|
||||
})
|
||||
|
||||
test('AI document query filters by month and document type', () => {
|
||||
const intent = resolveAiDocumentQueryIntent('我2月有哪些申请单?', { today })
|
||||
const records = filterAiDocumentQueryRecords(claims, intent)
|
||||
@@ -136,26 +169,64 @@ test('AI document query message renders html document cards with detail actions'
|
||||
assert.match(message, /<article class="ai-document-card ai-document-card--application is-success" aria-label="单据详情">/)
|
||||
assert.match(message, /<header class="ai-document-card__head">/)
|
||||
assert.match(message, /<span class="ai-document-card__status">已审批<\/span>/)
|
||||
assert.match(message, /<strong class="ai-document-card__reason">辅助国网仿生产服务器部署<\/strong>/)
|
||||
assert.match(message, /<strong class="ai-document-card__reason">差旅费用申请<\/strong>/)
|
||||
assert.match(message, /<div class="ai-document-card__summary">/)
|
||||
assert.match(message, /<div class="ai-document-card__details">/)
|
||||
assert.match(message, /<span class="ai-document-card__label">日期<\/span>/)
|
||||
assert.match(message, /2026-02-20/)
|
||||
assert.match(message, /<span class="ai-document-card__label">预计金额<\/span>/)
|
||||
assert.match(message, /<strong class="ai-document-card__value ai-document-card__amount">¥3,000\.00<\/strong>/)
|
||||
assert.doesNotMatch(message, /<span class="ai-document-card__label">当前节点<\/span>/)
|
||||
assert.match(message, /<span class="ai-document-card__label">单据类型<\/span>/)
|
||||
assert.match(message, /<strong class="ai-document-card__value">申请单 · 差旅费用申请<\/strong>/)
|
||||
assert.match(message, /<span class="ai-document-card__label">地点<\/span>/)
|
||||
assert.match(message, /上海/)
|
||||
assert.match(message, /<span class="ai-document-card__label">事由<\/span>/)
|
||||
assert.match(message, /辅助国网仿生产服务器部署/)
|
||||
assert.match(message, /<span class="ai-document-card__label">申请人<\/span>/)
|
||||
assert.match(message, /<strong class="ai-document-card__value">曹小筑 · 交付部<\/strong>/)
|
||||
assert.match(message, /<strong class="ai-document-card__value ai-document-card__number">AP-20260220001<\/strong>/)
|
||||
assert.match(message, /<strong class="ai-document-card__amount">¥3,000\.00<\/strong>/)
|
||||
assert.match(message, /<div class="ai-document-card__field ai-document-card__field--action">/)
|
||||
assert.doesNotMatch(message, /ai-document-card__meta/)
|
||||
assert.doesNotMatch(message, /ai-document-card__meta-item/)
|
||||
assert.match(message, /href="#ai-open-document-detail:AP-20260220001"/)
|
||||
assert.match(message, /href="#ai-open-document-detail:claim_id%3Dapp-1%26claim_no%3DAP-20260220001"/)
|
||||
// 报销单 claim-1 状态为 submitted → is-pending 语义类
|
||||
assert.match(message, /<article class="ai-document-card ai-document-card--reimbursement is-pending" aria-label="单据详情">/)
|
||||
assert.match(message, /href="#ai-open-document-detail:CL-20260221001"/)
|
||||
assert.match(message, /<span class="ai-document-card__status">审批中<\/span>/)
|
||||
assert.match(message, /href="#ai-open-document-detail:claim_id%3Dclaim-1%26claim_no%3DCL-20260221001"/)
|
||||
assert.doesNotMatch(message, />submitted</)
|
||||
assert.doesNotMatch(message, /\| 单据编号 \|/)
|
||||
assert.doesNotMatch(message, /^> /m)
|
||||
assert.doesNotMatch(message, /\*\*查询范围\*\*/)
|
||||
})
|
||||
|
||||
test('AI document query highlights approval-task cards and shows pending review status', () => {
|
||||
const intent = resolveAiDocumentQueryIntent('我有哪些审核单', { today })
|
||||
const message = buildAiDocumentQueryMessage(intent, [{
|
||||
id: 'approval-1',
|
||||
claim_no: 'AP-APPROVAL-001',
|
||||
document_type_code: 'application',
|
||||
expense_type: 'travel_application',
|
||||
status: 'submitted',
|
||||
approval_stage: '直属领导审批',
|
||||
reason: '参加相关残联会议',
|
||||
employee_name: '曹笑竹',
|
||||
department_name: '技术部',
|
||||
location: '上海',
|
||||
occurred_at: '2026-02-20T09:00:00Z',
|
||||
amount: 2120
|
||||
}])
|
||||
|
||||
assert.match(message, /ai-document-card--application ai-document-card--approval-task is-pending/)
|
||||
assert.match(message, /<span class="ai-document-card__status">待审批<\/span>/)
|
||||
assert.match(message, /<span class="ai-document-card__label">预计金额<\/span>/)
|
||||
assert.match(message, /<strong class="ai-document-card__value ai-document-card__amount">¥2,120\.00<\/strong>/)
|
||||
assert.doesNotMatch(message, /<span class="ai-document-card__label">当前节点<\/span>/)
|
||||
assert.match(message, /<span class="ai-document-card__label">单据类型<\/span>/)
|
||||
assert.match(message, /<strong class="ai-document-card__value">申请单 · 差旅费用申请<\/strong>/)
|
||||
assert.match(message, /href="#ai-open-document-detail:claim_id%3Dapproval-1%26claim_no%3DAP-APPROVAL-001"/)
|
||||
})
|
||||
|
||||
test('AI document query html cards render as trusted card markup', () => {
|
||||
const intent = resolveAiDocumentQueryIntent('我2月有哪些单据?', { today })
|
||||
const rendered = renderAiConversationHtml(buildAiDocumentQueryMessage(intent, claims))
|
||||
@@ -169,7 +240,7 @@ test('AI document query html cards render as trusted card markup', () => {
|
||||
assert.match(rendered, /class="ai-document-card__field"/)
|
||||
assert.match(rendered, /class="ai-document-card__label"/)
|
||||
assert.match(rendered, /class="ai-html-action-link ai-html-action-link-document ai-document-card__action"/)
|
||||
assert.match(rendered, /href="#ai-open-document-detail:CL-20260221001"/)
|
||||
assert.match(rendered, /href="#ai-open-document-detail:claim_id%3Dclaim-1%26claim_no%3DCL-20260221001"/)
|
||||
assert.doesNotMatch(rendered, /ai-document-card__meta/)
|
||||
assert.doesNotMatch(rendered, /<section class="ai-document-card-list/)
|
||||
assert.doesNotMatch(rendered, /<blockquote>/)
|
||||
|
||||
Reference in New Issue
Block a user