feat: 新增员工行为画像算法与费用风险标签体系
后端新增员工行为画像算法模块,支持标签规则引擎和评分计算, 完善员工模型、银行信息、序列化和导入逻辑,优化报销审批流 和工作流常量,增强 Hermes 同步和知识同步能力,前端新增费 用画像详情弹窗、雷达图和风险卡片组件,完善登录页和工作台 样式,优化文档中心和归档中心交互,补充单元测试。
This commit is contained in:
@@ -1,239 +1,239 @@
|
||||
export const DEFAULT_APP_VIEW_ORDER = [
|
||||
'overview',
|
||||
'workbench',
|
||||
'documents',
|
||||
'budget',
|
||||
'policies',
|
||||
'audit',
|
||||
'digitalEmployees',
|
||||
'logs',
|
||||
export const DEFAULT_APP_VIEW_ORDER = [
|
||||
'workbench',
|
||||
'documents',
|
||||
'budget',
|
||||
'audit',
|
||||
'overview',
|
||||
'policies',
|
||||
'digitalEmployees',
|
||||
'logs',
|
||||
'employees',
|
||||
'settings'
|
||||
]
|
||||
|
||||
const ALWAYS_VISIBLE_VIEWS = new Set(['workbench', 'documents', 'policies'])
|
||||
const VIEW_ROLE_RULES = {
|
||||
overview: ['finance', 'executive'],
|
||||
budget: ['budget_monitor', 'executive'],
|
||||
audit: ['finance'],
|
||||
digitalEmployees: ['finance'],
|
||||
logs: ['manager'],
|
||||
employees: ['manager'],
|
||||
settings: ['manager']
|
||||
}
|
||||
const CLAIM_MANAGER_ROLE_CODES = new Set(['executive'])
|
||||
const CLAIM_RETURN_ROLE_CODES = new Set(['finance', 'executive', 'manager', 'approver', 'budget_monitor'])
|
||||
const CLAIM_LEADER_APPROVAL_ROLE_CODES = new Set(['manager', 'approver'])
|
||||
const CLAIM_BUDGET_APPROVAL_GRADE = 'P8'
|
||||
const ALWAYS_VISIBLE_VIEWS = new Set(['workbench', 'documents', 'policies'])
|
||||
const VIEW_ROLE_RULES = {
|
||||
overview: ['finance', 'executive'],
|
||||
budget: ['budget_monitor', 'executive'],
|
||||
audit: ['finance'],
|
||||
digitalEmployees: ['finance'],
|
||||
logs: ['manager'],
|
||||
employees: ['manager'],
|
||||
settings: ['manager']
|
||||
}
|
||||
const CLAIM_MANAGER_ROLE_CODES = new Set(['executive'])
|
||||
const CLAIM_RETURN_ROLE_CODES = new Set(['finance', 'executive', 'manager', 'approver', 'budget_monitor'])
|
||||
const CLAIM_LEADER_APPROVAL_ROLE_CODES = new Set(['manager', 'approver'])
|
||||
const CLAIM_BUDGET_APPROVAL_GRADE = 'P8'
|
||||
|
||||
function normalizedRoleCodes(user) {
|
||||
if (!user) {
|
||||
return []
|
||||
}
|
||||
|
||||
return Array.isArray(user.roleCodes)
|
||||
? user.roleCodes
|
||||
.map((item) => normalizeRoleCode(item))
|
||||
.filter(Boolean)
|
||||
: []
|
||||
}
|
||||
|
||||
function normalizeRoleCode(value) {
|
||||
const roleCode = String(value || '').trim().toLowerCase()
|
||||
return roleCode === 'auditor' ? 'budget_monitor' : roleCode
|
||||
}
|
||||
|
||||
function normalizeComparableText(value) {
|
||||
return String(value || '').trim()
|
||||
}
|
||||
|
||||
function collectIdentityNames(...values) {
|
||||
return values
|
||||
.map((value) => normalizeComparableText(value))
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
function identityIntersects(leftValues, rightValues) {
|
||||
const rightSet = new Set(rightValues)
|
||||
return leftValues.some((item) => rightSet.has(item))
|
||||
}
|
||||
|
||||
function normalizedGrade(user) {
|
||||
return String(user?.grade || user?.employeeGrade || '').trim().toUpperCase()
|
||||
}
|
||||
|
||||
function departmentIntersects(request, user) {
|
||||
const requestDepartments = collectIdentityNames(
|
||||
request?.dept,
|
||||
request?.departmentName,
|
||||
request?.department_name
|
||||
)
|
||||
const currentDepartments = collectIdentityNames(
|
||||
user?.department,
|
||||
user?.departmentName,
|
||||
user?.department_name
|
||||
)
|
||||
|
||||
return requestDepartments.length > 0 && identityIntersects(requestDepartments, currentDepartments)
|
||||
}
|
||||
|
||||
function hasPlatformAdminIdentity(user) {
|
||||
if (!user) {
|
||||
return false
|
||||
}
|
||||
|
||||
const username = String(user.username || user.account || '').trim().toLowerCase()
|
||||
const role = String(user.role || '').trim().toLowerCase()
|
||||
const roleCodes = normalizedRoleCodes(user)
|
||||
|
||||
return (
|
||||
Boolean(user.isAdmin)
|
||||
|| username === 'admin'
|
||||
|| role === 'admin'
|
||||
|| role === '管理员'
|
||||
|| role === '系统管理员'
|
||||
|| roleCodes.includes('admin')
|
||||
)
|
||||
}
|
||||
|
||||
export function isManagerUser(user) {
|
||||
return hasPlatformAdminIdentity(user) || normalizedRoleCodes(user).includes('manager')
|
||||
}
|
||||
|
||||
export function isPlatformAdminUser(user) {
|
||||
return hasPlatformAdminIdentity(user)
|
||||
}
|
||||
|
||||
export function isFinanceUser(user) {
|
||||
return normalizedRoleCodes(user).includes('finance')
|
||||
}
|
||||
function normalizedRoleCodes(user) {
|
||||
if (!user) {
|
||||
return []
|
||||
}
|
||||
|
||||
export function isExecutiveUser(user) {
|
||||
return normalizedRoleCodes(user).includes('executive')
|
||||
}
|
||||
|
||||
export function isBudgetMonitorUser(user) {
|
||||
return normalizedRoleCodes(user).includes('budget_monitor')
|
||||
}
|
||||
|
||||
export function canEditBudgetCenter(user) {
|
||||
return isPlatformAdminUser(user) || isExecutiveUser(user)
|
||||
}
|
||||
|
||||
export function canSwitchBudgetDepartments(user) {
|
||||
return isPlatformAdminUser(user) || isExecutiveUser(user)
|
||||
}
|
||||
|
||||
export function canManageExpenseClaims(user) {
|
||||
if (isPlatformAdminUser(user)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return normalizedRoleCodes(user).some((roleCode) => CLAIM_MANAGER_ROLE_CODES.has(roleCode))
|
||||
}
|
||||
|
||||
export function canDeleteArchivedExpenseClaims(user) {
|
||||
return isPlatformAdminUser(user)
|
||||
}
|
||||
|
||||
export function canReturnExpenseClaims(user) {
|
||||
if (isPlatformAdminUser(user)) {
|
||||
return true
|
||||
}
|
||||
return Array.isArray(user.roleCodes)
|
||||
? user.roleCodes
|
||||
.map((item) => normalizeRoleCode(item))
|
||||
.filter(Boolean)
|
||||
: []
|
||||
}
|
||||
|
||||
function normalizeRoleCode(value) {
|
||||
const roleCode = String(value || '').trim().toLowerCase()
|
||||
return roleCode === 'auditor' ? 'budget_monitor' : roleCode
|
||||
}
|
||||
|
||||
function normalizeComparableText(value) {
|
||||
return String(value || '').trim()
|
||||
}
|
||||
|
||||
function collectIdentityNames(...values) {
|
||||
return values
|
||||
.map((value) => normalizeComparableText(value))
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
function identityIntersects(leftValues, rightValues) {
|
||||
const rightSet = new Set(rightValues)
|
||||
return leftValues.some((item) => rightSet.has(item))
|
||||
}
|
||||
|
||||
function normalizedGrade(user) {
|
||||
return String(user?.grade || user?.employeeGrade || '').trim().toUpperCase()
|
||||
}
|
||||
|
||||
function departmentIntersects(request, user) {
|
||||
const requestDepartments = collectIdentityNames(
|
||||
request?.dept,
|
||||
request?.departmentName,
|
||||
request?.department_name
|
||||
)
|
||||
const currentDepartments = collectIdentityNames(
|
||||
user?.department,
|
||||
user?.departmentName,
|
||||
user?.department_name
|
||||
)
|
||||
|
||||
return requestDepartments.length > 0 && identityIntersects(requestDepartments, currentDepartments)
|
||||
}
|
||||
|
||||
function hasPlatformAdminIdentity(user) {
|
||||
if (!user) {
|
||||
return false
|
||||
}
|
||||
|
||||
const username = String(user.username || user.account || '').trim().toLowerCase()
|
||||
const role = String(user.role || '').trim().toLowerCase()
|
||||
const roleCodes = normalizedRoleCodes(user)
|
||||
|
||||
return (
|
||||
Boolean(user.isAdmin)
|
||||
|| username === 'admin'
|
||||
|| role === 'admin'
|
||||
|| role === '管理员'
|
||||
|| role === '系统管理员'
|
||||
|| roleCodes.includes('admin')
|
||||
)
|
||||
}
|
||||
|
||||
export function isManagerUser(user) {
|
||||
return hasPlatformAdminIdentity(user) || normalizedRoleCodes(user).includes('manager')
|
||||
}
|
||||
|
||||
export function isPlatformAdminUser(user) {
|
||||
return hasPlatformAdminIdentity(user)
|
||||
}
|
||||
|
||||
export function isFinanceUser(user) {
|
||||
return normalizedRoleCodes(user).includes('finance')
|
||||
}
|
||||
|
||||
export function isExecutiveUser(user) {
|
||||
return normalizedRoleCodes(user).includes('executive')
|
||||
}
|
||||
|
||||
export function isBudgetMonitorUser(user) {
|
||||
return normalizedRoleCodes(user).includes('budget_monitor')
|
||||
}
|
||||
|
||||
export function canEditBudgetCenter(user) {
|
||||
return isPlatformAdminUser(user) || isExecutiveUser(user)
|
||||
}
|
||||
|
||||
export function canSwitchBudgetDepartments(user) {
|
||||
return isPlatformAdminUser(user) || isExecutiveUser(user)
|
||||
}
|
||||
|
||||
export function canManageExpenseClaims(user) {
|
||||
if (isPlatformAdminUser(user)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return normalizedRoleCodes(user).some((roleCode) => CLAIM_MANAGER_ROLE_CODES.has(roleCode))
|
||||
}
|
||||
|
||||
export function canDeleteArchivedExpenseClaims(user) {
|
||||
return isPlatformAdminUser(user)
|
||||
}
|
||||
|
||||
export function canReturnExpenseClaims(user) {
|
||||
if (isPlatformAdminUser(user)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return normalizedRoleCodes(user).some((roleCode) => CLAIM_RETURN_ROLE_CODES.has(roleCode))
|
||||
}
|
||||
|
||||
export function canApproveLeaderExpenseClaims(user) {
|
||||
if (isPlatformAdminUser(user)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return normalizedRoleCodes(user).some((roleCode) => CLAIM_LEADER_APPROVAL_ROLE_CODES.has(roleCode))
|
||||
}
|
||||
|
||||
export function canApproveBudgetExpenseApplications(user, request = null) {
|
||||
if (isPlatformAdminUser(user)) {
|
||||
return true
|
||||
}
|
||||
|
||||
const roleCodes = normalizedRoleCodes(user)
|
||||
if (roleCodes.includes('executive')) {
|
||||
return true
|
||||
}
|
||||
if (!roleCodes.includes('budget_monitor')) {
|
||||
return false
|
||||
}
|
||||
if (normalizedGrade(user) !== CLAIM_BUDGET_APPROVAL_GRADE) {
|
||||
return false
|
||||
}
|
||||
|
||||
return request ? departmentIntersects(request, user) : true
|
||||
}
|
||||
|
||||
export function isCurrentRequestApplicant(request, user) {
|
||||
const applicantNames = collectIdentityNames(
|
||||
request?.person,
|
||||
request?.employeeName,
|
||||
request?.employee_name,
|
||||
request?.profileName,
|
||||
request?.applicant
|
||||
)
|
||||
const currentNames = collectIdentityNames(
|
||||
user?.name,
|
||||
user?.username,
|
||||
user?.email,
|
||||
user?.employeeNo,
|
||||
user?.employee_no
|
||||
)
|
||||
|
||||
return applicantNames.length > 0 && identityIntersects(applicantNames, currentNames)
|
||||
}
|
||||
|
||||
export function isCurrentDirectManagerForRequest(request, user) {
|
||||
if (isCurrentRequestApplicant(request, user)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const managerNames = collectIdentityNames(
|
||||
request?.profileManager,
|
||||
request?.managerName,
|
||||
request?.manager_name,
|
||||
request?.directManagerName,
|
||||
request?.direct_manager_name,
|
||||
request?.manager
|
||||
)
|
||||
const currentNames = collectIdentityNames(
|
||||
user?.name,
|
||||
user?.username,
|
||||
user?.email,
|
||||
user?.employeeNo,
|
||||
user?.employee_no
|
||||
)
|
||||
|
||||
return managerNames.length > 0 && identityIntersects(managerNames, currentNames)
|
||||
}
|
||||
|
||||
export function canAccessAppView(user, viewId) {
|
||||
if (!viewId || !user) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!DEFAULT_APP_VIEW_ORDER.includes(viewId)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (viewId === 'budget') {
|
||||
if (isPlatformAdminUser(user)) {
|
||||
return true
|
||||
}
|
||||
const roleCodes = normalizedRoleCodes(user)
|
||||
return VIEW_ROLE_RULES.budget.some((roleCode) => roleCodes.includes(roleCode))
|
||||
}
|
||||
|
||||
if (isManagerUser(user)) {
|
||||
return true
|
||||
}
|
||||
export function canApproveLeaderExpenseClaims(user) {
|
||||
if (isPlatformAdminUser(user)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return normalizedRoleCodes(user).some((roleCode) => CLAIM_LEADER_APPROVAL_ROLE_CODES.has(roleCode))
|
||||
}
|
||||
|
||||
export function canApproveBudgetExpenseApplications(user, request = null) {
|
||||
if (isPlatformAdminUser(user)) {
|
||||
return true
|
||||
}
|
||||
|
||||
const roleCodes = normalizedRoleCodes(user)
|
||||
if (roleCodes.includes('executive')) {
|
||||
return true
|
||||
}
|
||||
if (!roleCodes.includes('budget_monitor')) {
|
||||
return false
|
||||
}
|
||||
if (normalizedGrade(user) !== CLAIM_BUDGET_APPROVAL_GRADE) {
|
||||
return false
|
||||
}
|
||||
|
||||
return request ? departmentIntersects(request, user) : true
|
||||
}
|
||||
|
||||
export function isCurrentRequestApplicant(request, user) {
|
||||
const applicantNames = collectIdentityNames(
|
||||
request?.person,
|
||||
request?.employeeName,
|
||||
request?.employee_name,
|
||||
request?.profileName,
|
||||
request?.applicant
|
||||
)
|
||||
const currentNames = collectIdentityNames(
|
||||
user?.name,
|
||||
user?.username,
|
||||
user?.email,
|
||||
user?.employeeNo,
|
||||
user?.employee_no
|
||||
)
|
||||
|
||||
return applicantNames.length > 0 && identityIntersects(applicantNames, currentNames)
|
||||
}
|
||||
|
||||
export function isCurrentDirectManagerForRequest(request, user) {
|
||||
if (isCurrentRequestApplicant(request, user)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const managerNames = collectIdentityNames(
|
||||
request?.profileManager,
|
||||
request?.managerName,
|
||||
request?.manager_name,
|
||||
request?.directManagerName,
|
||||
request?.direct_manager_name,
|
||||
request?.manager
|
||||
)
|
||||
const currentNames = collectIdentityNames(
|
||||
user?.name,
|
||||
user?.username,
|
||||
user?.email,
|
||||
user?.employeeNo,
|
||||
user?.employee_no
|
||||
)
|
||||
|
||||
return managerNames.length > 0 && identityIntersects(managerNames, currentNames)
|
||||
}
|
||||
|
||||
export function canAccessAppView(user, viewId) {
|
||||
if (!viewId || !user) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!DEFAULT_APP_VIEW_ORDER.includes(viewId)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (viewId === 'budget') {
|
||||
if (isPlatformAdminUser(user)) {
|
||||
return true
|
||||
}
|
||||
const roleCodes = normalizedRoleCodes(user)
|
||||
return VIEW_ROLE_RULES.budget.some((roleCode) => roleCodes.includes(roleCode))
|
||||
}
|
||||
|
||||
if (isManagerUser(user)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (ALWAYS_VISIBLE_VIEWS.has(viewId)) {
|
||||
return true
|
||||
|
||||
@@ -10,7 +10,7 @@ function isArchivedRequestPayload(request) {
|
||||
const normalizedStatus = String(request.status || '').trim().toLowerCase()
|
||||
const stage = String(request.approval_stage || request.approvalStage || '').trim()
|
||||
|
||||
if (stage === '归档入账' || stage === 'completed') {
|
||||
if (stage === '归档入账' || stage === '已付款' || stage === 'completed') {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ function isArchivedRequestPayload(request) {
|
||||
}
|
||||
|
||||
return ARCHIVED_CLAIM_STATUSES.has(normalizedStatus)
|
||||
&& (stage === '' || stage === '归档入账' || stage === 'completed')
|
||||
&& (stage === '' || stage === '归档入账' || stage === '已付款' || stage === 'completed')
|
||||
}
|
||||
|
||||
export function isArchivedDocumentRow(row) {
|
||||
|
||||
@@ -4,7 +4,7 @@ export function isArchivedExpenseClaim(claim) {
|
||||
const stage = String(claim?.approval_stage || claim?.approvalStage || '').trim()
|
||||
const status = String(claim?.status || '').trim().toLowerCase()
|
||||
|
||||
if (stage === '归档入账' || stage === 'completed' || stage.includes('归档')) {
|
||||
if (stage === '归档入账' || stage === '已付款' || stage === 'completed' || stage.includes('归档')) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -16,5 +16,5 @@ export function isArchivedExpenseClaim(claim) {
|
||||
return true
|
||||
}
|
||||
|
||||
return !stage || stage === '归档入账' || stage === 'completed'
|
||||
return !stage || stage === '归档入账' || stage === '已付款' || stage === 'completed'
|
||||
}
|
||||
|
||||
@@ -14,13 +14,22 @@ export const HERMES_SIMPLE_TASKS = [
|
||||
frequency: 'weekly',
|
||||
frequencyLabel: '每周一',
|
||||
weekday: 1
|
||||
},
|
||||
{
|
||||
id: 'employee_behavior_profile_scan',
|
||||
label: '员工画像巡检',
|
||||
hint: '沉淀费用、流程质量与 AI 协作画像快照',
|
||||
frequency: 'weekly',
|
||||
frequencyLabel: '每周一',
|
||||
weekday: 1
|
||||
}
|
||||
]
|
||||
|
||||
function buildDefaultSchedules() {
|
||||
const defaults = {
|
||||
global_risk_scan: { enabled: true, frequency: 'daily', time: '09:00', weekday: 1, monthDay: 1, month: 1 },
|
||||
weekly_expense_report: { enabled: false, frequency: 'weekly', time: '10:30', weekday: 1, monthDay: 1, month: 1 }
|
||||
weekly_expense_report: { enabled: false, frequency: 'weekly', time: '10:30', weekday: 1, monthDay: 1, month: 1 },
|
||||
employee_behavior_profile_scan: { enabled: false, frequency: 'weekly', time: '08:30', weekday: 1, monthDay: 1, month: 1 }
|
||||
}
|
||||
|
||||
for (const task of HERMES_SIMPLE_TASKS) {
|
||||
@@ -49,7 +58,8 @@ export function buildDefaultHermesEmployeeForm() {
|
||||
notifyOnFailure: true,
|
||||
capabilities: {
|
||||
global_risk_scan: true,
|
||||
weekly_expense_report: false
|
||||
weekly_expense_report: false,
|
||||
employee_behavior_profile_scan: false
|
||||
},
|
||||
schedules: buildDefaultSchedules()
|
||||
}
|
||||
|
||||
@@ -115,6 +115,7 @@ const APPROVAL_META = {
|
||||
draft: { label: '草稿', tone: 'draft' },
|
||||
in_progress: { label: '审批中', tone: 'info' },
|
||||
supplement: { label: '待补充', tone: 'warning' },
|
||||
pending_payment: { label: '待付款', tone: 'warning' },
|
||||
completed: { label: '已完成', tone: 'success' },
|
||||
rejected: { label: '已退回', tone: 'danger' }
|
||||
}
|
||||
@@ -126,8 +127,9 @@ const BACKEND_STATUS_META = {
|
||||
reviewing: { key: 'in_progress', label: '审批中', tone: 'info' },
|
||||
in_review: { key: 'in_progress', label: '审批中', tone: 'info' },
|
||||
in_progress: { key: 'in_progress', label: '审批中', tone: 'info' },
|
||||
pending_payment: { key: 'pending_payment', label: '待付款', tone: 'warning' },
|
||||
approved: { key: 'completed', label: '已完成', tone: 'success' },
|
||||
paid: { key: 'completed', label: '已完成', tone: 'success' },
|
||||
paid: { key: 'completed', label: '已付款', tone: 'success' },
|
||||
completed: { key: 'completed', label: '已完成', tone: 'success' },
|
||||
supplement: { key: 'supplement', label: '待补充', tone: 'warning' },
|
||||
returned: { key: 'supplement', label: '待提交', tone: 'warning' },
|
||||
@@ -259,7 +261,7 @@ export function isArchivedRequestView(request) {
|
||||
const displayStage = String(request?.workflowNode || request?.node || '').trim()
|
||||
const stage = rawStage || displayStage
|
||||
|
||||
if (stage === '归档入账' || stage === 'completed' || stage.includes('归档') || stage.includes('入账')) {
|
||||
if (stage === '归档入账' || stage === '已付款' || stage === 'completed' || stage.includes('归档') || stage.includes('入账')) {
|
||||
return true
|
||||
}
|
||||
if (
|
||||
@@ -270,7 +272,7 @@ export function isArchivedRequestView(request) {
|
||||
return true
|
||||
}
|
||||
if (['approved', 'completed', 'paid'].includes(status)) {
|
||||
return rawStage === '' || rawStage === '归档入账' || rawStage === 'completed'
|
||||
return rawStage === '' || rawStage === '归档入账' || rawStage === '已付款' || rawStage === 'completed'
|
||||
}
|
||||
return approvalKey === 'completed'
|
||||
}
|
||||
|
||||
@@ -5,11 +5,13 @@ const NON_RISK_SOURCES = new Set([
|
||||
'approval',
|
||||
'approval_log',
|
||||
'expense_claim_approval',
|
||||
'expense_claim_finance_approval'
|
||||
'expense_claim_finance_approval',
|
||||
'payment'
|
||||
])
|
||||
const NON_RISK_EVENTS = new Set([
|
||||
'expense_claim_approval',
|
||||
'expense_claim_finance_approval'
|
||||
'expense_claim_finance_approval',
|
||||
'expense_claim_payment_completed'
|
||||
])
|
||||
const NON_RISK_TONES = new Set(['info', 'pass', 'success', 'approved', 'ok', 'none'])
|
||||
const RISK_SOURCES = new Set([
|
||||
@@ -39,6 +41,8 @@ function isApprovalOnlyText(value) {
|
||||
/^(同意|通过|审批通过|审核通过|已同意|无意见)$/.test(text)
|
||||
|| /已审批通过/.test(text)
|
||||
|| /已完成财务审核/.test(text)
|
||||
|| /进入待付款/.test(text)
|
||||
|| /已确认付款/.test(text)
|
||||
|| /进入归档入账/.test(text)
|
||||
|| /流转至/.test(text)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user