feat: 报销审批流重构与管家计划全链路贯通

- 重构报销状态注册表、审批流路由与平台风险标记
- 完善管家意图规划器与模型计划构建器全链路
- 新增 OCR Worker 脚本、数据库会话管理与通知状态
- 优化文档中心、日志视图、预算中心与员工管理交互
- 增强工作台摘要、图标资源与全局主题样式
- 补充审批路由、状态注册、OCR 服务与管家规划器测试覆盖
This commit is contained in:
caoxiaozhu
2026-06-06 17:19:07 +08:00
parent f60cebadb8
commit e124e4bbcb
162 changed files with 9161 additions and 1941 deletions

View File

@@ -248,8 +248,25 @@ import TableEmptyState from '../components/shared/TableEmptyState.vue'
import TableLoadingState from '../components/shared/TableLoadingState.vue'
import { useMinimumVisibleState } from '../composables/useMinimumVisibleState.js'
import { mapExpenseClaimToRequest } from '../composables/useRequests.js'
import { fetchApprovalExpenseClaims, fetchArchivedExpenseClaims } from '../services/reimbursements.js'
import { countNewDocuments, isNewDocument, markDocumentViewed, markDocumentsViewed, readDocumentScope, readViewedDocumentKeys, writeDocumentScope } from '../utils/documentCenterNewState.js'
import {
REIMBURSEMENT_LIST_PREVIEW_PARAMS,
extractExpenseClaimItems,
fetchApprovalExpenseClaims,
fetchArchivedExpenseClaims
} from '../services/reimbursements.js'
import { fetchNotificationStates, patchNotificationStates } from '../services/notificationStates.js'
import {
buildDocumentViewedStatePatch,
buildDocumentsViewedStatePatches,
countNewDocuments,
isNewDocument,
markDocumentViewed,
markDocumentsViewed,
mergeNotificationStatesIntoViewedDocumentKeys,
readDocumentScope,
readViewedDocumentKeys,
writeDocumentScope
} from '../utils/documentCenterNewState.js'
import { sortDocumentRowsByLatestTime } from '../utils/documentCenterSort.js'
import { extractDateText, formatDocumentListTime, resolveDocumentSortTime, resolveDocumentStayTimeDisplay } from '../utils/documentCenterTime.js'
import { excludeArchivedDocumentRows, filterApplicationScopeNewRows, isArchivedDocumentRow, prepareApplicationScopeRows } from '../utils/documentCenterRows.js'
@@ -860,9 +877,36 @@ function changePageSize(size) {
currentPage.value = 1
}
function applyRemoteViewedDocumentStates(states) {
viewedDocumentKeys.value = mergeNotificationStatesIntoViewedDocumentKeys(states, viewedDocumentKeys.value)
}
async function loadRemoteViewedDocumentKeys() {
try {
applyRemoteViewedDocumentStates(await fetchNotificationStates())
} catch {
// 接口不可用时保留本机已读缓存,避免影响单据中心主流程。
}
}
async function syncDocumentViewedPatches(patches) {
const normalizedPatches = (Array.isArray(patches) ? patches : [patches]).filter(Boolean)
if (!normalizedPatches.length) {
return
}
try {
applyRemoteViewedDocumentStates(await patchNotificationStates(normalizedPatches))
} catch {
// 本机状态已先落地;远端失败时等待下次操作或刷新重试。
}
}
function openDocument(row) {
writeDocumentScope(activeScopeTab.value, scopeTabs)
const viewedPatch = buildDocumentViewedStatePatch(row)
viewedDocumentKeys.value = markDocumentViewed(row, viewedDocumentKeys.value)
void syncDocumentViewedPatches([viewedPatch])
emit('open-document', row.rawRequest || row)
}
@@ -871,7 +915,9 @@ function markAllDocumentsRead() {
return
}
const viewedPatches = buildDocumentsViewedStatePatches(allReadableDocumentRows.value, viewedDocumentKeys.value)
viewedDocumentKeys.value = markDocumentsViewed(allReadableDocumentRows.value, viewedDocumentKeys.value)
void syncDocumentViewedPatches(viewedPatches)
}
async function loadSupportingRows() {
@@ -879,30 +925,26 @@ async function loadSupportingRows() {
supportingError.value = ''
const [approvalResult, archiveResult] = await Promise.allSettled([
fetchApprovalExpenseClaims(),
fetchArchivedExpenseClaims()
fetchApprovalExpenseClaims(REIMBURSEMENT_LIST_PREVIEW_PARAMS),
fetchArchivedExpenseClaims(REIMBURSEMENT_LIST_PREVIEW_PARAMS)
])
if (approvalResult.status === 'fulfilled') {
approvalRows.value = excludeArchivedDocumentRows(
Array.isArray(approvalResult.value)
? approvalResult.value
extractExpenseClaimItems(approvalResult.value)
.map((item) => mapExpenseClaimToRequest(item))
.map((item) => buildDocumentRow(item, { source: 'approval' }))
.filter(Boolean)
: []
)
} else {
approvalRows.value = []
}
if (archiveResult.status === 'fulfilled') {
archiveRows.value = Array.isArray(archiveResult.value)
? archiveResult.value
.map((item) => mapExpenseClaimToRequest(item))
.map((item) => buildDocumentRow(item, { source: 'archive', archived: true }))
.filter(Boolean)
: []
archiveRows.value = extractExpenseClaimItems(archiveResult.value)
.map((item) => mapExpenseClaimToRequest(item))
.map((item) => buildDocumentRow(item, { source: 'archive', archived: true }))
.filter(Boolean)
} else {
archiveRows.value = []
supportingError.value = archiveResult.reason instanceof Error
@@ -915,6 +957,7 @@ async function loadSupportingRows() {
function reloadAll() {
emit('reload')
void loadRemoteViewedDocumentKeys()
void loadSupportingRows()
}
@@ -963,6 +1006,7 @@ watch(documentSummary, (summary) => {
}, { immediate: true })
onMounted(() => {
void loadRemoteViewedDocumentKeys()
void loadSupportingRows()
})
@@ -970,6 +1014,7 @@ watch(
() => props.refreshToken,
(token, previousToken) => {
if (token && token !== previousToken) {
void loadRemoteViewedDocumentKeys()
void loadSupportingRows()
}
}