feat(web): 报销单新增关联申请单门控与草稿检测流程

- 新增 travelReimbursementAssociationGateModel,查询可关联申请单/草稿报销单并生成跳过/选择/单独新建动作,区分差旅费与业务招待费类型
- travelReimbursementApplicationLinkModel 补充 buildLinkedApplicationReferenceIndex/buildRequiredApplicationActions 等关联构建逻辑
- useTravelReimbursementSuggestedActions 接入 select_required_application/skip 系列动作,'我要报销'入口改为先走关联门控
- useWorkbenchAiActionRouter 新增 SKIP_REQUIRED_APPLICATION_LINK/SKIP_REIMBURSEMENT_DRAFT_CHECK 动作分发
- useWorkbenchAiExpenseFlow 暴露 startAiReimbursementAssociationGate,stewardPlanModel 待处理流程适配
- 新增 workbench-ai-action-router、workbench-ai-reimbursement-association-gate 测试并更新 guided-flow、steward-plan 测试
This commit is contained in:
caoxiaozhu
2026-06-22 15:55:59 +08:00
parent aa965da69d
commit ba444a514f
11 changed files with 1756 additions and 25 deletions

View File

@@ -46,6 +46,22 @@ function uniqueValues(values) {
return Array.from(new Set((Array.isArray(values) ? values : []).map(normalizeText).filter(Boolean)))
}
function expandIdentityValues(values) {
const expanded = []
;(Array.isArray(values) ? values : []).forEach((value) => {
const normalized = normalizeText(value)
if (!normalized) {
return
}
expanded.push(normalized)
const atIndex = normalized.indexOf('@')
if (atIndex > 0) {
expanded.push(normalized.slice(0, atIndex))
}
})
return uniqueValues(expanded)
}
function normalizeClaimNo(claim) {
return normalizeText(claim?.claim_no || claim?.claimNo).toUpperCase()
}
@@ -205,7 +221,7 @@ function hasAnyApplicationReference(index) {
return Boolean(index?.ids?.size || index?.claimNos?.size)
}
function buildLinkedApplicationReferenceIndex(claims) {
export function buildLinkedApplicationReferenceIndex(claims) {
const index = createReferenceIndex()
;(Array.isArray(claims) ? claims : []).forEach((claim) => {
if (isExpenseApplicationClaim(claim)) {
@@ -297,6 +313,58 @@ export function getRequiredApplicationExpenseLabel(expenseType) {
return EXPENSE_TYPE_LABELS[normalizeLower(expenseType)] || '报销'
}
export function resolveRequiredApplicationReimbursementType(application = {}) {
const expenseType = normalizeLower(
application.application_expense_type
|| application.expense_type
|| application.expenseType
|| application.type_code
|| application.typeCode
)
const source = {
expense_type: expenseType,
reason: application.application_reason || application.reason,
title: application.application_reason || application.reason,
description: application.application_reason || application.reason,
location: application.application_location || application.location
}
if (APPLICATION_TYPE_ALIASES.travel.has(expenseType)) {
return {
expenseType: 'travel',
expenseTypeLabel: EXPENSE_TYPE_LABELS.travel
}
}
if (APPLICATION_TYPE_ALIASES.meal.has(expenseType)) {
return {
expenseType: 'meal',
expenseTypeLabel: EXPENSE_TYPE_LABELS.meal
}
}
if (matchesGenericApplicationByText(source, 'meal')) {
return {
expenseType: 'meal',
expenseTypeLabel: EXPENSE_TYPE_LABELS.meal
}
}
if (matchesGenericApplicationByText(source, 'travel')) {
return {
expenseType: 'travel',
expenseTypeLabel: EXPENSE_TYPE_LABELS.travel
}
}
return {
expenseType: REQUIRED_APPLICATION_EXPENSE_TYPES.has(expenseType) ? expenseType : 'travel',
expenseTypeLabel: getRequiredApplicationExpenseLabel(
REQUIRED_APPLICATION_EXPENSE_TYPES.has(expenseType) ? expenseType : 'travel'
)
}
}
export function isExpenseApplicationClaim(claim) {
const documentType = normalizeDocumentType(claim)
const expenseType = normalizeExpenseType(claim)
@@ -323,7 +391,7 @@ export function matchesRequiredApplicationExpenseType(claim, expenseType) {
}
export function isClaimOwnedByCurrentUser(claim, currentUser = {}) {
const userIds = uniqueValues([
const userIds = expandIdentityValues([
currentUser.id,
currentUser.employeeId,
currentUser.employee_id,
@@ -332,11 +400,13 @@ export function isClaimOwnedByCurrentUser(claim, currentUser = {}) {
currentUser.username,
currentUser.email
])
const claimIds = uniqueValues([
const claimIds = expandIdentityValues([
claim?.employee_id,
claim?.employeeId,
claim?.employee_no,
claim?.employeeNo,
claim?.employee_email,
claim?.employeeEmail,
claim?.username,
claim?.user_id,
claim?.userId