优化
This commit is contained in:
@@ -10,7 +10,7 @@ import { ApiResponseError } from '@/shared/api/http'
|
||||
import {
|
||||
adjustInboundFeeDetail, createInboundFeeDetail, deleteInboundFeeDetail,
|
||||
listInboundFeeDetails, listInboundFeeItemOptions, listInboundFeeSupplierOptions,
|
||||
reviewInboundFeeDetail, updateInboundFeeDetail, voidInboundFeeDetail,
|
||||
reviewInboundFeeDetail, updateInboundFeeDetail, uploadInboundFeePaymentVoucher, voidInboundFeeDetail,
|
||||
} from '@/modules/unified-orders/api/inboundFeeDetails'
|
||||
import InboundFeeDetailManager from './InboundFeeDetailManager.vue'
|
||||
import type { InboundFeeDetail } from '@/modules/unified-orders/api/inboundFeeDetails'
|
||||
@@ -18,12 +18,13 @@ import type { InboundFeeDetail } from '@/modules/unified-orders/api/inboundFeeDe
|
||||
vi.mock('@/modules/unified-orders/api/inboundFeeDetails', () => ({
|
||||
listInboundFeeDetails: vi.fn(), listInboundFeeItemOptions: vi.fn(), listInboundFeeSupplierOptions: vi.fn(),
|
||||
createInboundFeeDetail: vi.fn(), updateInboundFeeDetail: vi.fn(), deleteInboundFeeDetail: vi.fn(),
|
||||
reviewInboundFeeDetail: vi.fn(), adjustInboundFeeDetail: vi.fn(), voidInboundFeeDetail: vi.fn(),
|
||||
reviewInboundFeeDetail: vi.fn(), adjustInboundFeeDetail: vi.fn(), uploadInboundFeePaymentVoucher: vi.fn(), voidInboundFeeDetail: vi.fn(),
|
||||
}))
|
||||
|
||||
const pending: InboundFeeDetail = {
|
||||
id: '11', inboundId: '88', feeItemId: '31', sourceType: 'MANUAL', sourceFeeVersion: 1,
|
||||
inboundCode: 'RK-88', direction: 'RECEIVABLE', supplierId: null, supplierName: null, supplierCategory: null,
|
||||
settlementMode: null, paidAt: null, paymentMethod: null, paymentReferenceNo: null, paymentVoucherFileIds: null,
|
||||
businessDate: '2026-08-12', customerName: '测试客户', feeTypeNameSnapshot: '操作费',
|
||||
unitPrice: 10, currency: 'CNY', amount: 100, remark: 'marker', reviewStatus: 'PENDING',
|
||||
reviewReason: null, reviewerName: null, reviewedAt: null, publicationStatus: 'DRAFT',
|
||||
@@ -49,6 +50,7 @@ describe('InboundFeeDetailManager', () => {
|
||||
vi.mocked(createInboundFeeDetail).mockResolvedValue(pending)
|
||||
vi.mocked(updateInboundFeeDetail).mockResolvedValue(pending)
|
||||
vi.mocked(adjustInboundFeeDetail).mockResolvedValue({ ...approved, reviewStatus: 'PENDING' })
|
||||
vi.mocked(uploadInboundFeePaymentVoucher).mockResolvedValue({ fileId: '9001', fileName: '付款凭证.pdf' })
|
||||
vi.mocked(reviewInboundFeeDetail).mockResolvedValue(null)
|
||||
vi.mocked(deleteInboundFeeDetail).mockResolvedValue(null)
|
||||
vi.mocked(voidInboundFeeDetail).mockResolvedValue(null)
|
||||
@@ -81,6 +83,13 @@ describe('InboundFeeDetailManager', () => {
|
||||
for (const action of ['编辑', '审核', '驳回', '调整', '作废', '删除']) expect(buttons).not.toContain(action)
|
||||
})
|
||||
|
||||
it('shows payable settlement meaning and a DIRECT_PAID summary without changing fee direction', async () => {
|
||||
vi.mocked(listInboundFeeDetails).mockResolvedValue([{ ...pending, direction: 'PAYABLE', supplierId: '41', supplierName: '测试供应商', settlementMode: 'DIRECT_PAID', paidAt: '2026-08-12T10:00:00', paymentMethod: 'BANK_TRANSFER', paymentReferenceNo: 'BANK-11', paymentVoucherFileIds: ['9001'] }])
|
||||
const wrapper = mountManager(['firstleg:inbound:fee-detail:view'])
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('应付'); expect(wrapper.text()).toContain('已付成本'); expect(wrapper.text()).toContain('银行转账'); expect(wrapper.text()).toContain('BANK-11')
|
||||
})
|
||||
|
||||
it('does not request or expose fee rows without view permission', async () => {
|
||||
mountManager([]); await flushPromises()
|
||||
expect(listInboundFeeDetails).not.toHaveBeenCalled()
|
||||
@@ -163,7 +172,7 @@ describe('InboundFeeDetailManager', () => {
|
||||
})
|
||||
|
||||
it('keeps fee items usable but blocks payable save when the supplier request fails', async () => {
|
||||
vi.mocked(listInboundFeeDetails).mockResolvedValue([{ ...pending, direction: 'PAYABLE', supplierId: '41', supplierName: '测试供应商', supplierCategory: 'TRUCKING' }])
|
||||
vi.mocked(listInboundFeeDetails).mockResolvedValue([{ ...pending, direction: 'PAYABLE', supplierId: '41', supplierName: '测试供应商', supplierCategory: 'TRUCKING', settlementMode: 'ON_ACCOUNT' }])
|
||||
vi.mocked(listInboundFeeSupplierOptions).mockRejectedValue(new ApiResponseError('供应商不可用', 'A0403', 403, null, 'request-supplier', 'trace-supplier'))
|
||||
const wrapper = mountManager([
|
||||
'firstleg:inbound:fee-detail:view', 'firstleg:inbound:fee-detail:edit',
|
||||
@@ -191,11 +200,45 @@ describe('InboundFeeDetailManager', () => {
|
||||
await flushPromises()
|
||||
await wrapper.findAll('button').find((button) => button.text().includes('新增费用'))?.trigger('click')
|
||||
await flushPromises()
|
||||
const state = (wrapper.vm.$ as unknown as { setupState: { save(): Promise<void>; form: { direction: string; feeItemId: string; supplierId: string | null; amount: number } } }).setupState
|
||||
state.form.direction = 'PAYABLE'; state.form.feeItemId = '31'; await flushPromises(); state.form.supplierId = '41'; state.form.amount = 100
|
||||
const state = (wrapper.vm.$ as unknown as { setupState: { save(): Promise<void>; form: { direction: string; feeItemId: string; supplierId: string | null; settlementMode: string | null; amount: number } } }).setupState
|
||||
state.form.direction = 'PAYABLE'; state.form.feeItemId = '31'; await flushPromises(); state.form.supplierId = '41'; state.form.settlementMode = 'ON_ACCOUNT'; state.form.amount = 100
|
||||
await state.save(); await flushPromises()
|
||||
expect(listInboundFeeSupplierOptions).toHaveBeenCalledWith(null)
|
||||
expect(createInboundFeeDetail).toHaveBeenCalledWith('100', '88', expect.objectContaining({ direction: 'PAYABLE', supplierId: '41' }))
|
||||
expect(createInboundFeeDetail).toHaveBeenCalledWith('100', '88', expect.objectContaining({ direction: 'PAYABLE', supplierId: '41', settlementMode: 'ON_ACCOUNT', paidAt: null, paymentMethod: null, paymentReferenceNo: null, paymentVoucherFileIds: null }))
|
||||
})
|
||||
|
||||
it('clears settlement and paid fields when switching to RECEIVABLE or ON_ACCOUNT', async () => {
|
||||
const wrapper = mountManager(['firstleg:inbound:fee-detail:view', 'firstleg:inbound:fee-detail:edit', 'firstleg:fee-item:view', 'platform:supplier:list'])
|
||||
await flushPromises(); await wrapper.findAll('button').find((button) => button.text().includes('新增费用'))?.trigger('click'); await flushPromises()
|
||||
const state = (wrapper.vm.$ as unknown as { setupState: { form: { direction: string; settlementMode: string | null; paidAt: string | null; paymentMethod: string | null; paymentReferenceNo: string | null; paymentVoucherFileIds: string[] | null } } }).setupState
|
||||
Object.assign(state.form, { direction: 'PAYABLE', settlementMode: 'DIRECT_PAID', paidAt: '2026-08-12T10:00:00', paymentMethod: 'CASH', paymentReferenceNo: 'CASH-1', paymentVoucherFileIds: ['9001'] }); await flushPromises()
|
||||
state.form.settlementMode = 'ON_ACCOUNT'; await flushPromises()
|
||||
expect(state.form).toMatchObject({ settlementMode: 'ON_ACCOUNT', paidAt: null, paymentMethod: null, paymentReferenceNo: null, paymentVoucherFileIds: null })
|
||||
state.form.direction = 'RECEIVABLE'; await flushPromises()
|
||||
expect(state.form).toMatchObject({ direction: 'RECEIVABLE', settlementMode: null, paidAt: null, paymentMethod: null, paymentReferenceNo: null, paymentVoucherFileIds: null })
|
||||
})
|
||||
|
||||
it('requires complete DIRECT_PAID evidence and submits uploaded voucher IDs only after scoped upload succeeds', async () => {
|
||||
const wrapper = mountManager(['firstleg:inbound:fee-detail:view', 'firstleg:inbound:fee-detail:edit', 'firstleg:fee-item:view', 'platform:supplier:list'])
|
||||
await flushPromises(); await wrapper.findAll('button').find((button) => button.text().includes('新增费用'))?.trigger('click'); await flushPromises()
|
||||
const state = (wrapper.vm.$ as unknown as { setupState: { save(): Promise<void>; selectVoucher(event: Event): Promise<void>; form: { direction: string; feeItemId: string; supplierId: string | null; settlementMode: string | null; paidAt: string | null; paymentMethod: string | null; paymentReferenceNo: string | null; amount: number } } }).setupState
|
||||
Object.assign(state.form, { direction: 'PAYABLE', feeItemId: '31', supplierId: '41', settlementMode: 'DIRECT_PAID', paidAt: '2026-08-12T10:00:00', paymentMethod: 'BANK_TRANSFER', paymentReferenceNo: null, amount: 100 }); await flushPromises()
|
||||
await state.save(); expect(createInboundFeeDetail).not.toHaveBeenCalled()
|
||||
const input = document.createElement('input'); Object.defineProperty(input, 'files', { value: [new File(['x'], '付款凭证.pdf')] })
|
||||
await state.selectVoucher({ target: input } as unknown as Event); await state.save()
|
||||
expect(uploadInboundFeePaymentVoucher).toHaveBeenCalledWith('100', expect.objectContaining({ name: '付款凭证.pdf' }))
|
||||
expect(createInboundFeeDetail).toHaveBeenCalledWith('100', '88', expect.objectContaining({ settlementMode: 'DIRECT_PAID', paymentMethod: 'BANK_TRANSFER', paymentVoucherFileIds: ['9001'] }))
|
||||
})
|
||||
|
||||
it('keeps DIRECT_PAID save closed and exposes feedback when voucher upload fails', async () => {
|
||||
vi.mocked(uploadInboundFeePaymentVoucher).mockRejectedValue(new ApiResponseError('File服务不可用', 'A0503', 503, null, 'request-file', 'trace-file'))
|
||||
const wrapper = mountManager(['firstleg:inbound:fee-detail:view', 'firstleg:inbound:fee-detail:edit', 'firstleg:fee-item:view', 'platform:supplier:list'])
|
||||
await flushPromises(); await wrapper.findAll('button').find((button) => button.text().includes('新增费用'))?.trigger('click'); await flushPromises()
|
||||
const state = (wrapper.vm.$ as unknown as { setupState: { save(): Promise<void>; selectVoucher(event: Event): Promise<void>; form: { direction: string; feeItemId: string; supplierId: string | null; settlementMode: string | null; paidAt: string | null; paymentMethod: string | null; paymentReferenceNo: string | null; amount: number } } }).setupState
|
||||
Object.assign(state.form, { direction: 'PAYABLE', feeItemId: '31', supplierId: '41', settlementMode: 'DIRECT_PAID', paidAt: '2026-08-12T10:00:00', paymentMethod: 'ONLINE', paymentReferenceNo: 'ONLINE-1', amount: 100 }); await flushPromises()
|
||||
const input = document.createElement('input'); Object.defineProperty(input, 'files', { value: [new File(['x'], '失败凭证.png')] })
|
||||
await state.selectVoucher({ target: input } as unknown as Event); await state.save(); await flushPromises()
|
||||
expect(createInboundFeeDetail).not.toHaveBeenCalled(); expect(document.body.textContent).toContain('request-file'); expect(document.body.textContent).toContain('trace-file')
|
||||
})
|
||||
|
||||
it('refreshes fee rows and the order detail after every successful write', () => {
|
||||
|
||||
Reference in New Issue
Block a user