Files
admin-web/src/modules/overseas-warehouse/components/WarehouseTransferReceiptPanel.test.ts
T
2026-10-16 16:19:22 +08:00

134 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import ElementPlus from 'element-plus'
import { defineComponent, h } from 'vue'
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import WarehouseTransferReceiptPanel from './WarehouseTransferReceiptPanel.vue'
import { getWarehouseTransfer, getWarehouseTransferCartonLabel, type WarehouseTransferDetailDto } from '../api/warehouseTransfers'
vi.mock('../api/warehouseTransfers', () => ({ getWarehouseTransfer: vi.fn(), getWarehouseTransferCartonLabel: vi.fn() }))
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'], setup(props, { slots }) { return () => props.modelValue ? h('section', { 'data-test': 'box-dialog' }, slots.default?.()) : 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(() => { vi.mocked(getWarehouseTransfer).mockReset(); vi.mocked(getWarehouseTransferCartonLabel).mockReset() })
afterEach(() => vi.restoreAllMocks())
describe('转运预报与实际签收明细', () => {
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('实收差异原因:短收一箱,客户漏发')
})