费用优化
This commit is contained in:
@@ -6,7 +6,7 @@ import { ElMessage } from 'element-plus'
|
||||
import { ApiResponseError } from '@/shared/api/http'
|
||||
import { listPlatformTenants } from '@/modules/iam'
|
||||
import * as api from '../api/finance'
|
||||
import type { FinanceAccountSummary, FinanceRechargeRequest } from '../types'
|
||||
import type { FinanceAccountSummary, FinanceLedgerEntry, FinanceRechargeRequest } from '../types'
|
||||
import FinanceCustomerAccountsView from './FinanceCustomerAccountsView.vue'
|
||||
|
||||
const { default: ElementPlus, ElDialog } = createRequire(import.meta.url)('element-plus') as typeof import('element-plus')
|
||||
@@ -17,7 +17,7 @@ vi.mock('@/modules/downloads', () => ({ createPlatformExportTask: vi.fn() }))
|
||||
vi.mock('@/shared/components/exportTaskCreatedDialog', () => ({ showExportTaskCreatedDialog: vi.fn() }))
|
||||
vi.mock('../api/finance', () => ({
|
||||
listFinanceAccounts: vi.fn(), listFinanceAccountLedger: vi.fn(), listFinanceRechargeRequests: vi.fn(),
|
||||
reviewFinanceRechargeRequest: vi.fn(), adjustFinanceAccount: vi.fn(), updateFinanceCreditLimit: vi.fn(),
|
||||
reviewFinanceRechargeRequest: vi.fn(), adjustFinanceAccount: vi.fn(), refundFinanceConsumption: vi.fn(), updateFinanceCreditLimit: vi.fn(),
|
||||
downloadFinanceRechargeVoucher: vi.fn(), getFinanceCustomerTerms: vi.fn(), updateFinanceCustomerTerms: vi.fn(),
|
||||
}))
|
||||
const account: FinanceAccountSummary = {
|
||||
@@ -287,3 +287,105 @@ describe('customer wallet native dialogs', () => {
|
||||
expect(activeDialog(wrapper).find('.voucher-preview__image').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
const consumption: FinanceLedgerEntry = {
|
||||
ledgerId: '61', businessNo: 'AC-CONSUME-61', changeType: 'CONSUMPTION', amount: '-80.00',
|
||||
sourceNo: 'RELABEL-1', occurredAt: '', businessType: 'ADJUSTMENT', documentType: '', documentId: '', documentNo: '',
|
||||
receivableChange: 0, payableChange: 0, outstandingReceivableAfter: 0, operatorName: '', remark: '',
|
||||
}
|
||||
async function openRefundDialog() {
|
||||
vi.mocked(api.listFinanceAccountLedger).mockResolvedValue({ records: [consumption], total: 1, pages: 1 })
|
||||
const wrapper = await mountPage()
|
||||
await click(wrapper, '退款', '.ledger-table button')
|
||||
return { wrapper, dialog: activeDialog(wrapper) }
|
||||
}
|
||||
describe('linked original consumption refunds', () => {
|
||||
it.each(['', '0', '-1', '80.01', '1.001', 'NaN'])('rejects invalid refund %s', async amount => {
|
||||
const { dialog } = await openRefundDialog()
|
||||
await dialog.get('input[placeholder="请输入退款金额"]').setValue(amount)
|
||||
await dialog.get('textarea').setValue('测试退款')
|
||||
await click(dialog, '确认退款')
|
||||
expect(api.refundFinanceConsumption).not.toHaveBeenCalled()
|
||||
})
|
||||
it('requires a reason and keeps the original consumption visible', async () => {
|
||||
const { dialog } = await openRefundDialog()
|
||||
expect(dialog.text()).toContain('AC-CONSUME-61')
|
||||
expect(dialog.text()).toContain('RELABEL-1')
|
||||
await dialog.get('input[placeholder="请输入退款金额"]').setValue('10')
|
||||
await click(dialog, '确认退款')
|
||||
expect(api.refundFinanceConsumption).not.toHaveBeenCalled()
|
||||
})
|
||||
it('locks a pending refund, preserves failures, and retries the identical payload with the same key', async () => {
|
||||
const pending = deferred<Record<string, unknown>>()
|
||||
vi.mocked(api.refundFinanceConsumption).mockReturnValueOnce(pending.promise).mockResolvedValueOnce({})
|
||||
const { wrapper, dialog } = await openRefundDialog()
|
||||
await dialog.get('input[placeholder="请输入退款金额"]').setValue('12.3')
|
||||
await dialog.get('textarea').setValue(' 原单多扣 ')
|
||||
await click(dialog, '确认退款')
|
||||
expect(dialog.props()).toMatchObject({ showClose: false, closeOnPressEscape: false, closeOnClickModal: false })
|
||||
expect(dialog.get('textarea').attributes('disabled')).toBeDefined()
|
||||
await click(dialog, '取消')
|
||||
expect(dialog.props('modelValue')).toBe(true)
|
||||
pending.reject(failure())
|
||||
await flushPromises()
|
||||
expect(dialog.text()).toContain('测试操作未成功')
|
||||
const first = vi.mocked(api.refundFinanceConsumption).mock.calls[0]!
|
||||
expect(first).toEqual([account.customerId, '61', { amount: '12.30', reason: '原单多扣' }, expect.stringMatching(/^finance-refund-/)])
|
||||
await click(dialog, '确认退款')
|
||||
expect(vi.mocked(api.refundFinanceConsumption).mock.calls[1]).toEqual(first)
|
||||
expect(api.listFinanceAccountLedger).toHaveBeenCalledTimes(2)
|
||||
expect(wrapper.text()).toContain('账户流水')
|
||||
})
|
||||
it('creates a new key for changed amount after a rejected request', async () => {
|
||||
vi.mocked(api.refundFinanceConsumption).mockRejectedValue(failure())
|
||||
const { dialog } = await openRefundDialog()
|
||||
await dialog.get('input[placeholder="请输入退款金额"]').setValue('10')
|
||||
await dialog.get('textarea').setValue('退款')
|
||||
await click(dialog, '确认退款')
|
||||
await dialog.get('input[placeholder="请输入退款金额"]').setValue('11')
|
||||
await click(dialog, '确认退款')
|
||||
const calls = vi.mocked(api.refundFinanceConsumption).mock.calls
|
||||
expect(calls[0]![3]).not.toBe(calls[1]![3])
|
||||
})
|
||||
it('hides refunds without adjustment permission and labels refund original references', async () => {
|
||||
can.mockImplementation(permission => permission !== 'platform:finance:customer-account:adjust')
|
||||
vi.mocked(api.listFinanceAccountLedger).mockResolvedValue({ records: [consumption, { ...consumption, ledgerId: '62', changeType: 'REFUND', amount: '10.00', referenceNo: 'AC-CONSUME-61' }], total: 2, pages: 1 })
|
||||
const wrapper = await mountPage()
|
||||
expect(wrapper.findAll('.ledger-table button').some(button => button.text() === '退款')).toBe(false)
|
||||
expect(wrapper.text()).toContain('消费退款')
|
||||
expect(wrapper.text()).toContain('原消费:')
|
||||
})
|
||||
})
|
||||
|
||||
describe('customer wallet detail error recovery', () => {
|
||||
it.each(['ACCOUNT', 'LEDGER'] as const)('reloads the current detail after %s fails without same-route navigation', async failurePoint => {
|
||||
if (failurePoint === 'ACCOUNT') vi.mocked(api.listFinanceAccounts).mockRejectedValueOnce(failure())
|
||||
else vi.mocked(api.listFinanceAccountLedger).mockRejectedValueOnce(failure())
|
||||
const wrapper = await mountPage()
|
||||
expect(wrapper.get('.load-error-state').text()).toContain('测试操作未成功')
|
||||
const pending = deferred<Awaited<ReturnType<typeof api.listFinanceAccounts>>>()
|
||||
vi.mocked(api.listFinanceAccounts).mockReturnValueOnce(pending.promise)
|
||||
await click(wrapper, '重新加载', '.load-error-state button')
|
||||
expect(api.listFinanceAccounts).toHaveBeenCalledTimes(2)
|
||||
pending.resolve({ records: [{ ...account, actualBalance: '90.00' }], total: 1, pages: 1 })
|
||||
await flushPromises()
|
||||
expect(wrapper.find('.load-error-state').exists()).toBe(false)
|
||||
expect(wrapper.get('[aria-label="客户资金账户余额"]').text()).toContain('¥90.00')
|
||||
expect(wrapper.find('.ledger-table').exists()).toBe(true)
|
||||
expect(api.listFinanceAccountLedger).toHaveBeenCalledTimes(failurePoint === 'ACCOUNT' ? 1 : 2)
|
||||
expect(api.listFinanceRechargeRequests).toHaveBeenCalledTimes(failurePoint === 'ACCOUNT' ? 1 : 2)
|
||||
})
|
||||
})
|
||||
|
||||
it('enforces the refund contract reason limit before submitting', async () => {
|
||||
const { wrapper, dialog } = await openRefundDialog()
|
||||
expect(dialog.get('textarea').attributes('maxlength')).toBe('500')
|
||||
await dialog.get('input[placeholder="请输入退款金额"]').setValue('10')
|
||||
const vm = wrapper.vm as unknown as { refundForm: { reason: string } }
|
||||
vm.refundForm.reason = '退'.repeat(501)
|
||||
await click(dialog, '确认退款')
|
||||
expect(api.refundFinanceConsumption).not.toHaveBeenCalled()
|
||||
vm.refundForm.reason = '退'.repeat(500)
|
||||
await click(dialog, '确认退款')
|
||||
expect(api.refundFinanceConsumption).toHaveBeenCalledWith(account.customerId, consumption.ledgerId, { amount: '10.00', reason: '退'.repeat(500) }, expect.any(String))
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user