393 lines
25 KiB
TypeScript
393 lines
25 KiB
TypeScript
import { readFileSync } from 'node:fs'
|
|
import { resolve } from 'node:path'
|
|
import ElementPlus from 'element-plus'
|
|
import { createPinia, setActivePinia } from 'pinia'
|
|
import { createMemoryHistory, createRouter } from 'vue-router'
|
|
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
|
|
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
|
import { useAuthStore } from '@/core/auth/auth'
|
|
import { createPlatformExportTask } from '@/modules/downloads'
|
|
import { ApiResponseError } from '@/shared/api/http'
|
|
import { showExportTaskCreatedDialog } from '@/shared/components/exportTaskCreatedDialog'
|
|
import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
|
|
import { adjustFinanceFeeSource, editFinanceFeeSource, getFinanceFeeSource, listFinanceFeeSources, reviewFinanceFeeSource, submitFinanceFeeSource, voidFinanceFeeSource } from '../api/finance'
|
|
import type { FinanceFeeSourceDetail, FinanceFeeSourceSummary } from '../types'
|
|
import FinanceCustomerArapView from './FinanceCustomerArapView.vue'
|
|
|
|
vi.mock('@/modules/iam', () => ({ listPlatformTenants: vi.fn() }))
|
|
vi.mock('@/modules/downloads', () => ({ createPlatformExportTask: vi.fn() }))
|
|
vi.mock('@/shared/components/exportTaskCreatedDialog', () => ({ showExportTaskCreatedDialog: vi.fn() }))
|
|
vi.mock('../api/finance', () => ({
|
|
listFinanceFeeSources: vi.fn(),
|
|
getFinanceFeeSource: vi.fn(),
|
|
reviewFinanceFeeSource: vi.fn(),
|
|
editFinanceFeeSource: vi.fn(), submitFinanceFeeSource: vi.fn(), adjustFinanceFeeSource: vi.fn(), voidFinanceFeeSource: vi.fn(),
|
|
}))
|
|
|
|
class ResizeObserverStub implements ResizeObserver {
|
|
observe() {}
|
|
unobserve() {}
|
|
disconnect() {}
|
|
}
|
|
|
|
function createMemoryStorage(): Storage {
|
|
const values = new Map<string, string>()
|
|
return {
|
|
get length() { return values.size }, clear: () => values.clear(), getItem: (key) => values.get(key) ?? null,
|
|
key: (index) => [...values.keys()][index] ?? null, removeItem: (key) => values.delete(key), setItem: (key, value) => values.set(key, value),
|
|
}
|
|
}
|
|
|
|
const detail: FinanceFeeSourceDetail = {
|
|
poolItemId: '42', feeSourceId: '42', direction: 'RECEIVABLE', sourceType: 'INBOUND_FEE', sourceId: '81', sourceFeeId: '81', sourceVersion: 3,
|
|
sourceSystem: 'FIRSTLEG', sourceBusinessId: 'IB-91',
|
|
customerId: '10001', customerName: '客户A', supplierId: null, supplierName: '', orderId: null, inboundId: '91', inboundCode: 'IB-91', containerId: null, containerNo: '',
|
|
businessDate: '2026-08-11', feeItemName: '运费', quantity: 2, unitPrice: 50, amount: 100, currency: 'CNY', reviewStatus: 'PENDING',
|
|
sourceStatus: 'AVAILABLE', status: 'AVAILABLE', statementGenerated: false, operationVersion: 3, createdAt: '2026-08-11', operationLogs: [],
|
|
}
|
|
const feeSummary: FinanceFeeSourceSummary = {
|
|
poolItemId: '42', feeSourceId: '42', sourceSystem: 'FIRSTLEG', sourceType: 'INBOUND_FEE', sourceBusinessId: 'IB-91',
|
|
sourceId: '81', sourceVersion: 3, customerId: '10001', customerName: '客户A', direction: 'RECEIVABLE', supplierId: null,
|
|
supplierName: '', orderId: null, inboundId: '91', inboundCode: 'IB-91', containerId: null, containerNo: '', businessDate: '2026-08-11',
|
|
feeItemName: '运费', quantity: 2, unitPrice: 50, amount: 100, currency: 'CNY', reviewStatus: 'PENDING',
|
|
sourceStatus: 'AVAILABLE', statementGenerated: false, status: 'AVAILABLE', createdAt: '2026-08-11',
|
|
}
|
|
|
|
async function mountView(permissions: string[], initialPath = '/finance/customer-arap') {
|
|
const pinia = createPinia(); setActivePinia(pinia)
|
|
const auth = useAuthStore(); auth.token = 'token'; auth.permissions = permissions
|
|
useAccessPreviewStore().initializeAfterLogin()
|
|
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 },
|
|
{ path: '/finance/customer-statements', name: 'finance-customer-statements', component: { template: '<div>客户对账单</div>' } },
|
|
] })
|
|
await router.push(initialPath); await router.isReady()
|
|
const wrapper = mount(FinanceCustomerArapView, {
|
|
attachTo: document.body,
|
|
global: { plugins: [pinia, ElementPlus, router], stubs: { LayoutPreferenceList: true } },
|
|
})
|
|
await flushPromises()
|
|
return Object.assign(wrapper, { router })
|
|
}
|
|
|
|
async function loadRows(wrapper: Awaited<ReturnType<typeof mountView>>) {
|
|
const vm = wrapper.vm as unknown as { load(model: Record<string, string>): Promise<void> }
|
|
await vm.load({ customerId: '10001', direction: '', status: '', keyword: '' })
|
|
await flushPromises()
|
|
}
|
|
|
|
enableAutoUnmount(afterEach)
|
|
afterAll(() => vi.unstubAllGlobals())
|
|
|
|
describe('FinanceCustomerArapView G2 operations', () => {
|
|
beforeEach(() => {
|
|
vi.stubGlobal('ResizeObserver', ResizeObserverStub)
|
|
vi.stubGlobal('localStorage', createMemoryStorage())
|
|
vi.stubGlobal('sessionStorage', createMemoryStorage())
|
|
document.body.innerHTML = ''
|
|
const customer = { id: '10001', tenantName: '客户A', status: 'ACTIVE' } as PlatformTenantResource
|
|
vi.mocked(listPlatformTenants).mockReset().mockResolvedValue({ records: [customer], page: 1, size: 200, total: 1, pages: 1 })
|
|
vi.mocked(listFinanceFeeSources).mockReset().mockResolvedValue({ records: [feeSummary], total: 1, pages: 1 })
|
|
vi.mocked(getFinanceFeeSource).mockReset().mockResolvedValue(detail)
|
|
vi.mocked(reviewFinanceFeeSource).mockReset().mockResolvedValue({ ...detail, reviewStatus: 'APPROVED', operationVersion: 4 })
|
|
vi.mocked(editFinanceFeeSource).mockReset().mockResolvedValue({ ...detail, reviewStatus: 'DRAFT', operationVersion: 4 })
|
|
vi.mocked(submitFinanceFeeSource).mockReset().mockResolvedValue({ ...detail, reviewStatus: 'PENDING', operationVersion: 4 })
|
|
vi.mocked(adjustFinanceFeeSource).mockReset().mockResolvedValue({ ...detail, reviewStatus: 'DRAFT', operationVersion: 4 })
|
|
vi.mocked(voidFinanceFeeSource).mockReset().mockResolvedValue({ ...detail, sourceStatus: 'VOIDED', status: 'VOIDED', operationVersion: 4 })
|
|
vi.mocked(createPlatformExportTask).mockReset().mockResolvedValue({ id: 'exp-3', taskNo: 'EXP-3', taskName: '客户应收应付费用来源', status: 'PENDING' } as never)
|
|
vi.mocked(showExportTaskCreatedDialog).mockReset().mockResolvedValue(false)
|
|
})
|
|
|
|
it.each([
|
|
['DRAFT', ['详情', '编辑', '提交审核', '更多'], true],
|
|
['APPROVED', ['详情', '调整', '作废'], false],
|
|
['PENDING', ['详情', '作废'], false],
|
|
])('keeps fee-source actions reachable at the three-action boundary: %s', async (reviewStatus, labels, overflow) => {
|
|
const row = { ...feeSummary, sourceSystem: 'FINANCE', sourceType: 'MANUAL', reviewStatus } as FinanceFeeSourceSummary
|
|
vi.mocked(listFinanceFeeSources).mockResolvedValue({ records: [row], total: 1, pages: 1 })
|
|
const wrapper = await mountView(['platform:finance:customer-arap:list', 'platform:finance:fee-source:edit'])
|
|
await loadRows(wrapper)
|
|
const actions = wrapper.get('.el-table__body-wrapper .density-actions')
|
|
expect(actions.findAll('button').map(button => button.text().trim())).toEqual(labels)
|
|
expect(actions.find('.el-dropdown').exists()).toBe(overflow)
|
|
if (overflow) {
|
|
const trigger = actions.get('button[title^="更多费用来源操作"]')
|
|
expect(trigger.text()).toBe('更多')
|
|
await trigger.trigger('click')
|
|
await flushPromises()
|
|
const item = [...document.querySelectorAll<HTMLElement>('.el-dropdown-menu__item')].find(item => item.textContent?.trim() === '作废')
|
|
expect(item).toBeTruthy()
|
|
vi.mocked(getFinanceFeeSource).mockResolvedValueOnce({ ...detail, ...row })
|
|
item!.click()
|
|
await flushPromises()
|
|
expect(document.body.textContent).toContain('作废费用')
|
|
expect(voidFinanceFeeSource).not.toHaveBeenCalled()
|
|
}
|
|
})
|
|
|
|
it.each([['PAYABLE_BILLED', '已生成应付单'], ['PARTIALLY_PAID', '部分付款'], ['PAID', '已付款']])(
|
|
'shows payable progress and prevents editing occupied or paid sources: %s', async (status, label) => {
|
|
const row = { ...feeSummary, direction: 'PAYABLE', sourceSystem: 'FINANCE', sourceType: 'MANUAL',
|
|
reviewStatus: 'APPROVED', sourceStatus: status, status, statementGenerated: false } as FinanceFeeSourceSummary
|
|
vi.mocked(listFinanceFeeSources).mockResolvedValue({ records: [row], total: 1, pages: 1 })
|
|
const wrapper = await mountView(['platform:finance:customer-arap:list', 'platform:finance:fee-source:edit'])
|
|
await loadRows(wrapper)
|
|
const body = wrapper.get('.el-table__body-wrapper')
|
|
expect(body.text()).toContain(label)
|
|
expect(body.text()).not.toContain('可结算')
|
|
expect(body.text()).not.toContain('未生成')
|
|
expect(body.findAll('button').map(button => button.text().trim())).toEqual(['详情'])
|
|
vi.mocked(getFinanceFeeSource).mockResolvedValueOnce({ ...detail, ...row })
|
|
await body.get('button').trigger('click')
|
|
await flushPromises()
|
|
expect(wrapper.get('.arap-metrics').text()).toContain(label)
|
|
expect(wrapper.get('.arap-information').text()).toContain('不适用')
|
|
},
|
|
)
|
|
|
|
it('shows detail from the list permission but keeps G2 operations independent', async () => {
|
|
const wrapper = await mountView(['platform:finance:customer-arap:list'])
|
|
await loadRows(wrapper)
|
|
|
|
const buttonLabels = wrapper.findAll('button').map((button) => button.text())
|
|
expect(buttonLabels).not.toContain('审核')
|
|
expect(buttonLabels).not.toContain('结算')
|
|
const vm = wrapper.vm as unknown as { openDetail(row: Record<string, unknown>): Promise<void> }
|
|
await vm.openDetail({ id: 'RECEIVABLE:42', feeSourceId: '42' })
|
|
expect(getFinanceFeeSource).toHaveBeenCalledWith('42')
|
|
expect(reviewFinanceFeeSource).not.toHaveBeenCalled()
|
|
expect(editFinanceFeeSource).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('treats inbound and container fee types as Firstleg sources when sourceSystem is missing', async () => {
|
|
const legacyFirstleg = { ...feeSummary, sourceSystem: '', sourceType: 'CONTAINER_FEE', reviewStatus: 'APPROVED' as const }
|
|
vi.mocked(listFinanceFeeSources).mockResolvedValue({ records: [legacyFirstleg], total: 1, pages: 1 })
|
|
const wrapper = await mountView(['platform:finance:customer-arap:list', 'platform:finance:fee-source:edit'])
|
|
await loadRows(wrapper)
|
|
|
|
const buttonLabels = wrapper.findAll('button').map((button) => button.text())
|
|
expect(buttonLabels).not.toContain('编辑')
|
|
expect(buttonLabels).not.toContain('调整')
|
|
const vm = wrapper.vm as unknown as { openMutation(row: Record<string, unknown>, mode: 'ADJUST'): Promise<void> }
|
|
await vm.openMutation(legacyFirstleg as unknown as Record<string, unknown>, 'ADJUST')
|
|
expect(getFinanceFeeSource).not.toHaveBeenCalled()
|
|
expect(adjustFinanceFeeSource).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('closes the customer dropdown when the surrounding page is scrolled', async () => {
|
|
const source = readFileSync(resolve(process.cwd(), 'src/modules/finance/pages/FinanceCustomerArapView.vue'), 'utf8')
|
|
expect(source).toContain("document.addEventListener('scroll', closeCustomerSelectOnScroll, true)")
|
|
expect(source).toContain('onVisibleChange: onCustomerSelectVisible')
|
|
expect(source).not.toContain('input?.blur()')
|
|
expect(source).toContain("class: 'finance-customer-select'")
|
|
})
|
|
|
|
it('uses sourceSystem to suppress Firstleg editing when the source type is not in the legacy fallback list', async () => {
|
|
const firstlegOtherType = { ...feeSummary, sourceType: 'ORDER_FEE', reviewStatus: 'DRAFT' as const }
|
|
vi.mocked(listFinanceFeeSources).mockResolvedValue({ records: [firstlegOtherType], total: 1, pages: 1 })
|
|
const wrapper = await mountView(['platform:finance:customer-arap:list', 'platform:finance:fee-source:edit'])
|
|
await loadRows(wrapper)
|
|
|
|
const buttonLabels = wrapper.findAll('button').map((button) => button.text())
|
|
expect(buttonLabels).not.toContain('缂栬緫')
|
|
const vm = wrapper.vm as unknown as { openMutation(row: Record<string, unknown>, mode: 'EDIT'): Promise<void> }
|
|
await vm.openMutation(firstlegOtherType as unknown as Record<string, unknown>, 'EDIT')
|
|
expect(getFinanceFeeSource).not.toHaveBeenCalled()
|
|
expect(editFinanceFeeSource).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('loads all customers by default and keeps explicit customer filtering', async () => {
|
|
const wrapper = await mountView(['platform:finance:customer-arap:list', 'platform:customer:list'])
|
|
expect(listFinanceFeeSources).toHaveBeenCalledWith(expect.objectContaining({ page: 1, size: 20, customerId: undefined, direction: undefined }))
|
|
const vm = wrapper.vm as unknown as { load(model: Record<string, string>): Promise<void> }
|
|
await vm.load({ customerId: '10001', direction: 'RECEIVABLE', status: '', keyword: '' })
|
|
expect(listFinanceFeeSources).toHaveBeenLastCalledWith(expect.objectContaining({ customerId: '10001', direction: 'RECEIVABLE' }))
|
|
expect(wrapper.text()).not.toContain('全客户查询暂不可用')
|
|
})
|
|
|
|
it('maps each all-customer fee row with its backend customerId', async () => {
|
|
const customerB = { id: '10002', tenantName: '客户B', status: 'ACTIVE' } as PlatformTenantResource
|
|
vi.mocked(listPlatformTenants).mockResolvedValue({ records: [{ id: '10001', tenantName: '客户A', status: 'ACTIVE' } as PlatformTenantResource, customerB], page: 1, size: 200, total: 2, pages: 1 })
|
|
vi.mocked(listFinanceFeeSources).mockResolvedValue({ records: [
|
|
feeSummary,
|
|
{ ...feeSummary, poolItemId: '43', feeSourceId: '43', sourceBusinessId: 'CONT-43', customerId: '10002', customerName: '客户B', direction: 'PAYABLE', sourceType: 'CONTAINER_FEE', sourceId: '82', orderId: '11', inboundId: null, inboundCode: '', containerId: '31', containerNo: 'CONT-43', supplierId: '71', supplierName: '供应商A', feeItemName: '拖车费', amount: 200 },
|
|
], total: 2, pages: 1 })
|
|
const wrapper = await mountView(['platform:finance:customer-arap:list'])
|
|
const vm = wrapper.vm as unknown as { load(model: Record<string, string>): Promise<void> }
|
|
await vm.load({ customerId: '', direction: '', status: '', keyword: '' })
|
|
await flushPromises()
|
|
expect(wrapper.text()).toContain('客户A')
|
|
expect(wrapper.text()).toContain('客户B')
|
|
})
|
|
|
|
it('loads detail and submits review with the returned version', async () => {
|
|
const wrapper = await mountView(['platform:finance:customer-arap:list', 'platform:finance:fee-source:review'])
|
|
await loadRows(wrapper)
|
|
const vm = wrapper.vm as unknown as {
|
|
openDetail(row: Record<string, unknown>): Promise<void>
|
|
openReview(row: Record<string, unknown>): Promise<void>
|
|
submitReview(): Promise<void>
|
|
reviewForm: { action: 'APPROVE' | 'REJECT'; reason: string }
|
|
}
|
|
const row = { id: 'RECEIVABLE:42', feeSourceId: '42' }
|
|
|
|
await vm.openDetail(row); await flushPromises()
|
|
expect(getFinanceFeeSource).toHaveBeenCalledWith('42')
|
|
expect(document.body.textContent).toContain('IB-91')
|
|
expect(document.body.textContent).toContain('100.00')
|
|
expect(document.body.textContent).toContain('CNY')
|
|
|
|
await vm.openReview(row); vm.reviewForm.action = 'APPROVE'; vm.reviewForm.reason = '核对无误'; await vm.submitReview()
|
|
expect(reviewFinanceFeeSource).toHaveBeenCalledWith('42', { action: 'APPROVE', reason: '核对无误', expectedVersion: 3 })
|
|
})
|
|
|
|
it('renders request and trace ids when detail loading fails', async () => {
|
|
vi.mocked(getFinanceFeeSource).mockRejectedValueOnce(new ApiResponseError('费用来源不可用', 'FIN-FEE-404', 404, undefined, 'req-g2-error', 'trace-g2-error'))
|
|
const wrapper = await mountView(['platform:finance:customer-arap:list'])
|
|
await loadRows(wrapper)
|
|
const vm = wrapper.vm as unknown as { openDetail(row: Record<string, unknown>): Promise<void> }
|
|
|
|
await vm.openDetail({ id: 'RECEIVABLE:42', feeSourceId: '42' }); await flushPromises()
|
|
|
|
expect(document.body.textContent).toContain('费用来源不可用')
|
|
expect(document.body.textContent).toContain('req-g2-error')
|
|
expect(document.body.textContent).toContain('trace-g2-error')
|
|
})
|
|
|
|
it('restores the independent detail route from its fee source id', async () => {
|
|
const wrapper = await mountView(['platform:finance:customer-arap:list'], '/finance/customer-arap/42')
|
|
expect(getFinanceFeeSource).toHaveBeenCalledWith('42')
|
|
expect(wrapper.get('.density-detail-title').text()).toContain('运费')
|
|
expect(wrapper.text()).toContain('IB-91')
|
|
expect(wrapper.router.currentRoute.value.name).toBe('finance-customer-arap-detail')
|
|
})
|
|
|
|
it('uses the desktop detail layout for summary, source information, and operation history', async () => {
|
|
const wrapper = await mountView(['platform:finance:customer-arap:list'], '/finance/customer-arap/42')
|
|
expect(wrapper.find('.finance-arap-detail__layout').exists()).toBe(true)
|
|
expect(wrapper.find('.finance-arap-detail__surface').exists()).toBe(true)
|
|
expect(wrapper.find('.detail-page-layout__header').exists()).toBe(true)
|
|
expect(wrapper.find('.detail-page-layout__summary').exists()).toBe(true)
|
|
expect(wrapper.find('.arap-metrics').exists()).toBe(true)
|
|
expect(wrapper.findAll('.arap-metric')).toHaveLength(4)
|
|
expect(wrapper.find('.arap-information').text()).toContain('单据信息')
|
|
expect(wrapper.find('.arap-settlement').exists()).toBe(false)
|
|
expect(wrapper.find('.arap-information').text()).toContain('已生成对账单')
|
|
expect(wrapper.find('.arap-information').text()).toContain('审核原因')
|
|
expect(wrapper.find('.operation-log').text()).toContain('操作记录')
|
|
expect(wrapper.find('.arap-information').text()).toContain('业务日期')
|
|
expect(wrapper.find('.arap-information').text()).not.toContain('T00:00:00')
|
|
})
|
|
|
|
it('keeps the fee-source detail readable across the full workspace width', () => {
|
|
const page = readFileSync(resolve(process.cwd(), 'src/modules/finance/pages/FinanceCustomerArapView.vue'), 'utf8')
|
|
expect(page).not.toContain('width: min(100%, 1920px)')
|
|
expect(page).toContain('<el-descriptions border :column="3" class="arap-detail density-descriptions">')
|
|
expect(page).not.toContain('arap-detail-grid')
|
|
expect(page).toContain('finance-density-detail')
|
|
expect(page).not.toContain('max-width: 520px')
|
|
})
|
|
|
|
it('renders permitted fee-source row operations through the shared row-actions component', () => {
|
|
const page = readFileSync(resolve(process.cwd(), 'src/modules/finance/pages/FinanceCustomerArapView.vue'), 'utf8')
|
|
expect(page).toContain("import RowActions, { type RowActionItem } from '@/shared/components/RowActions.vue'")
|
|
expect(page).toContain("{ key: 'detail', label: '详情', priority: 300")
|
|
expect(page).toContain("{ key: 'submit', label: '提交审核', priority: 200")
|
|
expect(page).toContain("{ key: 'adjust', label: '调整', priority: 150")
|
|
expect(page).toContain("{ key: 'void', label: '作废', tone: 'danger'")
|
|
expect(page).toContain('!isUpstreamSource(row)')
|
|
expect(page).toContain('!isUpstreamSource(detail)')
|
|
expect(page).toContain("['INBOUND_FEE', 'CONTAINER_FEE'].includes(type)")
|
|
expect(page).toContain("=== 'LASTLEG'")
|
|
expect(page).toContain('<RowActions text-actions v-if="row.feeSourceId" :actions="sourceActions(row as ArapRow)"')
|
|
})
|
|
|
|
it('resolves blank detail customer names and keeps source labels business-facing', async () => {
|
|
vi.mocked(getFinanceFeeSource).mockResolvedValueOnce({ ...detail, customerName: '', sourceType: 'NEW_SOURCE' })
|
|
const wrapper = await mountView(['platform:finance:customer-arap:list'], '/finance/customer-arap/42')
|
|
expect(wrapper.get('.density-detail-title').text()).toContain('运费')
|
|
expect(wrapper.get('.finance-detail-context').text()).toBe('客户A')
|
|
expect(wrapper.text()).toContain('其他费用来源')
|
|
expect(wrapper.text()).not.toContain('其他来源')
|
|
expect(wrapper.text()).not.toContain('NEW_SOURCE')
|
|
expect(wrapper.text()).not.toContain('42 ·')
|
|
})
|
|
|
|
it('hides technical source identifiers and versions from list rows', async () => {
|
|
vi.mocked(listFinanceFeeSources).mockResolvedValue({ records: [{
|
|
...feeSummary, poolItemId: '358', feeSourceId: '358', sourceType: 'CONTAINER_FEE', sourceBusinessId: 'CONT-20260814', sourceId: '2088285842638942210', sourceVersion: 1,
|
|
direction: 'PAYABLE', orderId: null, inboundId: null, inboundCode: '', containerId: '2088285834627821569', containerNo: 'CONT-20260814', supplierId: '71', supplierName: '供应商A',
|
|
feeItemName: 'ADMIN-API-20260814152244615-整柜成本', amount: 300, businessDate: '2026-08-14', createdAt: '2026-08-14',
|
|
}], total: 1, pages: 1 })
|
|
const wrapper = await mountView(['platform:finance:customer-arap:list'])
|
|
await (wrapper.vm as unknown as { load(model: Record<string, string>): Promise<void> }).load({ customerId: '', direction: '', status: '', keyword: '' })
|
|
const text = wrapper.text()
|
|
expect(text).toContain('整柜成本')
|
|
expect(text).toContain('柜次 CONT-20260814')
|
|
expect(text).toContain('供应商A')
|
|
expect(text).toContain('2026/8/14')
|
|
expect(text).not.toContain('PAYABLE:358')
|
|
expect(text).not.toContain('2088285842638942210')
|
|
expect(text).not.toContain('/v1')
|
|
expect(text).not.toContain('CONTAINER_FEE')
|
|
expect(text).not.toContain('ADMIN-API-20260814152244615')
|
|
})
|
|
|
|
it.each(['10001', '客户 10001', '客户10001'])('never displays customer id placeholder %s as a customer name', async (customerName) => {
|
|
vi.mocked(listPlatformTenants).mockResolvedValueOnce({ records: [], page: 1, size: 200, total: 0, pages: 1 })
|
|
vi.mocked(getFinanceFeeSource).mockResolvedValueOnce({ ...detail, customerName })
|
|
const wrapper = await mountView(['platform:finance:customer-arap:list'], '/finance/customer-arap/42')
|
|
expect(wrapper.text()).toContain('客户名称暂不可用')
|
|
expect(wrapper.text()).not.toContain(customerName)
|
|
})
|
|
|
|
it('continues selected eligible receivables in the customer statement generation center', async () => {
|
|
const wrapper = await mountView(['platform:finance:customer-arap:list', 'platform:finance:reconciliation:create'])
|
|
await loadRows(wrapper)
|
|
const vm = wrapper.vm as unknown as {
|
|
selectedRows: Array<Record<string, unknown>>
|
|
continueSelectedGeneration(): Promise<void>
|
|
}
|
|
vm.selectedRows = [{ id: 'RECEIVABLE:42', feeSourceId: '42', customerId: '10001', direction: 'RECEIVABLE', reviewStatus: 'APPROVED', status: 'AVAILABLE', statementGenerated: false, amount: 100, businessDate: '2026-08-11' }]
|
|
await vm.continueSelectedGeneration()
|
|
expect(wrapper.router.currentRoute.value).toMatchObject({
|
|
name: 'finance-customer-statements',
|
|
query: { createMode: 'SELECTED', customerId: '10001', feeSourceIds: '42', periodStart: '2026-08-11', periodEnd: '2026-08-11' },
|
|
})
|
|
})
|
|
|
|
it('creates a fee-source export task with the complete active filters and typed booleans', async () => {
|
|
const wrapper = await mountView(['platform:finance:customer-arap:list', 'platform:finance:customer-arap:export', 'platform:download:list'])
|
|
const vm = wrapper.vm as unknown as { filters: Record<string, unknown>; exportFeeSources(): Promise<void> }
|
|
Object.assign(vm.filters, {
|
|
customerId: '10001', direction: 'RECEIVABLE', reviewStatus: 'APPROVED',
|
|
statementGenerated: 'false', businessRange: ['2026-08-01', '2026-08-31'], keyword: ' 运费 ',
|
|
})
|
|
await vm.exportFeeSources()
|
|
expect(createPlatformExportTask).toHaveBeenCalledWith({
|
|
exportType: 'FINANCE_FEE_SOURCE_LIST', format: 'XLSX', taskName: '客户应收应付费用来源',
|
|
parameters: {
|
|
customerId: '10001', direction: 'RECEIVABLE', reviewStatus: 'APPROVED',
|
|
statementGenerated: false, businessDateFrom: '2026-08-01', businessDateTo: '2026-08-31', keyword: '运费',
|
|
},
|
|
})
|
|
expect(showExportTaskCreatedDialog).toHaveBeenCalledWith({ taskName: '客户应收应付费用来源', taskNo: 'EXP-3', dense: true })
|
|
})
|
|
|
|
it('does not expose fee-source export without download-center access', async () => {
|
|
const wrapper = await mountView(['platform:finance:customer-arap:list', 'platform:finance:customer-arap:export'])
|
|
expect(wrapper.findAll('button').map((button) => button.text())).not.toContain('导出Excel')
|
|
expect(createPlatformExportTask).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('omits every empty fee-source filter instead of exporting the current table page', async () => {
|
|
const wrapper = await mountView(['platform:finance:customer-arap:list', 'platform:finance:customer-arap:export', 'platform:download:list'])
|
|
await (wrapper.vm as unknown as { exportFeeSources(): Promise<void> }).exportFeeSources()
|
|
expect(createPlatformExportTask).toHaveBeenCalledWith({
|
|
exportType: 'FINANCE_FEE_SOURCE_LIST', format: 'XLSX', taskName: '客户应收应付费用来源', parameters: {},
|
|
})
|
|
})
|
|
})
|