316 lines
24 KiB
TypeScript
316 lines
24 KiB
TypeScript
import ElementPlus from 'element-plus'
|
||
import { defineComponent, h } from 'vue'
|
||
import { enableAutoUnmount, flushPromises, mount, type VueWrapper } from '@vue/test-utils'
|
||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||
import WarehouseTransferReceiptPanel from './WarehouseTransferReceiptPanel.vue'
|
||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||
import DataTable from '@/shared/components/DataTable.vue'
|
||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||
import { ApiResponseError } from '@/shared/api/http'
|
||
import { getWarehouseTransfer, getWarehouseTransferCartonLabel, increaseWarehouseTransferForecast, type WarehouseTransferDetailDto } from '../api/warehouseTransfers'
|
||
|
||
vi.mock('../api/warehouseTransfers', () => ({ getWarehouseTransfer: vi.fn(), getWarehouseTransferCartonLabel: vi.fn(), increaseWarehouseTransferForecast: vi.fn() }))
|
||
const can = vi.hoisted(() => vi.fn(() => true))
|
||
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can }) }))
|
||
enableAutoUnmount(afterEach)
|
||
const DataTableStub = defineComponent({
|
||
props: ['data', 'columns'],
|
||
setup(props, { slots }) { return () => h('div', { class: 'test-table' }, [JSON.stringify(props.data), ...(props.data || []).flatMap((row: object) => [slots.boxAction?.({ row }), slots.quantityDifference?.({ row }), slots.boxDifference?.({ row })])]) },
|
||
})
|
||
const DialogStub = defineComponent({ props: ['modelValue', 'confirmText', 'confirmDisabled', 'confirmLoading'], emits: ['confirm'], setup(props, { slots, emit }) { return () => props.modelValue ? h('section', { 'data-test': 'box-dialog' }, [slots.default?.(), props.confirmText ? h('button', { disabled: props.confirmDisabled || props.confirmLoading, onClick: () => emit('confirm') }, props.confirmText) : null]) : null } })
|
||
const render = (transferNo = 'WT-SOURCE') => mount(WarehouseTransferReceiptPanel, { props: { customerId: 'CUSTOMER', transferNo }, global: { plugins: [ElementPlus], stubs: { DataTable: DataTableStub, AppDialog: DialogStub } } })
|
||
function fixture(): WarehouseTransferDetailDto {
|
||
return {
|
||
id: '1', transferNo: 'WT-SOURCE', status: 'WAIT_CUSTOMER_CONFIRMATION', needRelabel: false, packageCount: 4,
|
||
items: [{ id: '10', skuCode: 'SKU-A', productNameZh: '产品 A', quantity: '120', packageCount: 3 }, { id: '20', skuCode: 'SKU-B', productNameZh: '产品 B', quantity: 30, packageCount: 1 }],
|
||
cartons: [
|
||
{ cartonMark: 'BOX-1', cartonIndex: 1, totalCartons: 4, status: 'COUNTED', transferItemId: '10', transferPalletId: '100', quantity: '8', length: 100, width: 20, height: 50, weight: '2.5', countedAt: '2026-10-15T12:00:00' },
|
||
{ cartonMark: 'BOX-2', cartonIndex: 2, totalCartons: 4, status: 'COUNTED', transferItemId: '10', transferPalletId: '100', quantity: 12, length: 100, width: 20, height: 50, weight: 3.5 },
|
||
{ cartonMark: 'BOX-3', cartonIndex: 3, totalCartons: 4, status: 'COUNTED', transferItemId: '20', transferPalletId: '200', quantity: 7, length: 100, width: 20, height: 50, weight: 4 },
|
||
{ cartonMark: 'BOX-MISSING', cartonIndex: 4, totalCartons: 4, status: 'NOT_RECEIVED', transferItemId: '10', quantity: 999, weight: 999 },
|
||
],
|
||
pallets: [{ id: '100', sequenceNo: 1, palletNo: 'PALLET-A', cartonCount: 2, grossWeightKg: 6, volumeCbm: .2, status: 'COMPLETED' }, { id: '200', sequenceNo: 2, palletNo: 'PALLET-B', cartonCount: 1, grossWeightKg: 4, volumeCbm: .1, status: 'COMPLETED' }], relabelBindings: [],
|
||
}
|
||
}
|
||
beforeEach(() => { can.mockReset().mockReturnValue(true); vi.mocked(getWarehouseTransfer).mockReset(); vi.mocked(getWarehouseTransferCartonLabel).mockReset(); vi.mocked(increaseWarehouseTransferForecast).mockReset() })
|
||
afterEach(() => vi.restoreAllMocks())
|
||
describe('转运预报与实际签收明细', () => {
|
||
it('keeps historical receipt totals after discard or shipment and leaves unknown declaration differences blank', async () => {
|
||
const value = fixture()
|
||
value.items[0]!.quantity = null
|
||
value.cartons[0]!.status = 'DISCARD_PENDING'
|
||
value.cartons[1]!.status = 'SHIPPED'
|
||
value.cartons[2]!.status = 'DISCARDED'
|
||
vi.mocked(getWarehouseTransfer).mockResolvedValue(value)
|
||
const wrapper = render(); await flushPromises()
|
||
expect(wrapper.emitted('summary')!.at(-1)![0]).toMatchObject({ boxes: 3, quantity: 27, skus: 2, forecastQuantity: null })
|
||
const rows = wrapper.findAllComponents(DataTableStub)[0]!.props('data')
|
||
expect(rows[0]).toMatchObject({ receivedQuantity: 20, receivedBoxes: 2, quantityDifference: null })
|
||
expect(wrapper.findAll('.test-table')[0]!.text()).toContain('未记录')
|
||
})
|
||
it('shows every SKU in a mixed box and counts its physical measurements once', async () => {
|
||
const value = fixture()
|
||
value.cartons = [{ ...value.cartons[0]!, quantity: 10, items: [{ transferItemId: '10', skuCode: 'SKU-A', quantity: 3 }, { transferItemId: '20', skuCode: 'SKU-B', quantity: 7 }] }]
|
||
value.pallets = [value.pallets[0]!]
|
||
vi.mocked(getWarehouseTransfer).mockResolvedValue(value)
|
||
const wrapper = render(); await flushPromises()
|
||
expect(wrapper.emitted('summary')!.at(-1)![0]).toMatchObject({ boxes: 1, skus: 2, quantity: 10, weight: 2.5, volume: .1 })
|
||
const comparison = wrapper.findAllComponents(DataTableStub)[0]!.props('data')
|
||
expect(comparison[0]).toMatchObject({ receivedQuantity: 3, receivedBoxes: 1 })
|
||
expect(comparison[1]).toMatchObject({ receivedQuantity: 7, receivedBoxes: 1 })
|
||
expect(wrapper.findAll('.test-table')[1]!.text()).toContain('SKU-A × 3 件;SKU-B × 7 件')
|
||
await wrapper.findAll('button').find(button => button.text() === '查看箱号明细')!.trigger('click')
|
||
await wrapper.findAll('button').find(button => button.text() === '箱内明细')!.trigger('click')
|
||
const contents = wrapper.findAll('[data-test="box-dialog"]')[1]!.text()
|
||
expect(contents).toContain('SKU-A'); expect(contents).toContain('SKU-B')
|
||
expect(contents).toContain('产品 A'); expect(contents).toContain('产品 B')
|
||
})
|
||
it('keeps declarations separate and totals only counted boxes across pallets', async () => {
|
||
vi.mocked(getWarehouseTransfer).mockResolvedValue(fixture())
|
||
const wrapper = render(); await flushPromises()
|
||
expect(getWarehouseTransfer).toHaveBeenCalledWith('CUSTOMER', 'WT-SOURCE')
|
||
const summary = wrapper.emitted('summary')!.at(-1)![0]
|
||
expect(summary).toMatchObject({ forecastBoxes: 4, boxes: 3, quantity: 27, weight: 10 })
|
||
expect((summary as { volume: number }).volume).toBeCloseTo(.3)
|
||
const rows = wrapper.findAllComponents(DataTableStub)[0]!.props('data')
|
||
expect(rows[0]).toMatchObject({ quantity: '120', receivedQuantity: 20, receivedBoxes: 2, quantityDifference: -100, boxDifference: -1 })
|
||
expect(rows[1]).toMatchObject({ receivedQuantity: 7, quantityDifference: -23, boxDifference: 0 })
|
||
const tables = wrapper.findAll('.test-table')
|
||
expect(tables[0]!.text()).toContain('120')
|
||
expect(tables[1]!.text()).toContain('SKU-A × 20 件'); expect(tables[1]!.text()).toContain('SKU-B × 7 件')
|
||
expect(tables).toHaveLength(2)
|
||
await wrapper.findAll('button').find(button => button.text() === '查看箱号明细')!.trigger('click')
|
||
expect(wrapper.findAll('.test-table')[2]!.text()).not.toContain('BOX-MISSING')
|
||
await wrapper.findAll('button').find(button => button.text() === '箱内明细')!.trigger('click')
|
||
const box = wrapper.findAll('[data-test="box-dialog"]')[1]!
|
||
expect(box.text()).toContain('BOX-1'); expect(box.text()).toContain('产品 A'); expect(box.text()).toContain('"quantity":"8"')
|
||
expect(box.text()).not.toContain('120')
|
||
})
|
||
it('shows zero receipts before signing without treating forecast cartons as actual receipts', async () => {
|
||
const value = fixture(); value.pallets = []; value.cartons.forEach(box => { box.status = 'EXPECTED' })
|
||
vi.mocked(getWarehouseTransfer).mockResolvedValue(value)
|
||
const wrapper = render(); await flushPromises()
|
||
expect(wrapper.emitted('summary')!.at(-1)![0]).toMatchObject({ boxes: 0, quantity: 0 })
|
||
expect(wrapper.findAll('.test-table')[0]!.text()).toContain('待签收')
|
||
expect(wrapper.findAll('.test-table')).toHaveLength(2)
|
||
expect(wrapper.findAll('button').find(button => button.text() === '查看箱号明细')!.attributes('disabled')).toBeDefined()
|
||
})
|
||
it('shows signed positive and zero differences against each forecast row', async () => {
|
||
const value = fixture(); value.items[0]!.quantity = 10; value.items[0]!.packageCount = 2
|
||
vi.mocked(getWarehouseTransfer).mockResolvedValue(value)
|
||
const wrapper = render(); await flushPromises()
|
||
const table = wrapper.findAll('.test-table')[0]!
|
||
expect(table.text()).toContain('+10')
|
||
expect(table.findAll('.receipt-difference').map(cell => cell.text())).toEqual(['+10', '-23'])
|
||
expect(wrapper.findAllComponents(DataTableStub)[0]!.props('data')[0]).toMatchObject({ quantityDifference: 10, boxDifference: 0 })
|
||
})
|
||
it('prints server label HTML with the selected customer and source reference', async () => {
|
||
vi.mocked(getWarehouseTransfer).mockResolvedValue(fixture())
|
||
vi.mocked(getWarehouseTransferCartonLabel).mockResolvedValue({ transferNo: 'WT-SOURCE', cartons: fixture().cartons, printableHtml: '<html>SERVER-BOX-LABEL</html>' })
|
||
const popup = { opener: window, document: { open: vi.fn(), write: vi.fn(), close: vi.fn() }, focus: vi.fn(), print: vi.fn(), close: vi.fn() }
|
||
vi.spyOn(window, 'open').mockReturnValue(popup as unknown as Window)
|
||
const wrapper = render(); await flushPromises()
|
||
await wrapper.findAll('button').find(button => button.text() === '打印箱唛')!.trigger('click'); await flushPromises()
|
||
expect(getWarehouseTransferCartonLabel).toHaveBeenCalledExactlyOnceWith('CUSTOMER', 'WT-SOURCE')
|
||
expect(popup.document.write).toHaveBeenCalledWith('<html>SERVER-BOX-LABEL</html>')
|
||
expect(popup.print).toHaveBeenCalledOnce()
|
||
expect(popup.opener).toBeNull()
|
||
})
|
||
it('closes the print window and leaves receipt details available when labels fail', async () => {
|
||
vi.mocked(getWarehouseTransfer).mockResolvedValue(fixture())
|
||
vi.mocked(getWarehouseTransferCartonLabel).mockRejectedValue(new Error('标签暂不可用'))
|
||
const popup = { close: vi.fn(), print: vi.fn() }
|
||
vi.spyOn(window, 'open').mockReturnValue(popup as unknown as Window)
|
||
const wrapper = render(); await flushPromises()
|
||
await wrapper.findAll('button').find(button => button.text() === '打印箱唛')!.trigger('click'); await flushPromises()
|
||
expect(popup.close).toHaveBeenCalledOnce(); expect(popup.print).not.toHaveBeenCalled()
|
||
expect(wrapper.text()).toContain('箱唛加载失败,请重试')
|
||
expect(wrapper.text()).toContain('签收箱号明细(3 箱)')
|
||
})
|
||
it('does not request another order when the source reference is missing', async () => {
|
||
const wrapper = render(''); await flushPromises()
|
||
expect(getWarehouseTransfer).not.toHaveBeenCalled()
|
||
expect(wrapper.text()).toContain('缺少客户或来源转运单号')
|
||
})
|
||
it('discards a stale response after switching transfers', async () => {
|
||
let resolveOld!: (value: WarehouseTransferDetailDto) => void
|
||
vi.mocked(getWarehouseTransfer).mockImplementationOnce(() => new Promise(resolve => { resolveOld = resolve })).mockResolvedValueOnce({ ...fixture(), transferNo: 'WT-NEW', packageCount: 9 })
|
||
const wrapper = render(); await wrapper.setProps({ transferNo: 'WT-NEW' }); await flushPromises()
|
||
resolveOld(fixture()); await flushPromises()
|
||
expect(wrapper.emitted('summary')!.at(-1)![0]).toMatchObject({ forecastBoxes: 9 })
|
||
})
|
||
})
|
||
|
||
// 完成原因直接展示,多个箱保存同一原因时只显示一次。
|
||
it('displays saved discrepancy reason once without opening box details', async () => {
|
||
const value = fixture()
|
||
value.cartons.forEach(box => { box.discrepancyReason = '短收一箱,客户漏发' })
|
||
vi.mocked(getWarehouseTransfer).mockResolvedValue(value)
|
||
const wrapper = render(); await flushPromises()
|
||
expect(wrapper.findAll('.receipt-discrepancy-reason')).toHaveLength(1)
|
||
expect(wrapper.find('.receipt-discrepancy-reason').text()).toBe('实收差异原因:短收一箱,客户漏发')
|
||
})
|
||
|
||
/** 平台只填新增箱数,保留SKU预报并按原箱数校验。 */
|
||
it('平台可补整单预报箱数,无需选择SKU', async () => {
|
||
const value = { ...fixture(), inboundCode: 'WT-SOURCE', status: 'WAIT_PDA_RECEIVING' }
|
||
vi.mocked(getWarehouseTransfer).mockResolvedValue(value)
|
||
vi.mocked(increaseWarehouseTransferForecast).mockResolvedValue({ ...value, packageCount: 6 })
|
||
const wrapper = render(); await flushPromises()
|
||
await wrapper.findAll('button').find(button => button.text() === '增加预报箱数')!.trigger('click')
|
||
const number = wrapper.findComponent({ name: 'ElInputNumber' }); await number.vm.$emit('update:modelValue', 2)
|
||
await wrapper.findAll('button').find(button => button.text() === '确认增加')!.trigger('click'); await flushPromises()
|
||
expect(increaseWarehouseTransferForecast).toHaveBeenCalledWith('CUSTOMER', 'WT-SOURCE', { expectedPackageCount: 4, packageCount: 6 })
|
||
expect(wrapper.text()).toContain('预报箱数 6 箱')
|
||
})
|
||
|
||
async function nativeReceipt() {
|
||
const wrapper = mount(WarehouseTransferReceiptPanel, { attachTo: document.body, props: { customerId: 'CUSTOMER', transferNo: 'WT-SOURCE' }, global: { plugins: [ElementPlus], stubs: { transition: false } } })
|
||
await flushPromises()
|
||
return wrapper
|
||
}
|
||
function popup(wrapper: VueWrapper, title: string) { return wrapper.findAllComponents(AppDialog).find(dialog => dialog.props('title').startsWith(title))! }
|
||
async function openNativeForecast() {
|
||
const wrapper = await nativeReceipt()
|
||
await wrapper.findAll('button').find(button => button.text() === '增加预报箱数')!.trigger('click'); await flushPromises()
|
||
return { wrapper, dialog: popup(wrapper, '增加预报箱数') }
|
||
}
|
||
function forecastFixture() { return { ...fixture(), inboundCode: 'WT-SOURCE', status: 'WAIT_PDA_RECEIVING' } }
|
||
|
||
describe('receipt native popup integration', () => {
|
||
it('keeps counted boxes and mixed SKU contents through two independent fullscreen frames', async () => {
|
||
const value = fixture()
|
||
value.cartons[0] = { ...value.cartons[0]!, quantity: 10, discrepancyReason: '少收两件,客户确认', items: [{ transferItemId: '10', skuCode: 'SKU-A', quantity: 3 }, { transferItemId: '20', skuCode: 'SKU-B', quantity: 7 }] }
|
||
vi.mocked(getWarehouseTransfer).mockResolvedValue(value)
|
||
const wrapper = await nativeReceipt()
|
||
await wrapper.findAll('button').find(button => button.text() === '查看箱号明细')!.trigger('click'); await flushPromises()
|
||
const cartonsDialog = popup(wrapper, '签收箱号明细')
|
||
expect(cartonsDialog.props('title')).toBe('签收箱号明细 · WT-SOURCE')
|
||
expect(cartonsDialog.get('[aria-label="已清点箱号"]').findAll('.el-table__body-wrapper .el-table__row')).toHaveLength(3)
|
||
expect(cartonsDialog.text()).not.toContain('BOX-MISSING')
|
||
await cartonsDialog.get('button[aria-label="展开全屏"]').trigger('click'); await flushPromises()
|
||
await cartonsDialog.findAll('.el-table__body-wrapper button').find(button => button.text() === '箱内明细')!.trigger('click'); await flushPromises()
|
||
const boxDialog = popup(wrapper, '箱内清点明细')
|
||
expect(boxDialog.props('title')).toBe('箱内清点明细 · BOX-1')
|
||
await boxDialog.get('button[aria-label="展开全屏"]').trigger('click'); await flushPromises()
|
||
expect(boxDialog.findComponent({ name: 'ElDialog' }).props('fullscreen')).toBe(true)
|
||
expect(boxDialog.findComponent(DataTable).props('data')).toEqual([{ sku: 'SKU-A', name: '产品 A', quantity: 3 }, { sku: 'SKU-B', name: '产品 B', quantity: 7 }])
|
||
expect(boxDialog.text()).toContain('少收两件,客户确认')
|
||
expect(boxDialog.get('[aria-label="当前签收箱"]').text()).toContain('2026-10-15')
|
||
await boxDialog.get('.el-dialog__headerbtn').trigger('click'); await new Promise(resolve => setTimeout(resolve, 80)); await flushPromises()
|
||
expect(boxDialog.props('modelValue')).toBe(false)
|
||
expect(cartonsDialog.props('modelValue')).toBe(true)
|
||
expect(cartonsDialog.findComponent({ name: 'ElDialog' }).props('fullscreen')).toBe(true)
|
||
expect(getWarehouseTransfer).toHaveBeenCalledExactlyOnceWith('CUSTOMER', 'WT-SOURCE')
|
||
})
|
||
it('retains every counted box in the main dialog body without a nested height cap', async () => {
|
||
const value = fixture()
|
||
value.cartons = Array.from({ length: 30 }, (_, index) => ({ ...value.cartons[0]!, cartonMark: `BOX-${index + 1}`, cartonIndex: index + 1, totalCartons: 30 }))
|
||
vi.mocked(getWarehouseTransfer).mockResolvedValue(value)
|
||
const wrapper = await nativeReceipt()
|
||
await wrapper.findAll('button').find(button => button.text() === '查看箱号明细')!.trigger('click'); await flushPromises()
|
||
const dialog = popup(wrapper, '签收箱号明细')
|
||
expect(dialog.get('[aria-label="实收箱数核对"]').text()).toContain('30 箱')
|
||
expect(dialog.findAll('.el-table__body-wrapper .el-table__row')).toHaveLength(30)
|
||
expect(dialog.text()).toContain('BOX-30')
|
||
expect(dialog.findComponent(DataTable).props('maxHeight')).toBe('')
|
||
expect((dialog.get('.el-table__body-wrapper .el-scrollbar__wrap').element as HTMLElement).style.maxHeight).toBe('')
|
||
})
|
||
it('freezes the original forecast count through fullscreen and sends only the scoped count command', async () => {
|
||
const value = forecastFixture()
|
||
vi.mocked(getWarehouseTransfer).mockResolvedValue(value)
|
||
vi.mocked(increaseWarehouseTransferForecast).mockResolvedValue({ ...value, packageCount: 6 })
|
||
const { wrapper, dialog } = await openNativeForecast()
|
||
expect(dialog.props('title')).toBe('增加预报箱数 · WT-SOURCE')
|
||
expect(dialog.get('[aria-label="预报箱数核对"]').findAll('input')).toHaveLength(0)
|
||
dialog.findComponent({ name: 'ElInputNumber' }).vm.$emit('update:modelValue', 2); await flushPromises()
|
||
await dialog.get('button[aria-label="展开全屏"]').trigger('click'); await flushPromises()
|
||
expect(dialog.get('[aria-label="预报箱数核对"]').text()).toContain('增加后6 箱')
|
||
expect(dialog.findComponent({ name: 'ElInputNumber' }).props('modelValue')).toBe(2)
|
||
await dialog.findAll('button').find(button => button.text() === '确认增加')!.trigger('click'); await flushPromises()
|
||
expect(increaseWarehouseTransferForecast).toHaveBeenCalledExactlyOnceWith('CUSTOMER', 'WT-SOURCE', { expectedPackageCount: 4, packageCount: 6 })
|
||
expect(dialog.props('modelValue')).toBe(false)
|
||
expect(wrapper.text()).toContain('预报箱数 6 箱')
|
||
})
|
||
it('locks pending edits and closing, preserves the failed count and retries the same snapshot', async () => {
|
||
const value = forecastFixture()
|
||
vi.mocked(getWarehouseTransfer).mockResolvedValue(value)
|
||
let reject!: (reason: unknown) => void
|
||
vi.mocked(increaseWarehouseTransferForecast).mockReturnValueOnce(new Promise((_, no) => { reject = no })).mockResolvedValueOnce({ ...value, packageCount: 6 })
|
||
const { dialog } = await openNativeForecast()
|
||
dialog.findComponent({ name: 'ElInputNumber' }).vm.$emit('update:modelValue', 2); await flushPromises()
|
||
dialog.vm.$emit('confirm'); dialog.vm.$emit('confirm'); await flushPromises()
|
||
expect(increaseWarehouseTransferForecast).toHaveBeenCalledTimes(1)
|
||
expect(dialog.findComponent({ name: 'ElDialog' }).props()).toMatchObject({ showClose: false, closeOnPressEscape: false })
|
||
expect(dialog.get('input[placeholder="请输入新增箱数"]').attributes('disabled')).toBeDefined()
|
||
expect(dialog.findAll('button').find(button => button.text() === '取消')!.attributes('disabled')).toBeDefined()
|
||
reject(new ApiResponseError('箱数已被其他操作修改', 'A0409', 409, null, 'forecast-frame-request')); await flushPromises()
|
||
expect(dialog.props('modelValue')).toBe(true)
|
||
expect(dialog.findComponent({ name: 'ElInputNumber' }).props('modelValue')).toBe(2)
|
||
expect(dialog.text()).toContain('forecast-frame-request')
|
||
expect(dialog.get('[aria-label="预报箱数核对"]').text()).toContain('原预报箱数4 箱')
|
||
await dialog.findAll('button').find(button => button.text() === '重试本次增加')!.trigger('click'); await flushPromises()
|
||
expect(vi.mocked(increaseWarehouseTransferForecast).mock.calls).toEqual([
|
||
['CUSTOMER', 'WT-SOURCE', { expectedPackageCount: 4, packageCount: 6 }],
|
||
['CUSTOMER', 'WT-SOURCE', { expectedPackageCount: 4, packageCount: 6 }],
|
||
])
|
||
})
|
||
it('keeps whole-box and 5000-box limits at both the frame and submit handler', async () => {
|
||
const value = { ...forecastFixture(), packageCount: 4999 }
|
||
vi.mocked(getWarehouseTransfer).mockResolvedValue(value)
|
||
vi.mocked(increaseWarehouseTransferForecast).mockResolvedValue({ ...value, packageCount: 5000 })
|
||
const { dialog } = await openNativeForecast()
|
||
const input = dialog.findComponent({ name: 'ElInputNumber' })
|
||
expect(input.props('max')).toBe(1)
|
||
for (const count of [0, -1, 1.5, Number.NaN, 2]) {
|
||
input.vm.$emit('update:modelValue', count)
|
||
dialog.vm.$emit('confirm'); await flushPromises()
|
||
expect(increaseWarehouseTransferForecast).not.toHaveBeenCalled()
|
||
input.vm.$emit('update:modelValue', 1); await flushPromises()
|
||
}
|
||
input.vm.$emit('update:modelValue', 1); await flushPromises()
|
||
expect(dialog.props('confirmDisabled')).toBe(false)
|
||
dialog.vm.$emit('confirm'); await flushPromises()
|
||
expect(increaseWarehouseTransferForecast).toHaveBeenCalledExactlyOnceWith('CUSTOMER', 'WT-SOURCE', { expectedPackageCount: 4999, packageCount: 5000 })
|
||
})
|
||
it('resets a cancelled draft and its failure context when reopened', async () => {
|
||
vi.mocked(getWarehouseTransfer).mockResolvedValue(forecastFixture())
|
||
vi.mocked(increaseWarehouseTransferForecast).mockRejectedValue(new ApiResponseError('增加失败', 'A0503', 503, null, 'old-forecast-request'))
|
||
const { wrapper, dialog } = await openNativeForecast()
|
||
dialog.findComponent({ name: 'ElInputNumber' }).vm.$emit('update:modelValue', 2); await flushPromises()
|
||
dialog.vm.$emit('confirm'); await flushPromises()
|
||
expect(dialog.text()).toContain('old-forecast-request')
|
||
await dialog.findAll('button').find(button => button.text() === '取消')!.trigger('click'); await flushPromises()
|
||
await wrapper.findAll('button').find(button => button.text() === '增加预报箱数')!.trigger('click'); await flushPromises()
|
||
expect(dialog.findComponent({ name: 'ElInputNumber' }).props('modelValue')).toBe(1)
|
||
expect(dialog.findComponent(LoadErrorState).exists()).toBe(false)
|
||
expect(increaseWarehouseTransferForecast).toHaveBeenCalledTimes(1)
|
||
})
|
||
it('closes the old forecast frame on a scope change and ignores a late failure', async () => {
|
||
vi.mocked(getWarehouseTransfer).mockResolvedValueOnce(forecastFixture()).mockResolvedValueOnce({ ...forecastFixture(), transferNo: 'WT-NEW', inboundCode: 'WT-NEW', packageCount: 8 })
|
||
let reject!: (reason: unknown) => void
|
||
vi.mocked(increaseWarehouseTransferForecast).mockReturnValueOnce(new Promise((_, no) => { reject = no }))
|
||
const { wrapper, dialog } = await openNativeForecast()
|
||
dialog.findComponent({ name: 'ElInputNumber' }).vm.$emit('update:modelValue', 2); await flushPromises()
|
||
dialog.vm.$emit('confirm'); await flushPromises()
|
||
await wrapper.setProps({ customerId: 'CUSTOMER-NEW', transferNo: 'WT-NEW' }); await flushPromises()
|
||
expect(dialog.props('modelValue')).toBe(false)
|
||
reject(new ApiResponseError('旧票响应', 'A0503', 503, null, 'stale-forecast-request')); await flushPromises()
|
||
expect(wrapper.text()).toContain('预报箱数 8 箱')
|
||
expect(wrapper.text()).not.toContain('stale-forecast-request')
|
||
await wrapper.findAll('button').find(button => button.text() === '增加预报箱数')!.trigger('click'); await flushPromises()
|
||
expect(dialog.props('title')).toBe('增加预报箱数 · WT-NEW')
|
||
expect(dialog.get('[aria-label="预报箱数核对"]').text()).toContain('原预报箱数8 箱')
|
||
expect(dialog.findComponent({ name: 'ElInputNumber' }).props('modelValue')).toBe(1)
|
||
expect(increaseWarehouseTransferForecast).toHaveBeenCalledExactlyOnceWith('CUSTOMER', 'WT-SOURCE', { expectedPackageCount: 4, packageCount: 6 })
|
||
})
|
||
it('keeps forecast modification unavailable without approval permission', async () => {
|
||
can.mockReturnValue(false)
|
||
vi.mocked(getWarehouseTransfer).mockResolvedValue(forecastFixture())
|
||
const wrapper = await nativeReceipt()
|
||
expect(wrapper.findAll('button').some(button => button.text() === '增加预报箱数')).toBe(false)
|
||
popup(wrapper, '增加预报箱数').vm.$emit('confirm'); await flushPromises()
|
||
expect(increaseWarehouseTransferForecast).not.toHaveBeenCalled()
|
||
})
|
||
})
|