This commit is contained in:
2026-10-19 17:56:31 +08:00
parent 5a46198bf0
commit 5c28cd7c79
84 changed files with 3356 additions and 3630 deletions
@@ -42,10 +42,106 @@ function mountManager(permissions: string[]) {
}
describe('InboundFeeDetailManager', () => {
it.each([80, null])('refreshes the default price on reopening the editor: %s', async (defaultPrice) => {
const item = { id: '31', feeTypeName: '操作费', feeCode: 'OPERATION', defaultPrice: 60, supplierCategory: null, warehouseReceivable: true, warehousePayable: false }
vi.mocked(listInboundFeeItemOptions).mockResolvedValue([item])
const wrapper = mountManager(['firstleg:inbound:fee-detail:view', 'firstleg:inbound:fee-detail:edit', 'firstleg:fee-item:view'])
await flushPromises()
const state = (wrapper.vm.$ as unknown as { setupState: {
openEditor(mode: string, row?: InboundFeeDetail): Promise<void>
editorOpen: boolean
form: { feeItemId: string; unitPrice: number; amount: number }
} }).setupState
await state.openEditor('create'); await flushPromises()
state.editorOpen = false; await flushPromises()
vi.mocked(listInboundFeeItemOptions).mockResolvedValue([{ ...item, defaultPrice }])
await state.openEditor('create'); await flushPromises()
const select = wrapper.findAllComponents({ name: 'ElFormItem' }).find((field) => field.props('prop') === 'feeItemId')!.findComponent({ name: 'ElSelect' })
select.vm.$emit('update:modelValue', '31'); select.vm.$emit('change', '31')
await flushPromises()
expect(state.form).toMatchObject({ unitPrice: defaultPrice ?? 0, amount: defaultPrice ?? 0 })
expect(listInboundFeeItemOptions).toHaveBeenCalledTimes(2)
state.editorOpen = false; await flushPromises()
await state.openEditor('edit', pending); await flushPromises()
expect(state.form).toMatchObject({ unitPrice: 10, amount: 100 })
})
it('clears cached choices and blocks saving while refresh is pending or has failed', async () => {
const wrapper = mountManager(['firstleg:inbound:fee-detail:view', 'firstleg:inbound:fee-detail:edit', 'firstleg:fee-item:view'])
await flushPromises()
const state = (wrapper.vm.$ as unknown as { setupState: {
openEditor(mode: string, row?: InboundFeeDetail): Promise<void>
save(): Promise<void>
editorOpen: boolean
feeItems: unknown[]
feeItemsLoading: boolean
feeItemsError: { message: string } | null
actionError: { message: string } | null
form: { unitPrice: number; amount: number }
} }).setupState
await state.openEditor('edit', pending); await flushPromises()
expect(state.feeItems).toHaveLength(1)
state.editorOpen = false; await flushPromises()
let rejectRefresh!: (reason: Error) => void
vi.mocked(listInboundFeeItemOptions).mockImplementationOnce(() => new Promise((_, reject) => { rejectRefresh = reject }))
const opening = state.openEditor('edit', pending)
await flushPromises()
expect(state.feeItems).toEqual([])
expect(state.feeItemsLoading).toBe(true)
const select = wrapper.findAllComponents({ name: 'ElFormItem' }).find((field) => field.props('prop') === 'feeItemId')!.findComponent({ name: 'ElSelect' })
expect(select.props('disabled')).toBe(true)
await state.save()
expect(state.actionError?.message).toContain('正在加载')
rejectRefresh(new Error('加载失败'))
await opening; await flushPromises()
expect(state.feeItems).toEqual([])
expect(state.feeItemsError).not.toBeNull()
expect(state.form).toMatchObject({ unitPrice: 10, amount: 100 })
await state.save()
expect(updateInboundFeeDetail).not.toHaveBeenCalled()
state.feeItemsError = null
await state.save()
expect(updateInboundFeeDetail).not.toHaveBeenCalled()
expect(state.actionError?.message).toContain('不可用')
})
it('prefills only on user selection and saves a manually overridden price', async () => {
vi.mocked(listInboundFeeItemOptions).mockResolvedValue([
{ id: '31', feeTypeName: '操作费', feeCode: 'OPERATION', defaultPrice: 60, supplierCategory: null, warehouseReceivable: true, warehousePayable: false },
{ id: '32', feeTypeName: '手工费', feeCode: 'MANUAL', defaultPrice: null, supplierCategory: null, warehouseReceivable: true, warehousePayable: false },
{ id: '33', feeTypeName: '零元费', feeCode: 'ZERO', defaultPrice: 0, supplierCategory: null, warehouseReceivable: true, warehousePayable: false },
])
const wrapper = mountManager(['firstleg:inbound:fee-detail:view', 'firstleg:inbound:fee-detail:edit', 'firstleg:fee-item:view'])
await flushPromises()
const state = (wrapper.vm.$ as unknown as { setupState: {
openEditor(mode: string, row?: InboundFeeDetail): Promise<void>
save(): Promise<void>
form: { feeItemId: string; unitPrice: number; amount: number }
} }).setupState
await state.openEditor('edit', pending)
await flushPromises()
expect(state.form).toMatchObject({ unitPrice: 10, amount: 100 })
await state.openEditor('create')
await flushPromises()
const select = wrapper.findAllComponents({ name: 'ElFormItem' }).find((item) => item.props('prop') === 'feeItemId')!.findComponent({ name: 'ElSelect' })
for (const [id, price] of [['31', 60], ['32', 0], ['33', 0]] as const) {
select.vm.$emit('update:modelValue', id)
select.vm.$emit('change', id)
await flushPromises()
expect(state.form).toMatchObject({ unitPrice: price, amount: price })
}
select.vm.$emit('update:modelValue', '31')
select.vm.$emit('change', '31')
await flushPromises()
state.form.unitPrice = 65; state.form.amount = 75
await state.save(); await flushPromises()
expect(createInboundFeeDetail).toHaveBeenCalledWith('100', '88', expect.objectContaining({ feeItemId: '31', unitPrice: 65, amount: 75 }))
})
beforeEach(() => {
vi.clearAllMocks(); document.body.innerHTML = ''
vi.mocked(listInboundFeeDetails).mockResolvedValue([pending, approved])
vi.mocked(listInboundFeeItemOptions).mockResolvedValue([{ id: '31', feeTypeName: '操作费', feeCode: 'OPERATION', supplierCategory: 'TRUCKING', warehouseReceivable: true, warehousePayable: true }])
vi.mocked(listInboundFeeItemOptions).mockResolvedValue([{ id: '31', feeTypeName: '操作费', feeCode: 'OPERATION', defaultPrice: null, supplierCategory: 'TRUCKING', warehouseReceivable: true, warehousePayable: true }])
vi.mocked(listInboundFeeSupplierOptions).mockResolvedValue([{ id: '41', supplierName: '测试供应商', category: 'TRUCKING' }])
vi.mocked(createInboundFeeDetail).mockResolvedValue(pending)
vi.mocked(updateInboundFeeDetail).mockResolvedValue(pending)
@@ -227,7 +323,7 @@ describe('InboundFeeDetailManager', () => {
})
it('allows any loaded ACTIVE supplier when a payable fee item has no supplier category', async () => {
vi.mocked(listInboundFeeItemOptions).mockResolvedValue([{ id: '31', feeTypeName: '未映射费用', feeCode: 'UNMAPPED', supplierCategory: null, warehouseReceivable: false, warehousePayable: true }])
vi.mocked(listInboundFeeItemOptions).mockResolvedValue([{ id: '31', feeTypeName: '未映射费用', feeCode: 'UNMAPPED', defaultPrice: null, supplierCategory: null, warehouseReceivable: false, warehousePayable: true }])
vi.mocked(listInboundFeeSupplierOptions).mockResolvedValue([{ id: '41', supplierName: '测试供应商', category: 'CUSTOMS' }])
const wrapper = mountManager([
'firstleg:inbound:fee-detail:view', 'firstleg:inbound:fee-detail:edit',