feat: 新增预算费控模型与报销审批流引擎
后端新增预算费控服务和报销单审批流模块,引入申请人费用画像 算法,优化知识库 RAG 运行时和同步逻辑,完善报销单工作流常 量和明细同步,更新差旅报销规则电子表格,前端新增预算分析 组件和数字员工模型,完善审批对话框和洞察面板交互,优化侧 边栏和顶栏样式,补充单元测试。
This commit is contained in:
@@ -2,6 +2,7 @@ import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import {
|
||||
canApproveBudgetExpenseApplications,
|
||||
canApproveLeaderExpenseClaims,
|
||||
canAccessAppView,
|
||||
canDeleteArchivedExpenseClaims,
|
||||
@@ -22,6 +23,24 @@ test('direct approvers can return claims without receiving delete permissions',
|
||||
assert.equal(canReturnExpenseClaims(approverUser), true)
|
||||
assert.equal(canApproveLeaderExpenseClaims(managerUser), true)
|
||||
assert.equal(canApproveLeaderExpenseClaims(approverUser), true)
|
||||
assert.equal(canApproveBudgetExpenseApplications({ roleCodes: ['budget_monitor'], grade: 'P6' }), false)
|
||||
assert.equal(canApproveBudgetExpenseApplications({ roleCodes: ['budget_monitor'], grade: 'P8' }), true)
|
||||
assert.equal(
|
||||
canApproveBudgetExpenseApplications(
|
||||
{ roleCodes: ['budget_monitor'], grade: 'P8', departmentName: '交付部' },
|
||||
{ departmentName: '交付部' }
|
||||
),
|
||||
true
|
||||
)
|
||||
assert.equal(
|
||||
canApproveBudgetExpenseApplications(
|
||||
{ roleCodes: ['budget_monitor'], grade: 'P8', departmentName: '财务部' },
|
||||
{ departmentName: '交付部' }
|
||||
),
|
||||
false
|
||||
)
|
||||
assert.equal(canApproveBudgetExpenseApplications({ roleCodes: [], grade: 'P8' }), false)
|
||||
assert.equal(canApproveBudgetExpenseApplications({ roleCodes: ['executive'] }), true)
|
||||
assert.equal(canManageExpenseClaims(managerUser), false)
|
||||
assert.equal(canManageExpenseClaims(approverUser), false)
|
||||
})
|
||||
@@ -81,6 +100,37 @@ test('finance approval inbox only processes finance-stage requests', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test('budget approval inbox only processes budget-stage requests for budget monitor or senior finance roles', () => {
|
||||
const budgetUser = { roleCodes: ['budget_monitor'], grade: 'P8', name: '赵预算', departmentName: '交付部' }
|
||||
const otherDepartmentBudgetUser = { roleCodes: ['budget_monitor'], grade: 'P8', name: '王预算', departmentName: '财务部' }
|
||||
const seniorFinanceUser = { roleCodes: ['executive'], grade: 'P7', name: '高级财务' }
|
||||
const p8WithoutBudgetRole = { roleCodes: ['manager'], grade: 'P8', name: '高职级经理' }
|
||||
|
||||
assert.equal(
|
||||
canProcessApprovalRequest({ workflowNode: '预算管理者审批', person: '张三', departmentName: '交付部' }, budgetUser),
|
||||
true
|
||||
)
|
||||
assert.equal(
|
||||
canProcessApprovalRequest({ workflowNode: '预算管理者审批', person: '张三', departmentName: '交付部' }, seniorFinanceUser),
|
||||
true
|
||||
)
|
||||
assert.equal(
|
||||
canProcessApprovalRequest(
|
||||
{ workflowNode: '预算管理者审批', person: '张三', departmentName: '交付部' },
|
||||
otherDepartmentBudgetUser
|
||||
),
|
||||
false
|
||||
)
|
||||
assert.equal(
|
||||
canProcessApprovalRequest({ workflowNode: '预算管理者审批', person: '张三' }, p8WithoutBudgetRole),
|
||||
false
|
||||
)
|
||||
assert.equal(
|
||||
canProcessApprovalRequest({ workflowNode: '财务审批', person: '张三' }, budgetUser),
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
test('users with both finance and manager roles can process both relevant stages', () => {
|
||||
const financeManagerUser = { roleCodes: ['finance', 'manager'], name: '李经理' }
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ test('detail topbar ignores system allowance rows when checking missing tickets'
|
||||
|
||||
assert.equal(hasMissingAttachment(request), false)
|
||||
assert.equal(hasPendingInfo(request), false)
|
||||
assert.deepEqual(alerts, ['直属领导审批'])
|
||||
assert.deepEqual(alerts, ['SLA 催单次数 0'])
|
||||
})
|
||||
|
||||
test('detail topbar still flags real manual rows without required ticket info', () => {
|
||||
@@ -96,7 +96,7 @@ test('detail topbar still flags real manual rows without required ticket info',
|
||||
|
||||
assert.equal(hasMissingAttachment(request), true)
|
||||
assert.equal(hasPendingInfo(request), true)
|
||||
assert.deepEqual(alerts, ['待提交', '缺少票据', '待补信息'])
|
||||
assert.deepEqual(alerts, ['SLA 催单次数 0', '缺少票据', '待补信息'])
|
||||
})
|
||||
|
||||
test('application detail topbar does not ask for receipt attachments', () => {
|
||||
@@ -122,5 +122,29 @@ test('application detail topbar does not ask for receipt attachments', () => {
|
||||
|
||||
assert.equal(hasMissingAttachment(request), false)
|
||||
assert.equal(alerts.includes('缺少票据'), false)
|
||||
assert.deepEqual(alerts, ['直属领导审批'])
|
||||
assert.deepEqual(alerts, ['SLA 催单次数 0'])
|
||||
})
|
||||
|
||||
test('detail topbar shows SLA reminder count from direct fields and reminder events', () => {
|
||||
const directAlerts = buildDetailAlerts({
|
||||
node: '直属领导审批',
|
||||
approvalKey: 'in_progress',
|
||||
slaReminderCount: 2,
|
||||
expenseItems: []
|
||||
})
|
||||
|
||||
const eventAlerts = buildDetailAlerts({
|
||||
node: '直属领导审批',
|
||||
approvalKey: 'in_progress',
|
||||
riskFlags: [
|
||||
{ source: 'sla_reminder', message: '下属已催单' },
|
||||
{ event_type: 'urge', message: '再次催单' }
|
||||
],
|
||||
expenseItems: []
|
||||
})
|
||||
|
||||
assert.equal(directAlerts[0].label, 'SLA 催单次数 2')
|
||||
assert.equal(directAlerts[0].tone, 'warning')
|
||||
assert.equal(directAlerts[0].icon, 'mdi mdi-bell-ring-outline')
|
||||
assert.equal(eventAlerts[0].label, 'SLA 催单次数 2')
|
||||
})
|
||||
|
||||
@@ -85,15 +85,20 @@ test('documents center list shows created time and conditional stay time columns
|
||||
assert.match(documentsCenterView, /import \{[\s\S]*formatDocumentListTime[\s\S]*resolveDocumentStayTimeDisplay[\s\S]*\} from '..\/utils\/documentCenterTime\.js'/)
|
||||
assert.match(documentsCenterView, /<col class="col-created">/)
|
||||
assert.match(documentsCenterView, /<col v-if="showStayTimeColumn" class="col-stay">/)
|
||||
assert.match(documentsCenterView, /<col class="col-initiator">/)
|
||||
assert.match(documentsCenterView, /<th>单号<\/th>[\s\S]*<th>创建时间<\/th>[\s\S]*<th v-if="showStayTimeColumn">停留时间<\/th>/)
|
||||
assert.match(documentsCenterView, /<th>费用场景<\/th>[\s\S]*<th>发起人<\/th>[\s\S]*<th>事项<\/th>/)
|
||||
assert.match(documentsCenterView, /<td>\{\{ row\.createdAtDisplay \}\}<\/td>/)
|
||||
assert.match(documentsCenterView, /<td v-if="showStayTimeColumn">\{\{ row\.stayTimeDisplay \}\}<\/td>/)
|
||||
assert.match(documentsCenterView, /<td>\{\{ row\.initiatorName \}\}<\/td>/)
|
||||
assert.match(
|
||||
documentsCenterView,
|
||||
/const showStayTimeColumn = computed\(\(\) =>[\s\S]*DOCUMENT_SCOPE_APPLICATION[\s\S]*DOCUMENT_SCOPE_REVIEW/
|
||||
)
|
||||
assert.match(documentsCenterView, /createdAtDisplay: formatDocumentListTime\(createdAtSource\)/)
|
||||
assert.match(documentsCenterView, /stayTimeDisplay: resolveDocumentStayTimeDisplay\(normalized\)/)
|
||||
assert.match(documentsCenterView, /initiatorName,/)
|
||||
assert.match(documentsCenterView, /row\.initiatorName/)
|
||||
})
|
||||
|
||||
test('documents center action buttons are scoped to application and reimbursement tabs', () => {
|
||||
@@ -225,9 +230,10 @@ test('documents center status dropdown uses compact filter styling', () => {
|
||||
assert.match(documentsCenterStyles, /\.documents-list\s*\{[\s\S]*grid-template-rows:\s*auto auto minmax\(0,\s*1fr\) auto;/)
|
||||
assert.match(documentsCenterStyles, /\.status-tabs button\s*\{[\s\S]*display:\s*inline-flex;/)
|
||||
assert.match(documentsCenterStyles, /\.scope-tab-badge\s*\{[\s\S]*border-radius:\s*999px;/)
|
||||
assert.match(documentsCenterStyles, /min-width:\s*1320px;/)
|
||||
assert.match(documentsCenterStyles, /min-width:\s*1420px;/)
|
||||
assert.match(documentsCenterStyles, /\.col-created\s*\{\s*width:\s*10%;\s*\}/)
|
||||
assert.match(documentsCenterStyles, /\.col-stay\s*\{\s*width:\s*9%;\s*\}/)
|
||||
assert.match(documentsCenterStyles, /\.col-initiator\s*\{\s*width:\s*8%;\s*\}/)
|
||||
assert.match(documentsCenterStyles, /\.document-status-filter\s*\{[\s\S]*display:\s*inline-flex;/)
|
||||
assert.match(documentsCenterStyles, /\.document-status-filter\s*\{[\s\S]*min-height:\s*38px;/)
|
||||
assert.match(documentsCenterStyles, /\.status-filter-trigger\s*\{[\s\S]*min-width:\s*154px;/)
|
||||
|
||||
@@ -5,10 +5,12 @@ import { mapExpenseClaimToRequest } from '../src/composables/useRequests.js'
|
||||
|
||||
const CREATE_APPLICATION = '\u521b\u5efa\u7533\u8bf7'
|
||||
const DIRECT_MANAGER_APPROVAL = '\u76f4\u5c5e\u9886\u5bfc\u5ba1\u6279'
|
||||
const BUDGET_MANAGER_APPROVAL = '\u9884\u7b97\u7ba1\u7406\u8005\u5ba1\u6279'
|
||||
const APPROVAL_COMPLETED = '\u5ba1\u6279\u5b8c\u6210'
|
||||
const RETURNED = '\u9000\u56de'
|
||||
const WAIT_SUBMIT = '\u5f85\u63d0\u4ea4'
|
||||
const WAIT_LEADER_LI_APPROVAL = '\u7b49\u5f85 Leader Li \u6279\u590d'
|
||||
const WAIT_BUDGET_ZHAO_APPROVAL = '\u7b49\u5f85 \u8d75\u9884\u7b97 \u6279\u590d'
|
||||
const LEADER_RETURNED_STATUS = '\u9886\u5bfc\u5df2\u9000\u56de\uff0c\u5f85\u91cd\u65b0\u63d0\u4ea4'
|
||||
|
||||
test('application claims are mapped as application documents', () => {
|
||||
@@ -41,7 +43,7 @@ test('application claims are mapped as application documents', () => {
|
||||
assert.equal(request.expenseTableSummary, '预计金额已随申请提交')
|
||||
assert.deepEqual(
|
||||
request.progressSteps.map((step) => step.label),
|
||||
[CREATE_APPLICATION, WAIT_LEADER_LI_APPROVAL, APPROVAL_COMPLETED]
|
||||
[CREATE_APPLICATION, WAIT_LEADER_LI_APPROVAL, BUDGET_MANAGER_APPROVAL, APPROVAL_COMPLETED]
|
||||
)
|
||||
assert.equal(request.progressSteps.some((step) => step.label === 'AI预审'), false)
|
||||
assert.equal(request.progressSteps.some((step) => step.label === '财务审批'), false)
|
||||
@@ -50,6 +52,47 @@ test('application claims are mapped as application documents', () => {
|
||||
assert.equal(request.progressSteps.find((step) => step.label === WAIT_LEADER_LI_APPROVAL)?.current, true)
|
||||
})
|
||||
|
||||
test('application claims wait for department P8 budget monitor after leader approval', () => {
|
||||
const request = mapExpenseClaimToRequest({
|
||||
id: 'claim-application-budget',
|
||||
claim_no: 'AP-20260525103145-BUDGET',
|
||||
employee_name: '张三',
|
||||
department_name: '交付部',
|
||||
manager_name: 'Leader Li',
|
||||
expense_type: 'travel_application',
|
||||
reason: '支撑国网服务器上线部署',
|
||||
location: '上海',
|
||||
amount: 12000,
|
||||
invoice_count: 0,
|
||||
occurred_at: '2026-05-25T00:00:00.000Z',
|
||||
submitted_at: '2026-05-25T02:00:00.000Z',
|
||||
created_at: '2026-05-25T01:30:00.000Z',
|
||||
updated_at: '2026-05-25T03:00:00.000Z',
|
||||
status: 'submitted',
|
||||
approval_stage: BUDGET_MANAGER_APPROVAL,
|
||||
risk_flags_json: [
|
||||
{
|
||||
source: 'manual_approval',
|
||||
event_type: 'expense_application_approval',
|
||||
operator: 'Leader Li',
|
||||
previous_approval_stage: DIRECT_MANAGER_APPROVAL,
|
||||
next_approval_stage: BUDGET_MANAGER_APPROVAL,
|
||||
next_approver_name: '赵预算',
|
||||
next_approver_grade: 'P8',
|
||||
created_at: '2026-05-25T03:00:00.000Z'
|
||||
}
|
||||
],
|
||||
items: []
|
||||
})
|
||||
|
||||
assert.deepEqual(
|
||||
request.progressSteps.map((step) => step.label),
|
||||
[CREATE_APPLICATION, DIRECT_MANAGER_APPROVAL, WAIT_BUDGET_ZHAO_APPROVAL, APPROVAL_COMPLETED]
|
||||
)
|
||||
assert.equal(request.progressSteps.find((step) => step.label === WAIT_BUDGET_ZHAO_APPROVAL)?.current, true)
|
||||
assert.equal(request.progressSteps.find((step) => step.label === DIRECT_MANAGER_APPROVAL)?.time, 'Leader Li通过')
|
||||
})
|
||||
|
||||
test('returned application claims include leader return node and supplement status', () => {
|
||||
const request = mapExpenseClaimToRequest({
|
||||
id: 'claim-application-returned',
|
||||
@@ -86,7 +129,7 @@ test('returned application claims include leader return node and supplement stat
|
||||
|
||||
assert.deepEqual(
|
||||
request.progressSteps.map((step) => step.label),
|
||||
[CREATE_APPLICATION, WAIT_LEADER_LI_APPROVAL, RETURNED, WAIT_SUBMIT]
|
||||
[CREATE_APPLICATION, DIRECT_MANAGER_APPROVAL, RETURNED, WAIT_SUBMIT]
|
||||
)
|
||||
assert.equal(request.progressSteps.find((step) => step.label === RETURNED)?.time, 'Leader Li\u9000\u56de')
|
||||
assert.match(request.progressSteps.find((step) => step.label === RETURNED)?.detail, /2026-05-25/)
|
||||
@@ -96,7 +139,7 @@ test('returned application claims include leader return node and supplement stat
|
||||
assert.equal(request.progressSteps.some((step) => step.label === APPROVAL_COMPLETED), false)
|
||||
})
|
||||
|
||||
test('approved application claims complete after direct manager approval only', () => {
|
||||
test('approved application claims complete after budget approval', () => {
|
||||
const request = mapExpenseClaimToRequest({
|
||||
id: 'claim-application-approved',
|
||||
claim_no: 'AP-20260525113045-HGFEDCBA',
|
||||
@@ -120,6 +163,16 @@ test('approved application claims complete after direct manager approval only',
|
||||
event_type: 'expense_application_approval',
|
||||
operator: '李经理',
|
||||
previous_approval_stage: '直属领导审批',
|
||||
next_approval_stage: '预算管理者审批',
|
||||
next_approver_name: '赵预算',
|
||||
next_approver_grade: 'P8',
|
||||
created_at: '2026-05-25T03:00:00.000Z'
|
||||
},
|
||||
{
|
||||
source: 'budget_approval',
|
||||
event_type: 'expense_application_budget_approval',
|
||||
operator: '赵预算',
|
||||
previous_approval_stage: '预算管理者审批',
|
||||
next_approval_stage: '审批完成',
|
||||
created_at: '2026-05-25T03:00:00.000Z'
|
||||
}
|
||||
@@ -131,10 +184,11 @@ test('approved application claims complete after direct manager approval only',
|
||||
assert.equal(request.workflowNode, '审批完成')
|
||||
assert.deepEqual(
|
||||
request.progressSteps.map((step) => step.label),
|
||||
['创建申请', '直属领导审批', '审批完成']
|
||||
['创建申请', '直属领导审批', '预算管理者审批', '审批完成']
|
||||
)
|
||||
assert.equal(request.progressSteps.every((step) => step.done), true)
|
||||
assert.equal(request.progressSteps.find((step) => step.label === '直属领导审批')?.time, '李经理通过')
|
||||
assert.equal(request.progressSteps.find((step) => step.label === '预算管理者审批')?.time, '赵预算通过')
|
||||
})
|
||||
|
||||
test('progress steps show approval operator time and current stay duration', () => {
|
||||
|
||||
@@ -19,6 +19,10 @@ const approvalDialog = readFileSync(
|
||||
fileURLToPath(new URL('../src/components/travel/TravelRequestApprovalDialog.vue', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
const budgetAnalysisComponent = readFileSync(
|
||||
fileURLToPath(new URL('../src/components/travel/TravelRequestBudgetAnalysis.vue', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
const reimbursementService = readFileSync(
|
||||
fileURLToPath(new URL('../src/services/reimbursements.js', import.meta.url)),
|
||||
'utf8'
|
||||
@@ -53,18 +57,23 @@ test('approval-mode detail collects leader opinion inside confirm dialog before
|
||||
assert.match(detailScript, /const approveConfirmDialogOpen = ref\(false\)/)
|
||||
assert.match(detailScript, /const canApproveRequest = computed/)
|
||||
assert.match(detailScript, /canApproveLeaderExpenseClaims/)
|
||||
assert.match(detailScript, /canApproveBudgetExpenseApplications/)
|
||||
assert.match(detailScript, /isCurrentDirectManagerForRequest/)
|
||||
assert.match(detailScript, /isCurrentRequestApplicant/)
|
||||
assert.match(detailScript, /isFinanceApprovalStage/)
|
||||
assert.match(detailScript, /const isBudgetApprovalStage = computed/)
|
||||
assert.match(detailScript, /const showBudgetAnalysis = computed/)
|
||||
assert.match(detailScript, /const isCurrentApplicant = computed/)
|
||||
assert.match(detailScript, /const isCurrentDirectManagerApprover = computed/)
|
||||
assert.match(detailScript, /const canProcessFinanceApprovalStage = computed/)
|
||||
assert.match(detailScript, /const canProcessBudgetApprovalStage = computed/)
|
||||
assert.match(detailScript, /approvalOpinionTitle/)
|
||||
assert.match(detailScript, /approvalConfirmDescription/)
|
||||
assert.match(detailScript, /approvalNextStage/)
|
||||
assert.doesNotMatch(detailScript, /approvalNextStage/)
|
||||
assert.doesNotMatch(detailScript, /showApplicationLeaderOpinionInput/)
|
||||
assert.doesNotMatch(detailScript, /showLeaderApprovalPanel/)
|
||||
assert.match(detailScript, /const requiresApprovalOpinion = computed\(\(\) => isDirectManagerApprovalStage\.value\)/)
|
||||
assert.match(detailScript, /const requiresApprovalOpinion = computed\(\(\) => false\)/)
|
||||
assert.match(detailScript, /approvalOpinionTitle = computed\(\(\) => \(isFinanceApprovalStage\.value \? '财务意见' : '附加意见'\)\)/)
|
||||
assert.match(detailScript, /buildLeaderApprovalEvents/)
|
||||
assert.match(detailScript, /buildLeaderApprovalInfo/)
|
||||
assert.match(detailScript, /const leaderApprovalEvents = computed/)
|
||||
@@ -76,11 +85,13 @@ test('approval-mode detail collects leader opinion inside confirm dialog before
|
||||
assert.match(detailScript, /isDirectManagerApprovalStage\.value\)[\s\S]*return isCurrentDirectManagerApprover\.value/)
|
||||
assert.match(detailScript, /isDirectManagerApprovalStage\.value[\s\S]*&& isCurrentDirectManagerApprover\.value/)
|
||||
assert.match(detailScript, /canProcessFinanceApprovalStage\.value/)
|
||||
assert.match(detailScript, /canProcessBudgetApprovalStage\.value/)
|
||||
assert.doesNotMatch(detailScript, /leaderApprovalReadonlyText/)
|
||||
assert.match(detailScript, /resolveGeneratedDraftClaimNo/)
|
||||
assert.match(detailScript, /approveActionLabel/)
|
||||
assert.match(detailScript, /approveExpenseClaim\(request\.value\.claimId, \{[\s\S]*opinion: leaderOpinion\.value\.trim\(\)/)
|
||||
assert.match(detailScript, /approveExpenseClaim\(request\.value\.claimId, \{[\s\S]*opinion: leaderOpinion\.value\.trim\(\) \|\| '同意'/)
|
||||
assert.match(detailScript, /报销草稿 \$\{generatedDraftClaimNo\} 已生成/)
|
||||
assert.match(detailScript, /流转至预算管理者审批/)
|
||||
|
||||
assert.doesNotMatch(detailTemplate, /v-if="showLeaderApprovalPanel"/)
|
||||
assert.doesNotMatch(detailTemplate, /showApplicationLeaderOpinionInput/)
|
||||
@@ -96,6 +107,7 @@ test('approval-mode detail collects leader opinion inside confirm dialog before
|
||||
assert.doesNotMatch(detailTemplate, /leaderApprovalReadonlyText/)
|
||||
assert.doesNotMatch(detailTemplate, /\u5f85\u76f4\u5c5e\u9886\u5bfc\u586b\u5199\u5ba1\u6279\u610f\u89c1/)
|
||||
assert.match(detailTemplate, /领导意见/)
|
||||
assert.match(detailTemplate, /<TravelRequestBudgetAnalysis[\s\S]*v-if="showBudgetAnalysis"[\s\S]*:claim-id="request\.claimId"/)
|
||||
assert.match(approvalDialog, /\{\{ opinionTitle \}\}/)
|
||||
assert.doesNotMatch(detailTemplate, /v-model="leaderOpinion"/)
|
||||
assert.match(detailTemplate, /@click="handleApproveRequest"/)
|
||||
@@ -105,7 +117,10 @@ test('approval-mode detail collects leader opinion inside confirm dialog before
|
||||
assert.match(detailTemplate, /:description="approvalConfirmDescription"/)
|
||||
assert.match(detailTemplate, /:confirm-text="approveConfirmText"/)
|
||||
assert.match(detailTemplate, /:busy-text="approveBusyText"/)
|
||||
assert.match(detailTemplate, /:next-stage="approvalNextStage"/)
|
||||
assert.doesNotMatch(detailTemplate, /:next-stage="approvalNextStage"/)
|
||||
assert.doesNotMatch(approvalDialog, /submit-confirm-summary/)
|
||||
assert.doesNotMatch(approvalDialog, /单据编号/)
|
||||
assert.doesNotMatch(approvalDialog, /当前节点/)
|
||||
assert.match(detailTemplate, /v-model:opinion="leaderOpinion"/)
|
||||
assert.match(detailTemplate, /:opinion-placeholder="approvalOpinionPlaceholder"/)
|
||||
assert.match(detailTemplate, /:opinion-hint="approvalOpinionHint"/)
|
||||
@@ -119,8 +134,8 @@ test('approval-mode detail collects leader opinion inside confirm dialog before
|
||||
assert.doesNotMatch(handleApproveRequest, /approveExpenseClaim/)
|
||||
assert.doesNotMatch(handleApproveRequest, /leaderOpinion\.value\.trim/)
|
||||
assert.match(confirmApproveRequest, /approveExpenseClaim/)
|
||||
assert.match(confirmApproveRequest, /requiresApprovalOpinion\.value && !leaderOpinion\.value\.trim\(\)/)
|
||||
assert.match(confirmApproveRequest, /请先填写领导意见,填写后才能确认审核。/)
|
||||
assert.doesNotMatch(confirmApproveRequest, /requiresApprovalOpinion\.value && !leaderOpinion\.value\.trim\(\)/)
|
||||
assert.doesNotMatch(confirmApproveRequest, /请先填写领导意见,填写后才能确认审核。/)
|
||||
|
||||
assert.match(approvalDialog, /<textarea/)
|
||||
assert.match(approvalDialog, /update:opinion/)
|
||||
@@ -141,4 +156,11 @@ test('approval-mode detail collects leader opinion inside confirm dialog before
|
||||
|
||||
assert.match(reimbursementService, /export function approveExpenseClaim\(claimId, payload = \{\}\)/)
|
||||
assert.match(reimbursementService, /\/approve/)
|
||||
assert.match(reimbursementService, /export function fetchExpenseClaimBudgetAnalysis/)
|
||||
assert.match(reimbursementService, /\/budget-analysis/)
|
||||
assert.match(budgetAnalysisComponent, /预算分析/)
|
||||
assert.match(budgetAnalysisComponent, /当前预算额度/)
|
||||
assert.match(budgetAnalysisComponent, /此次费用占预算/)
|
||||
assert.match(budgetAnalysisComponent, /综合评分/)
|
||||
assert.match(budgetAnalysisComponent, /fetchExpenseClaimBudgetAnalysis/)
|
||||
})
|
||||
|
||||
@@ -684,9 +684,12 @@ test('return reason dialog is wired into approval and detail return actions', ()
|
||||
assert.match(returnReasonDialog, /application_budget_basis_missing/)
|
||||
assert.match(returnReasonDialog, /application_policy_mismatch/)
|
||||
assert.match(returnReasonDialog, /application_attachment_needed/)
|
||||
assert.match(returnReasonDialog, /application_other/)
|
||||
assert.match(returnReasonDialog, /退单选项/)
|
||||
assert.match(returnReasonDialog, /selectionError/)
|
||||
assert.match(returnReasonDialog, /selectedCodes\.value\.length === 0/)
|
||||
assert.match(returnReasonDialog, /selectedApplicationCode/)
|
||||
assert.match(returnReasonDialog, /application \? 'radio' : 'checkbox'/)
|
||||
assert.match(returnReasonDialog, /selectedReasonCodes\.value\.length === 0/)
|
||||
assert.match(returnReasonDialog, /lastAutoReason/)
|
||||
assert.match(returnReasonDialog, /reason_codes/)
|
||||
assert.match(approvalCenterTemplate, /<TravelRequestDetailView/)
|
||||
|
||||
Reference in New Issue
Block a user