fix: align audit and dispatch workflows and release failed requests
This commit is contained in:
@@ -23,41 +23,41 @@ describe('transfer movement UI', () => {
|
||||
can.mockImplementation(code => code !== 'platform:overseas-warehouse:warehouse:list')
|
||||
const denied = render(detail('OUTBOUNDED')); expect(denied.text()).not.toContain('登记目标仓实收'); expect(listTransferReceivingWarehouses).not.toHaveBeenCalled()
|
||||
})
|
||||
it.each([
|
||||
{ cause: new ApiResponseError('目标不可用', 'A0409', 200), recorded: false, canReselect: true },
|
||||
{ cause: new Error('response lost'), recorded: false, canReselect: false },
|
||||
{ cause: new ApiResponseError('目标不可用', 'A0409', 200), recorded: true, canReselect: false },
|
||||
])('allows target correction only after definite rejection plus authoritative no-command readback: $canReselect', async ({ cause, recorded, canReselect }) => {
|
||||
const body = { sourceInventoryPositionId: 'hidden-source', destinationOverseasWarehouseId: 'NY', destinationOverseasWarehouseCode: 'NY-CODE', destinationOverseasWarehouseName: '纽约', destinationWarehouseCode: 'ABE4', destinationPalletId: 'P-NY', destinationPalletNo: 'PALLET-NY', warehouseLocationId: 'L-NY', warehouseLocationCode: 'LOC-NY', batchNo: 'WT-1', idempotencyKey: 'original-key' }
|
||||
sessionStorage.setItem('warehouse-transfer-movement:customer:WT-1', JSON.stringify({ pending: { type: 'receipt', body } }))
|
||||
const fresh = detail('OUTBOUNDED')
|
||||
if (recorded) fresh.receiptCommands = [{ sourceInventoryPositionId: 'hidden-source', status: 'PREPARED', requestHash: 'hash', request: body, target: { warehouseId: 'NY', warehouseCode: 'NY-CODE', warehouseName: '纽约', palletId: 'P-NY', palletNo: 'PALLET-NY', locationId: 'L-NY', locationCode: 'LOC-NY', fbaCode: 'ABE4' } }]
|
||||
vi.mocked(receiveWarehouseTransfer).mockRejectedValue(cause); vi.mocked(getWarehouseTransfer).mockResolvedValue(fresh)
|
||||
const wrapper = render(detail('OUTBOUNDED')); await button(wrapper, '重试原操作').trigger('click'); await flushPromises()
|
||||
expect(wrapper.text()).not.toContain('重新选择目标')
|
||||
await button(wrapper, '只读核对当前状态').trigger('click'); await flushPromises()
|
||||
expect(wrapper.text().includes('重新选择目标')).toBe(canReselect)
|
||||
expect(receiveWarehouseTransfer).toHaveBeenCalledTimes(1)
|
||||
expect(JSON.parse(sessionStorage.getItem('warehouse-transfer-movement:customer:WT-1')!).pending.body).toEqual(body)
|
||||
if (canReselect) {
|
||||
vi.mocked(listTransferReceivingWarehouses).mockResolvedValue({ records: [] })
|
||||
await button(wrapper, '重新选择目标').trigger('click'); await flushPromises()
|
||||
expect(JSON.parse(sessionStorage.getItem('warehouse-transfer-movement:customer:WT-1')!).pending).toBeNull()
|
||||
expect(wrapper.text()).toContain('上次目标选择被拒绝:目标不可用')
|
||||
expect(receiveWarehouseTransfer).toHaveBeenCalledTimes(1)
|
||||
}
|
||||
it.each(['{bad', JSON.stringify({pending:{type:'receipt',body:{idempotencyKey:'old'}}})])('ignores stored failed receipts without blocking current selection', async stored => {
|
||||
sessionStorage.setItem('warehouse-transfer-movement:customer:WT-1', stored)
|
||||
vi.mocked(listTransferReceivingWarehouses).mockResolvedValue({ records: [] })
|
||||
const wrapper = render(detail('OUTBOUNDED')); await button(wrapper, '登记目标仓实收').trigger('click'); await flushPromises()
|
||||
expect(wrapper.find('[aria-label="目标仓实收选择"]').exists()).toBe(true)
|
||||
expect(receiveWarehouseTransfer).not.toHaveBeenCalled(); expect(wrapper.text()).not.toContain('重试原操作')
|
||||
})
|
||||
it('recovers a reserved original server command in a fresh browser and never queries arbitrary occupied resources', async () => {
|
||||
it.each(['PREPARED', 'RESERVED'] as const)('continues a %s source from current server state and unlocks other sources after failure', async status => {
|
||||
const command: WarehouseTransferReceiptCommandDto = { sourceInventoryPositionId: 'hidden-source', status: 'RESERVED', requestHash: 'server-hash', request: { sourceInventoryPositionId: 'hidden-source', destinationOverseasWarehouseId: 'NY', destinationOverseasWarehouseCode: 'NY-CODE', destinationOverseasWarehouseName: '纽约', destinationWarehouseCode: 'ABE4', destinationPalletId: 'P-NY', destinationPalletNo: 'PALLET-NY', warehouseLocationId: 'L-NY', warehouseLocationCode: 'LOC-NY', batchNo: 'WT-1', idempotencyKey: 'server-original' }, target: { warehouseId: 'NY', warehouseCode: 'NY-CODE', warehouseName: '纽约', palletId: 'P-NY', palletNo: 'PALLET-NY', locationId: 'L-NY', locationCode: 'LOC-NY', fbaCode: 'ABE4' } }
|
||||
command.status = status
|
||||
const d = detail('OUTBOUNDED'); d.receiptCommands = [command]
|
||||
d.allocations!.push({ ...d.allocations![0]!, inventoryPositionId: 'other-source', inboundCode: 'IN-2' })
|
||||
const completed = detail('RECEIVED'); completed.allocations![0]!.status = 'RECEIVED'; completed.receiptCommands = [{ ...command, status: 'COMPLETED' }]
|
||||
vi.mocked(receiveWarehouseTransfer).mockResolvedValue(completed)
|
||||
vi.mocked(getWarehouseTransfer).mockResolvedValue(d)
|
||||
vi.mocked(receiveWarehouseTransfer).mockRejectedValueOnce(new Error('response lost')).mockResolvedValueOnce(completed)
|
||||
const wrapper = render(d); await flushPromises()
|
||||
expect(wrapper.text()).toContain('从本票服务端记录恢复'); expect(wrapper.text()).not.toContain('登记目标仓实收')
|
||||
await button(wrapper, '重试原操作').trigger('click'); await flushPromises()
|
||||
expect(wrapper.text()).not.toContain('重试原操作'); expect(wrapper.text()).toContain('登记目标仓实收')
|
||||
expect(receiveWarehouseTransfer).not.toHaveBeenCalled()
|
||||
expect(getWarehouseTransfer).not.toHaveBeenCalled()
|
||||
await button(wrapper, '确认本来源实收').trigger('click'); await flushPromises()
|
||||
expect(receiveWarehouseTransfer).toHaveBeenCalledExactlyOnceWith('customer', 'WT-1', command.request)
|
||||
expect(wrapper.emitted('updated')?.[0]?.[0]).toMatchObject({ status: 'RECEIVED' })
|
||||
expect(getWarehouseTransfer).toHaveBeenCalledTimes(1)
|
||||
expect(button(wrapper, '登记目标仓实收').attributes('disabled')).toBeUndefined()
|
||||
expect(button(wrapper, '确认本来源实收').attributes('disabled')).toBeUndefined()
|
||||
expect(sessionStorage.length).toBe(0)
|
||||
await button(wrapper, '确认本来源实收').trigger('click'); await flushPromises()
|
||||
expect(getWarehouseTransfer).toHaveBeenCalledTimes(2)
|
||||
expect(receiveWarehouseTransfer).toHaveBeenCalledTimes(2)
|
||||
expect(wrapper.emitted('updated')?.at(-1)?.[0]).toEqual(completed)
|
||||
// 服务端已完成时只展示最新结果,不再次提交。
|
||||
vi.mocked(getWarehouseTransfer).mockResolvedValue(completed)
|
||||
await button(wrapper, '确认本来源实收').trigger('click'); await flushPromises()
|
||||
expect(receiveWarehouseTransfer).toHaveBeenCalledTimes(2)
|
||||
expect(listPallets).not.toHaveBeenCalled(); expect(getPallet).not.toHaveBeenCalled()
|
||||
expect(wrapper.text()).not.toContain('hidden-source')
|
||||
})
|
||||
it('does not use a browser remembered occupied target when server receipt history is missing', async () => {
|
||||
sessionStorage.setItem('warehouse-transfer-movement:customer:WT-1', JSON.stringify({ pending: null, previous: { warehouse: { id: 'OTHER' }, pallet: { id: 'OTHER', status: 'OCCUPIED' } } }))
|
||||
@@ -79,20 +79,18 @@ describe('transfer movement UI', () => {
|
||||
expect(button(wrapper, '确认本来源实收').attributes('disabled')).toBeUndefined()
|
||||
expect(receiveWarehouseTransfer).not.toHaveBeenCalled()
|
||||
})
|
||||
it('requires permission and explicit actual-outbound confirmation; unknown outcome survives remount with same body', async () => {
|
||||
it('requires outbound permission and releases a failed confirmation for a fresh click', async () => {
|
||||
vi.mocked(outboundWarehouseTransfer).mockRejectedValueOnce(new Error('response lost')).mockResolvedValueOnce(detail('OUTBOUNDED'))
|
||||
const first = render(); await button(first, '确认本票出库').trigger('click'); expect(outboundWarehouseTransfer).not.toHaveBeenCalled()
|
||||
await button(first, '确认实际出库').trigger('click'); await flushPromises()
|
||||
const original = JSON.parse(JSON.stringify(vi.mocked(outboundWarehouseTransfer).mock.calls[0]))
|
||||
expect(first.text()).toContain('原操作内容已保留'); first.unmount()
|
||||
const second = render(detail('OUTBOUNDED')); await flushPromises()
|
||||
expect(second.text()).not.toContain('hidden-source'); expect(second.text()).toContain('重试原操作')
|
||||
await button(second, '重试原操作').trigger('click'); await flushPromises()
|
||||
expect(vi.mocked(outboundWarehouseTransfer).mock.calls[1]).toEqual(original)
|
||||
expect(second.emitted('updated')?.[0]?.[0]).toMatchObject({ status: 'OUTBOUNDED' })
|
||||
const wrapper = render(); await button(wrapper, '确认本票出库').trigger('click'); expect(outboundWarehouseTransfer).not.toHaveBeenCalled()
|
||||
await button(wrapper, '确认实际出库').trigger('click'); await flushPromises()
|
||||
expect(button(wrapper, '确认本票出库').attributes('disabled')).toBeUndefined(); expect(sessionStorage.length).toBe(0)
|
||||
const original = vi.mocked(outboundWarehouseTransfer).mock.calls[0]![2]
|
||||
await button(wrapper, '确认实际出库').trigger('click'); await flushPromises()
|
||||
expect(vi.mocked(outboundWarehouseTransfer).mock.calls[1]![2].idempotencyKey).not.toBe(original.idempotencyKey)
|
||||
expect(wrapper.emitted('updated')?.[0]?.[0]).toMatchObject({ status: 'OUTBOUNDED' })
|
||||
can.mockReturnValue(false); const denied = render(); expect(denied.text()).not.toContain('确认本票出库')
|
||||
})
|
||||
it('queries only selected warehouse page, reads exact bound location and keeps receipt request after response loss', async () => {
|
||||
it('queries only selected warehouse page, reads exact bound location and unlocks the selection after response loss', async () => {
|
||||
vi.mocked(listTransferReceivingWarehouses).mockResolvedValue({ records: [{ id: 'NY', customerId: '0', warehouseCode: 'NY-CODE', warehouseName: '纽约', warehouseType: 'OVERSEAS', status: 'ACTIVE' }] })
|
||||
const p = { id: 'P-NY', customerId: '0', warehouseId: 'NY', locationId: 'L-NY', palletCode: 'PALLET-NY', palletType: 'STANDARD', fbaWarehouseCode: 'ABE4', capacityCbm: 2, unitPrice: 0, status: 'IDLE', version: 0 }
|
||||
vi.mocked(listPallets).mockResolvedValue({ records: [p], page: 1, size: 20, total: 1, pages: 1 })
|
||||
@@ -109,10 +107,10 @@ describe('transfer movement UI', () => {
|
||||
await button(wrapper, '确认本来源实收').trigger('click'); await flushPromises()
|
||||
const original = JSON.parse(JSON.stringify(vi.mocked(receiveWarehouseTransfer).mock.calls[0]))
|
||||
expect(original[2]).toMatchObject({ sourceInventoryPositionId: 'hidden-source', destinationPalletNo: 'PALLET-NY', warehouseLocationCode: 'LOC-NY' })
|
||||
await button(wrapper, '只读核对当前状态').trigger('click'); await flushPromises()
|
||||
expect(button(wrapper, '查询目标托盘').attributes('disabled')).toBeUndefined()
|
||||
expect(receiveWarehouseTransfer).toHaveBeenCalledTimes(1)
|
||||
await button(wrapper, '重试原操作').trigger('click'); await flushPromises()
|
||||
expect(vi.mocked(receiveWarehouseTransfer).mock.calls[1]).toEqual(original)
|
||||
await button(wrapper, '确认本来源实收').trigger('click'); await flushPromises()
|
||||
expect(vi.mocked(receiveWarehouseTransfer).mock.calls[1]![2].idempotencyKey).not.toBe(original[2].idempotencyKey)
|
||||
expect(wrapper.text()).not.toContain('hidden-source')
|
||||
})
|
||||
it('does not request resources when permission is absent or source already received', async () => {
|
||||
|
||||
Reference in New Issue
Block a user