import { flushPromises, mount } from '@vue/test-utils' import { beforeEach, expect, it, vi } from 'vitest' import WorkReceiptPlans from './WorkReceiptPlans.vue' import { getContainerPlanProgress, getContainerInboundPlanPreview, convertLastMileContainerToInboundPlans, type ContainerInboundPlanPreviewDto } from '@/modules/overseas-warehouse' import type { LeaveGuard } from './useWorkDraft' const state = vi.hoisted(() => ({ canRead: true })) vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => state.canRead }) })) vi.mock('element-plus', () => ({ ElMessage: { warning: vi.fn(), success: vi.fn() }, ElMessageBox: { confirm: vi.fn() } })) vi.mock('@/modules/overseas-warehouse', async original => ({ ...await original(), getContainerPlanProgress: vi.fn(), getContainerInboundPlanPreview: vi.fn(), convertLastMileContainerToInboundPlans: vi.fn() })) let guard: LeaveGuard function render() { return mount(WorkReceiptPlans, { props: { containerBookingId: '9007199254740993', canWrite: true }, global: { provide: { 'appointment-work-leave-guard': (value: LeaveGuard) => { guard = value; return () => undefined } }, stubs: { ElButton: { props: ['disabled'], template: '' }, LoadErrorState: true, ApiErrorMeta: true } } }) } function button(wrapper: ReturnType, text: string) { return wrapper.findAll('button').find(button => button.text() === text)! } function preview(overrides: Partial = {}) { return { sourceContainerBookingId: '9007199254740993', containerNo: 'TEST-CN', overseasWarehouseName: '接仓仓库', snapshotVersion: 4, snapshotHash: 'snapshot-4', pendingOrderCount: 1, blockedOrderCount: 0, sourcePalletCount: 2, supplementPalletCount: 1, totalPlannedPalletCount: 0, ready: true, groups: [], ...overrides } as ContainerInboundPlanPreviewDto } beforeEach(() => { vi.clearAllMocks(); state.canRead = true; vi.mocked(getContainerPlanProgress).mockResolvedValue({ summary: { containerNo: 'TEST-CN', overseasWarehouseName: '接仓仓库' }, orders: [{ orderId: 'internal-1', customerId: '9007199254740999', planId: 'internal-plan', planNo: 'IP-001', planValid: true, inboundCode: 'IN-001' }, { orderId: 'internal-2', customerId: 'customer-b', planId: null, planNo: null, planValid: false, inboundCode: 'IN-002' }] } as never) }) it('opens a real plan in the exact customer scope while leaving missing plans unavailable', async () => { const wrapper = render(); await flushPromises() expect(getContainerPlanProgress).toHaveBeenCalledExactlyOnceWith('9007199254740993') const plans = wrapper.findAll('button').filter(item => item.text() === '查看原接仓记录') await plans[0]!.trigger('click') expect(wrapper.emitted('external')).toEqual([['/overseas-warehouse/inbound-orders/IP-001?customerId=9007199254740999']]) expect(plans[1]!.attributes('disabled')).toBeDefined() expect(wrapper.text()).not.toContain('internal-plan'); expect(wrapper.text()).not.toContain('9007199254740999'); wrapper.unmount() }) it('does not read the container source without its original permission', async () => { state.canRead = false; const wrapper = render(); await flushPromises(); expect(getContainerPlanProgress).not.toHaveBeenCalled(); wrapper.unmount() }) it('submits the actual preview snapshot and permits a fresh click after an unknown result', async () => { vi.mocked(getContainerInboundPlanPreview).mockResolvedValue(preview()) vi.mocked(convertLastMileContainerToInboundPlans).mockRejectedValueOnce(new Error('lost response')).mockResolvedValueOnce([]) const wrapper = render(); await flushPromises(); await button(wrapper, '核对本柜入库计划预览').trigger('click'); await flushPromises() await button(wrapper, '确认生成本柜计划').trigger('click'); await flushPromises() const original = vi.mocked(convertLastMileContainerToInboundPlans).mock.calls[0]! expect(original).toEqual([{ sourceContainerBookingId: '9007199254740993', snapshotVersion: 4, snapshotHash: 'snapshot-4' }, expect.any(String)]) expect(await guard()).toBe(true) await button(wrapper, '确认生成本柜计划').trigger('click'); await flushPromises() expect(vi.mocked(convertLastMileContainerToInboundPlans).mock.calls[1]![0]).toEqual(original[0]); expect(vi.mocked(convertLastMileContainerToInboundPlans).mock.calls[1]![1]).not.toBe(original[1]) expect(wrapper.emitted('updated')).toHaveLength(1); wrapper.unmount() }) it('uses the original resource recovery snapshot and key instead of creating new plans', async () => { vi.mocked(getContainerInboundPlanPreview).mockResolvedValue(preview({ pendingOrderCount: 0, resourceRetry: { idempotencyKey: 'original-key', snapshotVersion: 2, snapshotHash: 'original-snapshot' } })) vi.mocked(convertLastMileContainerToInboundPlans).mockResolvedValue([]) const wrapper = render(); await flushPromises(); await button(wrapper, '核对本柜入库计划预览').trigger('click'); await flushPromises() await button(wrapper, '重试原资源确认').trigger('click'); await flushPromises() expect(convertLastMileContainerToInboundPlans).toHaveBeenCalledWith({ sourceContainerBookingId: '9007199254740993', snapshotVersion: 2, snapshotHash: 'original-snapshot' }, 'original-key'); wrapper.unmount() }) it('does not submit a blocked preview', async () => { vi.mocked(getContainerInboundPlanPreview).mockResolvedValue(preview({ ready: false, blockedOrderCount: 1 })) const wrapper = render(); await flushPromises(); await button(wrapper, '核对本柜入库计划预览').trigger('click'); await flushPromises() expect(button(wrapper, '确认生成本柜计划').attributes('disabled')).toBeDefined(); expect(convertLastMileContainerToInboundPlans).not.toHaveBeenCalled(); wrapper.unmount() })