feat: 本体字段治理与风险规则模板执行器重构

- 新增本体字段注册表与字段治理审计脚本
- 重构风险规则模板执行器、DSL 验证与清单分类器
- 完善票据夹服务与差旅请求详情页交互
- 优化趋势图表与总览页数据展示
- 增强报销平台风险分级与模拟公司筛选
- 补充本体字段、风险规则生成与票据夹服务测试覆盖
This commit is contained in:
caoxiaozhu
2026-06-03 15:46:56 +08:00
parent e12b140508
commit 34457f9c3e
81 changed files with 4858 additions and 1073 deletions

View File

@@ -13,30 +13,38 @@ function testReceiptFolderViewSurface() {
assert.match(view, /activeStatus = ref\('all'\)/)
assert.match(view, /value: 'all'/)
assert.match(view, /value: 'unlinked'/)
assert.match(view, /value: 'linked'/)
assert.match(view, /openAssociateDialog/)
assert.match(view, /receipt-detail-toolbar/)
assert.match(view, /receipt-dashboard/)
assert.match(view, /receipt-dashboard-preview/)
assert.match(view, /receipt-dashboard-side/)
assert.match(view, /receipt-dashboard-bottom/)
assert.match(view, /receipt-ocr-panel/)
assert.match(view, /receipt-status-panel/)
assert.match(view, /keyReceiptFields/)
assert.match(view, /editableOtherFields/)
assert.match(view, /ocrPreviewFields/)
assert.match(view, /class="receipt-key-grid"/)
assert.match(view, /class="receipt-other-collapse"/)
assert.match(view, /class="receipt-other-scroll"/)
assert.match(view, /<EnterpriseDetailPage/)
assert.match(view, /variant="receipt-folder-detail"/)
assert.match(view, /<template #main>/)
assert.match(view, /<template #side>/)
assert.match(view, /<template #bottom>/)
assert.match(view, /receipt-preview-panel/)
assert.match(view, /receipt-ticket-info-panel/)
assert.match(view, /receipt-association-panel/)
assert.match(view, /receipt-edit-log-section/)
assert.match(view, /receipt-all-field-grid/)
assert.match(view, /receiptInfoEditing/)
assert.match(view, /startReceiptInfoEdit/)
assert.match(view, /cancelReceiptInfoEdit/)
assert.match(view, /receiptEditLogs/)
assert.match(view, /previewFrameUrl/)
assert.match(view, /previewTransform/)
assert.match(view, /String\(value \?\? ''\)\.trim\(\)/)
assert.match(view, /openAssociateDialogForCurrentReceipt/)
assert.match(view, /createReceiptDetailDashboardModel/)
assert.match(view, /ElCollapse/)
assert.doesNotMatch(view, /addField/)
assert.match(view, /const isTrainTicket = computed/)
assert.doesNotMatch(view, /打开源文件/)
assert.match(view, /createReceiptDetailFieldModel/)
assert.doesNotMatch(view, /receipt-detail-toolbar/)
assert.doesNotMatch(view, /receipt-side-stack/)
assert.doesNotMatch(view, /receipt-bottom-grid/)
assert.doesNotMatch(view, /receipt-status-panel/)
assert.doesNotMatch(view, /receipt-key-grid/)
assert.doesNotMatch(view, /receipt-other-collapse/)
assert.doesNotMatch(view, /ElCollapse/)
assert.doesNotMatch(view, /openSourceFile/)
assert.match(view, /back-label=/)
assert.doesNotMatch(view, /back-btn/)
assert.match(view, /deleteCurrentReceipt/)
assert.match(view, /ElCheckboxGroup/)
assert.match(view, /fetchReceiptFolderItems\('all'\)/)
@@ -93,60 +101,66 @@ function testReceiptFolderDetailLayoutAdjustments() {
const receiptView = readProjectFile('web/src/views/ReceiptFolderView.vue')
const receiptStyles = readProjectFile('web/src/assets/styles/views/receipt-folder-view.css')
const fieldModel = readProjectFile('web/src/views/scripts/receiptFolderDetailFields.js')
const dashboardModel = readProjectFile('web/src/views/scripts/receiptFolderDetailDashboard.js')
const detailPage = readProjectFile('web/src/components/shared/EnterpriseDetailPage.vue')
assert.match(receiptView, /showStatusColumn/)
assert.match(receiptView, /<col v-if="showStatusColumn" class="col-status">/)
assert.match(receiptView, /<th v-if="showStatusColumn">/)
assert.match(receiptView, /document_date/)
assert.match(receiptView, /<td>\s*<strong class="doc-id">/)
assert.match(receiptView, /<td v-if="showStatusColumn">\s*<span class="status-tag"/)
assert.match(receiptView, /const activeStatus = ref\('all'\)/)
assert.match(receiptView, /import EnterpriseDetailCard/)
assert.match(receiptView, /import EnterpriseDetailPage/)
assert.match(receiptView, /<EnterpriseDetailPage/)
assert.match(receiptView, /variant="receipt-folder-detail"/)
assert.match(receiptView, /<EnterpriseDetailCard class="receipt-basic-panel"/)
assert.match(receiptView, /receipt-dashboard-preview/)
assert.match(receiptView, /receipt-dashboard-bottom/)
assert.match(receiptView, /createReceiptDetailFieldModel/)
assert.match(receiptView, /createReceiptDetailDashboardModel/)
assert.match(receiptView, /<td[^>]*>\s*<strong class="doc-id">/)
assert.match(receiptView, /buildDetailPayload\(\)/)
assert.match(receiptView, /receiptDetailSubtitle/)
assert.match(receiptView, /receiptDetailTopBarPayload/)
assert.match(receiptView, /eyebrow:/)
assert.match(receiptView, /detail-topbar-change/)
assert.doesNotMatch(receiptView, /<article v-else class="receipt-folder-detail/)
assert.doesNotMatch(receiptView, /class="back-btn"/)
assert.doesNotMatch(receiptView, /receipt-detail-head/)
assert.doesNotMatch(receiptView, /detail-actions receipt-detail-foot/)
assert.doesNotMatch(receiptView, /receipt-basic-panel/)
assert.doesNotMatch(receiptView, /receipt-ocr-panel/)
assert.match(receiptStyles, /\.receipt-folder-list th:first-child/)
assert.match(receiptStyles, /\.receipt-folder-detail :deep\(\.detail-scroll\)[\s\S]*display: flex/)
assert.match(receiptStyles, /\.receipt-folder-detail :deep\(\.detail-grid\)/)
assert.match(receiptStyles, /\.receipt-folder-detail :deep\(\.detail-bottom\)/)
assert.match(receiptStyles, /\.receipt-folder-detail :deep\(\.detail-actions\)/)
assert.match(receiptStyles, /\.receipt-folder-detail :deep\(\.enterprise-detail-card \.card-head\)/)
assert.match(receiptStyles, /\.receipt-detail-toolbar/)
assert.match(receiptStyles, /\.receipt-dashboard/)
assert.match(receiptStyles, /\.receipt-dashboard-bottom/)
assert.match(receiptStyles, /\.receipt-preview-tools/)
assert.match(receiptStyles, /\.receipt-log-list/)
assert.match(receiptStyles, /\.receipt-key-grid/)
assert.match(receiptStyles, /\.receipt-other-collapse/)
assert.match(receiptStyles, /\.receipt-other-scroll/)
assert.doesNotMatch(receiptStyles, /\.receipt-detail-head\b/)
assert.doesNotMatch(receiptStyles, /\.receipt-detail-layout\b/)
assert.doesNotMatch(receiptStyles, /\.detail-loading\b/)
assert.doesNotMatch(receiptStyles, /\.back-btn\b/)
assert.doesNotMatch(receiptStyles, /\.danger-btn\b/)
assert.match(receiptStyles, /\.receipt-preview-panel/)
assert.match(receiptStyles, /\.receipt-ticket-info-panel/)
assert.match(receiptStyles, /\.receipt-association-panel/)
assert.match(receiptStyles, /\.receipt-preview-box[\s\S]*height: clamp\(380px, 56vh, 640px\)/)
assert.match(receiptStyles, /\.receipt-all-field-grid/)
assert.match(receiptStyles, /\.receipt-edit-log-list/)
assert.doesNotMatch(receiptStyles, /\.receipt-detail-toolbar/)
assert.doesNotMatch(receiptStyles, /\.receipt-side-stack/)
assert.doesNotMatch(receiptStyles, /\.receipt-bottom-grid/)
assert.doesNotMatch(receiptStyles, /\.receipt-log-list/)
assert.match(detailPage, /\$slots\.bottom/)
assert.match(detailPage, /class="detail-bottom"/)
assert.match(fieldModel, /TRAIN_KEY_FIELD_DEFINITIONS/)
assert.match(fieldModel, /id: 'invoice_number'/)
assert.match(fieldModel, /id: 'invoice_date'/)
assert.match(fieldModel, /id: 'fare'/)
assert.match(fieldModel, /id: 'passenger_name'/)
assert.match(fieldModel, /syncEditableFieldsToTopLevel/)
const dashboardModel = readProjectFile('web/src/views/scripts/receiptFolderDetailDashboard.js')
assert.match(dashboardModel, /createReceiptDetailDashboardModel/)
assert.match(dashboardModel, /basicInfoItems/)
assert.match(dashboardModel, /operationLogs/)
assert.match(dashboardModel, /archiveInfoItems/)
assert.match(dashboardModel, /linkedClaimItems/)
assert.doesNotMatch(dashboardModel, /operationLogs/)
assert.doesNotMatch(dashboardModel, /archiveInfoItems/)
}
function testAssistantUnlinkedReceiptPrompt() {
const submitComposer = readProjectFile('web/src/views/scripts/useTravelReimbursementSubmitComposer.js')
const assistantView = readProjectFile('web/src/views/scripts/TravelReimbursementCreateView.js')
assert.match(submitComposer, /fetchReceiptFolderItems/)
assert.match(submitComposer, /promptUnlinkedReceiptFolderIfNeeded/)
assert.match(submitComposer, /fetchReceiptFolderItems\('unlinked'\)/)
assert.match(submitComposer, /skipReceiptFolderUnlinkedPrompt/)
assert.match(submitComposer, /open_receipt_folder/)
assert.match(submitComposer, /continue_upload_with_unlinked_receipts/)
assert.match(assistantView, /actionType === 'open_receipt_folder'/)
assert.match(assistantView, /router\.push\(\{ name: 'app-receiptFolder' \}\)/)
assert.match(assistantView, /actionType === 'continue_upload_with_unlinked_receipts'/)
assert.match(assistantView, /skipReceiptFolderUnlinkedPrompt: true/)
}
function run() {
@@ -155,6 +169,7 @@ function run() {
testAppShellWiresReceiptFolder()
testSharedDocumentListStyleReuse()
testReceiptFolderDetailLayoutAdjustments()
testAssistantUnlinkedReceiptPrompt()
console.log('receipt folder view tests passed')
}