修复财务页面竞态并整理采购与运输展示
This commit is contained in:
@@ -60,7 +60,7 @@ function setFee(overrides: Partial<FinanceFeeSourceDetail> = {}) {
|
||||
vi.mocked(api.listFinanceFeeSources).mockResolvedValue({ records: [current], total: 1, pages: 1 })
|
||||
vi.mocked(api.getFinanceFeeSource).mockResolvedValue(current)
|
||||
}
|
||||
async function mountPage(kind: 'arap' | 'statements') {
|
||||
async function mountPage(kind: 'arap' | 'statements', cachedWorkspace = false) {
|
||||
const router = createRouter({ history: createMemoryHistory(), routes: [
|
||||
{ path: '/finance/customer-arap', name: 'finance-customer-arap', component: FinanceCustomerArapView },
|
||||
{ path: '/finance/customer-arap/:feeSourceId', name: 'finance-customer-arap-detail', component: FinanceCustomerArapView },
|
||||
@@ -69,7 +69,10 @@ async function mountPage(kind: 'arap' | 'statements') {
|
||||
] })
|
||||
await router.push(kind === 'arap' ? '/finance/customer-arap' : '/finance/customer-statements')
|
||||
await router.isReady()
|
||||
const wrapper = mount(kind === 'arap' ? FinanceCustomerArapView : FinanceCustomerStatementsView, { attachTo: document.body, global: { plugins: [ElementPlus, router] } })
|
||||
const component = cachedWorkspace
|
||||
? { template: '<router-view v-slot="{ Component, route }"><keep-alive><component :is="Component" :key="route.name" /></keep-alive></router-view>' }
|
||||
: kind === 'arap' ? FinanceCustomerArapView : FinanceCustomerStatementsView
|
||||
const wrapper = mount(component, { attachTo: document.body, global: { plugins: [ElementPlus, router] } })
|
||||
await flushPromises()
|
||||
return { wrapper, router }
|
||||
}
|
||||
@@ -99,17 +102,6 @@ async function fillCreation(dialog: VueWrapper, selected = false) {
|
||||
await flushPromises()
|
||||
await dialog.get('textarea').setValue('测试备注')
|
||||
}
|
||||
async function openAdjustment() {
|
||||
const { wrapper, router } = await mountPage('statements')
|
||||
await rowClick(wrapper, '调整')
|
||||
return { wrapper, router, dialog: activeDialog(wrapper) }
|
||||
}
|
||||
async function fillAdjustment(dialog: VueWrapper) {
|
||||
await dialog.get('textarea[placeholder="请输入整体调整原因"]').setValue(' 整体调整 ')
|
||||
await dialog.get('.el-table__body-wrapper input[type="checkbox"]').setValue(true)
|
||||
dialog.getComponent({ name: 'ElInputNumber' }).vm.$emit('update:modelValue', 88.12)
|
||||
await dialog.get('.el-table__body-wrapper input[placeholder="请输入明细说明"]').setValue(' 行说明 ')
|
||||
}
|
||||
enableAutoUnmount(afterEach)
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks()
|
||||
@@ -233,6 +225,30 @@ describe('fee source and statement native dialogs', () => {
|
||||
await click(dialog, '确定')
|
||||
expect(api.createReconciliationStatement).toHaveBeenCalledWith(expect.objectContaining({ generationMode: 'SELECTED', feeSourceIds: [fee.feeSourceId] }))
|
||||
})
|
||||
it.each([false, true])('opens selected statement generation directly from arap with a cached statement page=%s', async cached => {
|
||||
setFee({ reviewStatus: 'APPROVED' })
|
||||
const { wrapper, router } = await mountPage(cached ? 'statements' : 'arap', true)
|
||||
const cachedPage = cached ? wrapper.getComponent(FinanceCustomerStatementsView).element : null
|
||||
if (cached) {
|
||||
await click(wrapper, '生成对账单')
|
||||
await click(activeDialog(wrapper), '取消')
|
||||
await router.push({ name: 'finance-customer-arap' })
|
||||
await flushPromises()
|
||||
}
|
||||
await wrapper.get('.el-table__body-wrapper input[type="checkbox"]').setValue(true)
|
||||
await click(wrapper, '生成所选对账单')
|
||||
|
||||
const page = wrapper.getComponent(FinanceCustomerStatementsView)
|
||||
if (cachedPage) expect(page.element).toBe(cachedPage)
|
||||
const dialog = activeDialog(wrapper)
|
||||
expect(dialog.props('title')).toBe('生成客户对账单')
|
||||
expect(dialog.getComponent({ name: 'ElRadioGroup' }).props('modelValue')).toBe('SELECTED')
|
||||
expect(dialog.findAllComponents({ name: 'ElSelect' })[0]!.props('modelValue')).toBe(String(customer.id))
|
||||
expect(dialog.findAllComponents({ name: 'ElDatePicker' }).map(item => item.props('modelValue'))).toEqual(['2026-10-01', '2026-10-01'])
|
||||
expect(dialog.get('.selected-source-summary').text()).toContain('已选 1 笔,合计 100.00 CNY')
|
||||
expect(router.currentRoute.value.query).toEqual({})
|
||||
expect(api.createReconciliationStatement).not.toHaveBeenCalled()
|
||||
})
|
||||
it('preserves customer and period validation before creating', async () => {
|
||||
const { dialog } = await openCreate()
|
||||
await click(dialog, '确定')
|
||||
@@ -271,48 +287,14 @@ describe('fee source and statement native dialogs', () => {
|
||||
expect((dialog.get('textarea').element as HTMLTextAreaElement).value).toBe('测试备注')
|
||||
expect(dialog.props('modelValue')).toBe(true)
|
||||
})
|
||||
it('keeps all adjustment rows in the main dialog flow with plain statement facts', async () => {
|
||||
vi.mocked(api.getReconciliationStatement).mockResolvedValue({ ...detail, lines: Array.from({ length: 13 }, (_, index) => ({ ...detail.lines[0]!, statementLineId: 'line-' + index, feeItemName: '费用' + (index + 1) })) })
|
||||
const { dialog } = await openAdjustment()
|
||||
expect(dialog.props('width')).toContain('900px')
|
||||
expect(dialog.props('title')).toContain(statement.billNo)
|
||||
expect(dialog.find('[data-tone="summary"] input').exists()).toBe(false)
|
||||
expect(dialog.getComponent({ name: 'ElTable' }).props('maxHeight')).toBeUndefined()
|
||||
expect(dialog.findAll('.el-table__body-wrapper tr')).toHaveLength(13)
|
||||
expect(dialog.get('.el-table__body-wrapper').text()).toContain('费用13')
|
||||
})
|
||||
it('saves only checked lines with full IDs and the freshly loaded version', async () => {
|
||||
const { dialog } = await openAdjustment()
|
||||
await fillAdjustment(dialog)
|
||||
await dialog.get('button[aria-label="展开全屏"]').trigger('click')
|
||||
await click(dialog, '确定')
|
||||
expect(api.actionReconciliationStatement).toHaveBeenCalledWith(statement.id, { action: 'ADJUST', reason: '整体调整', expectedVersion: 8, adjustmentLines: [{ statementLineId: '9223372036854775011', adjustedAmount: 88.12, reason: '行说明' }] })
|
||||
})
|
||||
it('requires an overall reason, selection and each selected line reason', async () => {
|
||||
const { dialog } = await openAdjustment()
|
||||
await click(dialog, '确定')
|
||||
it.each(['APPROVED', 'ISSUED', 'PAID'])('does not offer adjustment or void after statement approval: %s', async status => {
|
||||
vi.mocked(api.listFinanceStatements).mockResolvedValue([{ ...statement, status }])
|
||||
const { wrapper } = await mountPage('statements')
|
||||
const labels = wrapper.findAll('.el-table__body-wrapper button').map(button => button.text().trim())
|
||||
expect(labels).toContain('详情')
|
||||
expect(labels).not.toContain('调整')
|
||||
expect(labels).not.toContain('作废')
|
||||
expect(api.actionReconciliationStatement).not.toHaveBeenCalled()
|
||||
await dialog.get('textarea').setValue('整体调整')
|
||||
await click(dialog, '确定')
|
||||
expect(api.actionReconciliationStatement).not.toHaveBeenCalled()
|
||||
await dialog.get('.el-table__body-wrapper input[type="checkbox"]').setValue(true)
|
||||
await click(dialog, '确定')
|
||||
expect(api.actionReconciliationStatement).not.toHaveBeenCalled()
|
||||
})
|
||||
it('locks adjustment rows and retains their state with visible failure diagnostics', async () => {
|
||||
const pending = deferred<FinanceReconciliationDetail>()
|
||||
vi.mocked(api.actionReconciliationStatement).mockReturnValue(pending.promise)
|
||||
const { dialog } = await openAdjustment()
|
||||
await fillAdjustment(dialog)
|
||||
await click(dialog, '确定')
|
||||
expect(dialog.props('showClose')).toBe(false)
|
||||
expect(dialog.get('.el-table__body-wrapper input[type="checkbox"]').attributes('disabled')).toBeDefined()
|
||||
pending.reject(failure())
|
||||
await flushPromises()
|
||||
expect(dialog.getComponent(OperationErrorToast).props('details')).toMatchObject({ message: '测试拒绝操作', requestId: 'finance-dialog-test' })
|
||||
expect((dialog.get('.el-table__body-wrapper input[type="checkbox"]').element as HTMLInputElement).checked).toBe(true)
|
||||
expect((dialog.get('.el-table__body-wrapper input[placeholder="请输入明细说明"]').element as HTMLInputElement).value).toBe(' 行说明 ')
|
||||
expect(dialog.props('modelValue')).toBe(true)
|
||||
})
|
||||
it.each([
|
||||
['驳回', 'PENDING_REVIEW', 'REJECT'], ['作废', 'DRAFT', 'VOID'], ['重开', 'REJECTED', 'REOPEN'],
|
||||
|
||||
Reference in New Issue
Block a user