style(admin): unify dialog layouts and dense readonly presentation
This commit is contained in:
+73
-2
@@ -3,10 +3,11 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import ElementPlus from 'element-plus'
|
||||
import { defineComponent, h } from 'vue'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
import DataTable from '@/shared/components/DataTable.vue'
|
||||
import { ApiResponseError } from '@/shared/api/http'
|
||||
import WarehouseTransferFulfillmentPanel from './WarehouseTransferFulfillmentPanel.vue'
|
||||
import { allocateWarehouseTransfer, getWarehouseTransfer, type WarehouseTransferDetailDto } from '../api/warehouseTransfers'
|
||||
import { listWarehouseTransferSources } from '../api/warehouseTransferSources'
|
||||
import { listWarehouseTransferSources, type WarehouseTransferSourceCandidate } from '../api/warehouseTransferSources'
|
||||
|
||||
const { push, can } = vi.hoisted(() => ({ push: vi.fn(), can: vi.fn() }))
|
||||
vi.mock('vue-router', () => ({ useRouter: () => ({ push }) }))
|
||||
@@ -21,8 +22,15 @@ function fixture(transferNo = 'WT-1', values: Partial<WarehouseTransferDetailDto
|
||||
function render(customerId = 'customer-1', transferNo = 'WT-1') {
|
||||
return mount(WarehouseTransferFulfillmentPanel, { props: { customerId, transferNo }, global: { plugins: [ElementPlus], stubs: { AppDialog: dialogStub } } })
|
||||
}
|
||||
const sourceCandidate: WarehouseTransferSourceCandidate = { inventoryPositionId: '9223372036854775001', palletId: '9223372036854775011', inboundCode: 'IN-1', skuCode: 'SKU-A', productNameZh: '测试货物', palletNo: 'DP-1', warehouseLocationCode: 'LOC-1', availablePackages: 3, canSelect: true, blockReason: '' }
|
||||
async function openNativeAllocation() {
|
||||
const wrapper = mount(WarehouseTransferFulfillmentPanel, { attachTo: document.body, props: { customerId: 'customer-1', transferNo: 'WT-1' }, global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
await wrapper.findAll('button').find(button => button.text() === '选择库存来源 / 分配')!.trigger('click'); await flushPromises()
|
||||
return { wrapper, dialog: wrapper.getComponent(AppDialog) }
|
||||
}
|
||||
describe('WarehouseTransferFulfillmentPanel', () => {
|
||||
beforeEach(() => { vi.clearAllMocks(); can.mockReturnValue(true); vi.mocked(getWarehouseTransfer).mockResolvedValue(fixture()) })
|
||||
beforeEach(() => { vi.clearAllMocks(); can.mockReturnValue(true); vi.mocked(getWarehouseTransfer).mockReset().mockResolvedValue(fixture()); vi.mocked(allocateWarehouseTransfer).mockReset(); vi.mocked(listWarehouseTransferSources).mockReset() })
|
||||
it('requires manage and inventory read permissions before offering allocation', async () => {
|
||||
can.mockImplementation(code => code !== 'lastleg:warehouse-transfer:manage')
|
||||
const wrapper = render(); await flushPromises()
|
||||
@@ -190,4 +198,67 @@ describe('WarehouseTransferFulfillmentPanel', () => {
|
||||
await wrapper.findAll('button').find(x => x.text().includes('重新加载'))!.trigger('click'); await flushPromises()
|
||||
expect(wrapper.text()).toContain('RL-RETRY')
|
||||
})
|
||||
it('retains partial and complete selections through native fullscreen and submits every exact source', async () => {
|
||||
const second = { ...sourceCandidate, inventoryPositionId: '9223372036854775002', palletId: '9223372036854775012', palletNo: 'DP-2', skuCode: 'SKU-B' }
|
||||
const blocked = { ...sourceCandidate, inventoryPositionId: 'BLOCKED', palletNo: 'DP-BLOCKED', canSelect: false, availablePackages: 0, blockReason: '当前无可分配箱数' }
|
||||
vi.mocked(listWarehouseTransferSources).mockResolvedValue({ candidates: [sourceCandidate, second, blocked], issues: [] })
|
||||
vi.mocked(allocateWarehouseTransfer).mockResolvedValue(fixture('WT-1', { status: 'ALLOCATED' }))
|
||||
const { dialog } = await openNativeAllocation()
|
||||
expect(dialog.props('title')).toBe('选择本票库存来源 · WT-1')
|
||||
expect(dialog.get('label[aria-label="选择 DP-BLOCKED SKU-A"] input').attributes('disabled')).toBeDefined()
|
||||
await dialog.get('label[aria-label="选择 DP-1 SKU-A"] input').setValue(true); await flushPromises()
|
||||
expect(dialog.props('confirmDisabled')).toBe(true)
|
||||
expect(allocateWarehouseTransfer).not.toHaveBeenCalled()
|
||||
await dialog.get('label[aria-label="选择 DP-2 SKU-B"] input').setValue(true); await flushPromises()
|
||||
await dialog.get('button[aria-label="展开全屏"]').trigger('click'); await flushPromises()
|
||||
expect(dialog.findComponent({ name: 'ElDialog' }).props('fullscreen')).toBe(true)
|
||||
expect(dialog.get('[aria-label="库存分配核对"]').text()).toContain('已选择2 箱')
|
||||
expect(dialog.props('confirmDisabled')).toBe(false)
|
||||
await dialog.findAll('button').find(button => button.text() === '确认库存分配')!.trigger('click'); await flushPromises()
|
||||
expect(allocateWarehouseTransfer).toHaveBeenCalledExactlyOnceWith('customer-1', 'WT-1', {
|
||||
idempotencyKey: expect.any(String), allocations: [
|
||||
{ inventoryPositionId: sourceCandidate.inventoryPositionId, palletId: sourceCandidate.palletId, packageCount: 1 },
|
||||
{ inventoryPositionId: second.inventoryPositionId, palletId: second.palletId, packageCount: 1 },
|
||||
],
|
||||
})
|
||||
expect(dialog.props('modelValue')).toBe(false)
|
||||
})
|
||||
it('locks the native frame while saving and retains the identical command after an uncertain failure', async () => {
|
||||
vi.mocked(getWarehouseTransfer).mockResolvedValue(fixture('WT-1', { packageCount: 1 }))
|
||||
vi.mocked(listWarehouseTransferSources).mockResolvedValue({ candidates: [sourceCandidate], issues: [] })
|
||||
let reject!: (reason: unknown) => void
|
||||
vi.mocked(allocateWarehouseTransfer).mockReturnValueOnce(new Promise((_, no) => { reject = no })).mockResolvedValueOnce(fixture('WT-1', { status: 'ALLOCATED' }))
|
||||
const { wrapper, dialog } = await openNativeAllocation()
|
||||
await dialog.get('label[aria-label="选择 DP-1 SKU-A"] input').setValue(true); await flushPromises()
|
||||
dialog.vm.$emit('confirm'); dialog.vm.$emit('confirm'); await flushPromises()
|
||||
expect(allocateWarehouseTransfer).toHaveBeenCalledTimes(1)
|
||||
const original = vi.mocked(allocateWarehouseTransfer).mock.calls[0]!
|
||||
expect(dialog.findComponent({ name: 'ElDialog' }).props()).toMatchObject({ showClose: false, closeOnPressEscape: false })
|
||||
expect(dialog.get('input[aria-label="DP-1 分配箱数"]').attributes('disabled')).toBeDefined()
|
||||
reject(new ApiResponseError('库存分配响应未确认', 'A0503', 503, null, 'allocation-frame-request')); await flushPromises()
|
||||
expect(dialog.props('modelValue')).toBe(true)
|
||||
expect(dialog.text()).toContain('allocation-frame-request')
|
||||
expect(dialog.get('input[aria-label="DP-1 分配箱数"]').attributes('disabled')).toBeDefined()
|
||||
await dialog.findAll('button').find(button => button.text() === '取消')!.trigger('click'); await flushPromises()
|
||||
await wrapper.findAll('button').find(button => button.text() === '重试本次库存分配')!.trigger('click'); await flushPromises()
|
||||
await dialog.findAll('button').find(button => button.text() === '重试本次分配')!.trigger('click'); await flushPromises()
|
||||
expect(vi.mocked(allocateWarehouseTransfer).mock.calls[1]).toEqual(original)
|
||||
expect(listWarehouseTransferSources).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
it('retries source errors in the native frame and keeps a long candidate table in the main body', async () => {
|
||||
const candidates = Array.from({ length: 25 }, (_, index) => ({ ...sourceCandidate, inventoryPositionId: `POSITION-${index}`, palletId: `PALLET-${index}`, palletNo: `DP-${index}` }))
|
||||
vi.mocked(listWarehouseTransferSources).mockRejectedValueOnce(new ApiResponseError('来源读取失败', 'A0503', 503, null, 'source-frame-request')).mockResolvedValueOnce({ candidates, issues: ['绑定资料待核查'] })
|
||||
const { dialog } = await openNativeAllocation()
|
||||
expect(dialog.text()).toContain('source-frame-request')
|
||||
expect(dialog.props('confirmDisabled')).toBe(true)
|
||||
await dialog.findAll('button').find(button => button.text() === '重新加载')!.trigger('click'); await flushPromises()
|
||||
const table = dialog.get('[aria-label="本票来源库存"]')
|
||||
expect(table.findAll('.el-table__body-wrapper .el-table__row')).toHaveLength(25)
|
||||
expect(table.text()).toContain('DP-24')
|
||||
expect(dialog.findComponent(DataTable).props('maxHeight')).toBe('')
|
||||
expect((dialog.get('.el-table__body-wrapper .el-scrollbar__wrap').element as HTMLElement).style.maxHeight).toBe('')
|
||||
expect(dialog.text()).toContain('绑定资料待核查')
|
||||
expect(dialog.props('confirmDisabled')).toBe(true)
|
||||
expect(allocateWarehouseTransfer).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user