This commit is contained in:
2026-08-18 09:09:34 +08:00
parent dcbca0bf85
commit 8046f9b9ee
27 changed files with 230 additions and 83 deletions
+3 -2
View File
@@ -79,8 +79,9 @@ describe('finance five-page delivery contract', () => {
const columns = page.slice(page.indexOf('const columns:'))
expect(columns.indexOf("key: 'actions'")).toBeLessThan(columns.indexOf("key: 'direction'"))
expect(page).toContain('<ApiErrorMeta :details="detailError"')
expect(page).toContain('<ApiErrorMeta :details="reviewError"')
expect(page).toContain('<ApiErrorMeta :details="inclusionError"')
expect(page).toContain('<OperationErrorToast :details="reviewError || inclusionError || generationError" />')
expect(page).not.toContain('<ApiErrorMeta :details="reviewError"')
expect(page).not.toContain('<ApiErrorMeta :details="inclusionError"')
expect(page).not.toContain('接口尚未提供')
expect(page).not.toContain('disabled title=')
})
@@ -9,6 +9,7 @@ import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
import { getApiErrorDetails, getApiErrorMessage, type ApiErrorDetails } from '@/shared/api/http'
import AdminPage from '@/shared/components/AdminPage.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
@@ -312,6 +313,7 @@ watch(() => [route.name, String(route.params.feeSourceId || '')], ([name, feeSou
<template>
<AdminPage as="div" class="finance-arap-page" spacing="normal">
<OperationErrorToast :details="reviewError || inclusionError || generationError" />
<el-result v-if="!canView" icon="warning" title="无权访问客户应收应付" sub-title="请联系管理员开通账单查看权限" />
<ListPageLayout v-else-if="!detailOpen" aria-label="客户应收应付">
<template #toolbar><QueryToolbar v-model="filters" class="surface-panel" ariaLabel="客户应收应付筛选" :fields="queryFields" preference-key="finance-customer-arap-query" @search="search" /></template>
@@ -339,15 +341,12 @@ watch(() => [route.name, String(route.params.feeSourceId || '')], ([name, feeSou
</section>
</AdminPage>
<AppDialog v-model="reviewOpen" title="费用来源审核" confirm-text="提交审核" :confirm-loading="reviewSaving" @confirm="submitReview">
<el-alert v-if="reviewError" type="error" :closable="false"><ApiErrorMeta :details="reviewError" /></el-alert>
<el-form label-position="top"><el-form-item label="审核结论"><el-segmented v-model="reviewForm.action" :options="[{ label: '通过', value: 'APPROVE' }, { label: '驳回', value: 'REJECT' }]" /></el-form-item><el-form-item :label="reviewForm.action === 'REJECT' ? '驳回原因' : '审核说明(可选)'"><el-input v-model="reviewForm.reason" type="textarea" :rows="3" maxlength="500" :placeholder="reviewForm.action === 'REJECT' ? '请填写驳回原因' : '请输入审核说明(可选)'" show-word-limit /></el-form-item></el-form>
</AppDialog>
<AppDialog v-model="inclusionOpen" title="参与结算设置" confirm-text="保存设置" :confirm-loading="inclusionSaving" @confirm="submitInclusion">
<el-alert v-if="inclusionError" type="error" :closable="false"><ApiErrorMeta :details="inclusionError" /></el-alert>
<el-form label-position="top"><el-form-item label="结算状态"><el-switch v-model="inclusionForm.included" active-text="参与结算" inactive-text="排除结算" /></el-form-item><el-form-item label="调整原因"><el-input v-model="inclusionForm.reason" type="textarea" :rows="3" maxlength="500" placeholder="请说明参与或排除结算的原因" show-word-limit /></el-form-item></el-form>
</AppDialog>
<AppDialog v-model="generationOpen" :title="generationForm.mode === 'BY_CUTOFF' ? '按截止日期生成对账单' : '按勾选生成对账单'" confirm-text="生成草稿" :confirm-loading="generationSaving" @confirm="submitGeneration">
<el-alert v-if="generationError" type="error" :closable="false"><ApiErrorMeta :details="generationError" /></el-alert>
<el-form label-position="top"><el-form-item label="客户"><el-select v-model="generationForm.customerId" filterable placeholder="请选择客户" style="width:100%"><el-option v-for="customer in activeCustomers" :key="String(customer.id)" :label="customer.tenantName" :value="String(customer.id)" /></el-select></el-form-item><el-form-item label="业务期间"><el-date-picker v-model="generationForm.period" type="daterange" value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="截止日期" style="width:100%" /></el-form-item><el-form-item v-if="generationForm.mode === 'SELECTED'" label="已选费用"><span>{{ selectedRows.length }} 笔,合计 {{ money(selectedAmount) }} CNY</span></el-form-item><el-form-item label="备注"><el-input v-model="generationForm.remark" type="textarea" :rows="3" maxlength="500" show-word-limit /></el-form-item></el-form>
</AppDialog>
</AdminPage>
@@ -38,7 +38,9 @@ describe('Finance formal settlement desktop presentation', () => {
expect(source.match(/width="min\(760px, 94vw\)"/g)).toHaveLength(2)
expect(source).toContain('.partial-summary { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));')
expect(source).toContain('.settlement-operation-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));')
expect(source).toContain('<el-alert v-if="receiptValidationError"')
expect(source).toContain('<el-alert v-if="receiptError"')
expect(source).toContain('<OperationErrorToast :message="receiptValidationError" />')
expect(source).toContain('<OperationErrorToast :details="receiptError" />')
expect(source).not.toContain('<el-alert v-if="receiptValidationError"')
expect(source).not.toContain('<el-alert v-if="receiptError"')
})
})
@@ -10,6 +10,7 @@ import { categoryLabels, listSuppliers, type Supplier } from '@/modules/supplier
import { getApiErrorDetails, getApiErrorMessage, type ApiErrorDetails } from '@/shared/api/http'
import AdminPage from '@/shared/components/AdminPage.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
@@ -480,6 +481,8 @@ onMounted(() => {
<template>
<AdminPage as="div" class="finance-settlement-page" spacing="normal">
<OperationErrorToast :message="receiptValidationError" />
<OperationErrorToast :details="receiptError" />
<el-result v-if="!canView" icon="warning" :title="`无权访问${title}`" sub-title="请联系管理员开通结算单查看权限" />
<DetailPageLayout v-else-if="detailMode" class="settlement-detail-page surface-panel" :aria-label="`${title}详情`">
<template #header><header class="settlement-detail-header">
@@ -631,9 +634,6 @@ onMounted(() => {
<AppDialog v-model="receiptOpen" title="登记收款" width="min(760px, 94vw)" :confirm-loading="receiptBusy" @confirm="submitReceipt">
<div v-if="detail" class="partial-summary"><div><span>结算单号</span><strong>{{ detail.settlementNo }}</strong></div><div><span>客户公司</span><strong>{{ detailPartyName() }}</strong></div><div><span>应收金额</span><strong>{{ money(detail.amount) }}</strong></div><div><span>已收金额</span><strong class="is-success">{{ money(detail.paidAmount) }}</strong></div><div><span>当前余额</span><strong class="is-danger">{{ money(detail.outstandingAmount) }}</strong></div></div>
<el-alert v-if="receiptValidationError" type="error" :title="receiptValidationError" :closable="false" show-icon class="operation-error" />
<el-alert v-if="receiptError" type="error" :title="receiptError.message" :closable="false" show-icon class="operation-error" />
<ApiErrorMeta v-if="receiptError" :details="receiptError" class="operation-error-meta" />
<el-form class="settlement-operation-form" label-position="top"><el-form-item label="收款日期"><el-date-picker v-model="receiptForm.receivedDate" type="date" value-format="YYYY-MM-DD" /></el-form-item><el-form-item label="本次收款金额(CNY)"><el-input-number v-model="receiptForm.amount" :min="0.01" :max="detail?.outstandingAmount || 0.01" :precision="2" /></el-form-item><el-form-item label="收款方式"><el-select v-model="receiptForm.paymentMethod"><el-option label="银行转账" value="BANK_TRANSFER" /><el-option label="现金" value="CASH" /><el-option label="其他" value="OTHER" /></el-select></el-form-item><el-form-item label="收款凭证"><el-upload :key="receiptUploadKey" :auto-upload="false" :limit="1" accept="image/*" :on-change="selectReceiptFile"><el-button>选择图片</el-button></el-upload></el-form-item><el-form-item label="备注"><el-input v-model="receiptForm.remark" type="textarea" /></el-form-item></el-form>
<div v-if="detail" class="remaining-preview"><span>登记后剩余余额</span><strong>{{ remainingAfter(receiptForm.amount) }}</strong></div>
</AppDialog>
@@ -1,4 +1,4 @@
import ElementPlus, { ElMessageBox } from 'element-plus'
import ElementPlus, { ElMessage, ElMessageBox } from 'element-plus'
import { createPinia, setActivePinia } from 'pinia'
import { createMemoryHistory, createRouter } from 'vue-router'
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
@@ -150,7 +150,8 @@ describe('finance G3/G4 page operations', () => {
}, ['9001'])
})
it('keeps receipt errors in the modal and rejects non-image vouchers before upload', async () => {
it('shows receipt errors as dismissible light messages and rejects non-image vouchers before upload', async () => {
const messageSpy = vi.spyOn(ElMessage, 'error')
const pinia = piniaWithPermissions(['platform:finance:settlement-ar:list', 'platform:finance:receipt:create'])
const wrapper = mount(FinanceFormalSettlementPage, { props: { kind: 'receivable' }, attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
await flushPromises()
@@ -172,10 +173,12 @@ describe('finance G3/G4 page operations', () => {
vm.selectReceiptFile({ raw: new File(['receipt'], 'receipt.png', { type: 'image/png' }) })
vi.mocked(uploadFinanceVoucher).mockRejectedValueOnce(new ApiResponseError('FIN_400: 凭证校验失败', 'FIN_400', 400, undefined, 'request-1', 'trace-1'))
await vm.submitReceipt()
await flushPromises()
expect(vm.receiptOpen).toBe(true)
expect(vm.receiptError?.message).toContain('FIN_400')
expect(wrapper.text()).toContain('FIN_400')
expect(messageSpy).toHaveBeenCalledWith(expect.objectContaining({ message: expect.stringContaining('FIN_400'), showClose: true, duration: 4500 }))
expect(wrapper.text()).not.toContain('FIN_400')
expect(wrapper.find('[aria-label="结算单应收列表"]').exists()).toBe(true)
})