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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user