This commit is contained in:
2026-09-04 12:18:39 +08:00
parent 72520225d9
commit 4eda56acf1
8 changed files with 381 additions and 12 deletions
@@ -61,6 +61,14 @@ const pendingWeight: WeightResultResource = {
adjustedAt: null,
confirmedBy: null,
confirmedAt: null,
palletSequence: 1,
blockId: '70001',
slotNo: 1,
reservationId: '71001',
locationId: '72001',
locationCode: 'A01',
sharedOverflow: false,
slotStatus: 'BOUND',
version: 0,
}
@@ -71,6 +79,24 @@ const workspace: WeightWorkspaceResource = {
actualGrossWeightKg: null,
actualNetWeightKg: null,
weightResults: [pendingWeight],
palletLocationGuide: {
blockId: '70001',
reservedSlotCount: 1,
actualPalletCount: 1,
reservedEmptySlotCount: 0,
slots: [{
slotNo: 1,
reservationId: '71001',
locationId: '72001',
locationCode: 'A01',
sharedOverflow: false,
slotStatus: 'BOUND',
boundWeightResultId: '30001',
palletSequence: 1,
palletId: '50001',
palletCode: 'TP-001',
}],
},
}
function palletPage(records = [{
@@ -205,9 +231,137 @@ describe('WeightOcrPanel confirmation behavior', () => {
expect(wrapper.text()).not.toContain('raw-0')
})
it('renders server-bound pallet sequence, target location, and pending putaway status ahead of OCR facts', async () => {
const wrapper = mountPanel()
await flushPromises()
const card = wrapper.get('.weight-card')
expect(card.text()).toContain('第 1 托')
expect(card.text()).toContain('目标库位')
expect(card.text()).toContain('A01')
expect(card.text()).toContain('整票预留库位')
expect(card.text()).toContain('待上架')
const putawayGuide = card.get('.weight-card__putaway').element
expect(putawayGuide.compareDocumentPosition(
card.get('.weight-card__value').element,
) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy()
})
it('renders the authoritative whole-inbound reservation summary and an empty reserved slot', async () => {
vi.mocked(getWeightWorkspace).mockResolvedValue({
...workspace,
palletLocationGuide: {
blockId: '70001',
reservedSlotCount: 3,
actualPalletCount: 2,
reservedEmptySlotCount: 1,
slots: [
{ slotNo: 1, reservationId: '71001', locationId: '72001', locationCode: 'A01', sharedOverflow: false, slotStatus: 'BOUND', boundWeightResultId: '30001', palletSequence: 1, palletId: '50001', palletCode: 'TP-001' },
{ slotNo: 2, reservationId: '71002', locationId: '72002', locationCode: 'A02', sharedOverflow: false, slotStatus: 'TRANSFERRED', boundWeightResultId: '30002', palletSequence: 2, palletId: '50002', palletCode: 'TP-002' },
{ slotNo: 3, reservationId: '71003', locationId: '72003', locationCode: 'A03', sharedOverflow: false, slotStatus: 'RESERVED_EMPTY', boundWeightResultId: null, palletSequence: null, palletId: null, palletCode: null },
],
},
})
const wrapper = mountPanel()
await flushPromises()
const guide = wrapper.get('.putaway-guide')
expect(guide.text()).toContain('整票预留库位')
expect(guide.text()).toContain('A01 / A02 / A03')
expect(guide.text()).toContain('预留槽位数3')
expect(guide.text()).toContain('实际托数2')
expect(guide.text()).toContain('预留空位数1')
expect(guide.text()).toContain('A03预留空位本票预留空位')
})
it('shows an explicit whole-inbound unassigned state when no guide exists', async () => {
vi.mocked(getWeightWorkspace).mockResolvedValue({ ...workspace, palletLocationGuide: null })
const wrapper = mountPanel()
await flushPromises()
expect(wrapper.text()).toContain('尚未分配整票预留库位')
})
it('marks A0 as a temporary overflow location from the server flag', async () => {
vi.mocked(getWeightWorkspace).mockResolvedValue({
...workspace,
weightResults: [{ ...pendingWeight, locationCode: 'A0', sharedOverflow: true }],
palletLocationGuide: {
...workspace.palletLocationGuide!,
slots: [{
...workspace.palletLocationGuide!.slots[0]!,
locationCode: 'A0',
sharedOverflow: true,
}],
},
})
const wrapper = mountPanel()
await flushPromises()
expect(wrapper.text()).toContain('A0 临时溢出位')
expect(wrapper.get('.weight-card__putaway').classes()).toContain('is-overflow')
expect(wrapper.get('.putaway-guide__slots li').classes()).toContain('is-overflow')
})
it.each([
['TRANSFERRED', '已上架'],
['RELEASED', '已释放'],
['RESERVED_EMPTY', '预留空位'],
] as const)('renders the authoritative %s slot status as %s', async (slotStatus, label) => {
vi.mocked(getWeightWorkspace).mockResolvedValue({
...workspace,
weightResults: [{ ...pendingWeight, slotStatus }],
})
const wrapper = mountPanel()
await flushPromises()
expect(wrapper.text()).toContain(label)
})
it('shows an explicit unassigned state when the server has not bound a slot', async () => {
vi.mocked(getWeightWorkspace).mockResolvedValue({
...workspace,
weightResults: [{
...pendingWeight,
palletSequence: null,
blockId: null,
slotNo: null,
reservationId: null,
locationId: null,
locationCode: null,
sharedOverflow: null,
slotStatus: null,
}],
})
const wrapper = mountPanel()
await flushPromises()
expect(wrapper.text()).toContain('托盘未分配')
expect(wrapper.text()).toContain('目标库位未分配')
})
it('deletes a pending image with its optimistic-lock version', async () => {
vi.spyOn(ElMessageBox, 'confirm').mockResolvedValue(undefined as never)
const confirm = vi.spyOn(ElMessageBox, 'confirm').mockResolvedValue(undefined as never)
vi.mocked(deleteWeightResult).mockResolvedValue(undefined)
vi.mocked(getWeightWorkspace)
.mockResolvedValueOnce(workspace)
.mockResolvedValueOnce({
...workspace,
weightResults: [],
palletLocationGuide: {
...workspace.palletLocationGuide!,
actualPalletCount: 0,
reservedEmptySlotCount: 1,
slots: [{
...workspace.palletLocationGuide!.slots[0]!,
slotStatus: 'RESERVED_EMPTY',
boundWeightResultId: null,
palletSequence: null,
palletId: null,
palletCode: null,
}],
},
})
const wrapper = mountPanel()
await flushPromises()
@@ -215,6 +369,48 @@ describe('WeightOcrPanel confirmation behavior', () => {
await flushPromises()
expect(deleteWeightResult).toHaveBeenCalledWith('10001', '20001', '30001', 0)
expect(confirm).toHaveBeenCalledWith(
expect.stringContaining('对应库位仍为本票保留,不会释放给其他订单'),
'删除称重图片',
expect.any(Object),
)
expect(getWeightWorkspace).toHaveBeenCalledTimes(2)
expect(wrapper.findAll('.weight-card')).toHaveLength(0)
expect(wrapper.get('.putaway-guide').text()).toContain('预留空位数1')
expect(wrapper.get('.putaway-guide').text()).toContain('A01预留空位本票预留空位')
})
it('keeps the image and server workspace visible when deletion fails', async () => {
vi.spyOn(ElMessageBox, 'confirm').mockResolvedValue(undefined as never)
const errorMessage = vi.spyOn(ElMessage, 'error')
vi.mocked(deleteWeightResult).mockRejectedValueOnce(
new ApiResponseError('无权删除当前称重图片', 'A0403', 403),
)
const wrapper = mountPanel()
await flushPromises()
await wrapper.get('button[aria-label="删除称重图片 1"]').trigger('click')
await flushPromises()
expect(wrapper.findAll('.weight-card')).toHaveLength(1)
expect(wrapper.text()).toContain('A01')
expect(getWeightWorkspace).toHaveBeenCalledTimes(1)
expect(errorMessage).toHaveBeenCalledWith(expect.objectContaining({
message: '当前账号无权访问该数据,请联系管理员确认权限',
}))
})
it('shows a retryable workspace load failure without fabricating putaway guidance', async () => {
vi.mocked(getWeightWorkspace).mockRejectedValueOnce(
new ApiResponseError('称重工作区暂不可用', 'A0500', 503),
)
const wrapper = mountPanel()
await flushPromises()
expect(wrapper.text()).toContain('称重数据加载失败')
expect(wrapper.text()).toContain('称重工作区暂不可用')
expect(wrapper.find('.putaway-guide').exists()).toBe(false)
expect(wrapper.find('.putaway-guide-empty').exists()).toBe(false)
})
it('shows only the key weights and allows direct confirmation', async () => {