This commit is contained in:
2026-08-17 16:45:46 +08:00
parent 4a9438315c
commit d95c90bd1a
48 changed files with 1217 additions and 374 deletions
@@ -50,7 +50,9 @@ const container = {
}
const plan = {
id: '99', bookingId: container.id, planNo: 'LP-99', status: 'EDITING' as const, containerNo: 'MSCU-1', voyageNo: null, version: 2,
items: [{ id: '101', loadingPlanId: '99', accountId: '1', inboundId: '2', inboundDetailId: '3', orderId: '4', orderNo: 'ORD-1', inboundCode: 'IN-1', customerName: '示例企业', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', skuCode: 'SKU-1', productNameZh: '服装', packageCount: 6, productQuantity: 6, grossWeightKg: 60, volumeCbm: 1.2, loadSequence: 1, version: 1 }],
canAssign: true, canEdit: true, blockedReason: null, finalCompletionStatus: 'NOT_STARTED' as const,
pdaTaskId: null, pdaTaskStatus: null, pdaTaskVersion: null,
items: [{ id: '101', loadingPlanId: '99', accountId: '1', inboundId: '2', inboundDetailId: '3', orderId: '4', orderNo: 'ORD-1', inboundCode: 'IN-1', customerName: '示例企业', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', skuCode: 'SKU-1', productNameZh: '服装', packageCount: 6, productQuantity: 6, grossWeightKg: 60, volumeCbm: 1.2, loadSequence: 1, version: 1, pickedPackageCount: 0, minPackageCount: 1, canAdjust: true, canRemove: true }],
}
describe('GlobalSmartLoadingView', () => {
@@ -191,7 +193,7 @@ describe('GlobalSmartLoadingView', () => {
expect(wrapper.text()).not.toContain('是否退税')
})
it('prefers an editable container on first load so pushed plans do not show a false editing error', async () => {
it('prefers an editing container on first load while keeping loading containers available', async () => {
const pushed = { ...container, id: '9007199254740995', planStatus: 'LOADING' as const }
vi.mocked(listContainers).mockResolvedValue({ records: [pushed, container], page: 1, size: 100, total: 2, pages: 1 })
const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } })
@@ -224,16 +226,19 @@ describe('GlobalSmartLoadingView', () => {
expect(waitingOption.text()).not.toContain('拣货中')
})
it('excludes picking containers from the empty-container workbench', async () => {
it('keeps picking containers available when the plan capability allows increments', async () => {
const pushed = { ...container, id: '9007199254740995', planId: '9007199254740996', cabinetNo: 'CB-2', planStatus: 'LOADING' as const }
vi.mocked(listContainers).mockResolvedValue({ records: [pushed], page: 1, size: 100, total: 1, pages: 1 })
vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValue({ ...plan, bookingId: pushed.id, status: 'LOADING', pdaTaskId: '700', pdaTaskStatus: 'LOADING', pdaTaskVersion: 4 })
const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } })
await flushPromises()
expect(getSmartLoadingPlanByBooking).not.toHaveBeenCalled()
expect(listSmartLoadingCandidates).not.toHaveBeenCalled()
expect(wrapper.findAll('.container-option')).toHaveLength(0)
expect(wrapper.text()).not.toContain('CB-2')
expect(getSmartLoadingPlanByBooking).toHaveBeenCalledWith(pushed.id)
expect(listSmartLoadingCandidates).toHaveBeenCalledWith(pushed.id, pushed.warehouseId)
expect(wrapper.findAll('.container-option')).toHaveLength(1)
expect(wrapper.text()).toContain('CB-2')
expect(wrapper.text()).toContain('已下发 PDA,仍可维护未完成的装柜明细')
expect(wrapper.text()).toContain('刷新 PDA 明细与版本')
})
it('hides completed containers from the smart loading workbench', async () => {
@@ -267,7 +272,7 @@ describe('GlobalSmartLoadingView', () => {
expect(recommendSmartLoading).toHaveBeenCalledWith(plan.id, 2)
expect(getSmartLoadingPlanByBooking).toHaveBeenCalledTimes(2)
expect(warningMessage).toHaveBeenCalledWith('方案已被其他人修改,已刷新最新版本,请重新确认后提交')
expect(warningMessage).toHaveBeenCalledWith('计划或 PDA 明细已变化,已刷新最新版本并保留筛选条件,请重新操作')
expect(wrapper.text()).not.toContain('装柜操作失败')
expect(wrapper.text()).not.toContain('request-1')
})
@@ -307,7 +312,7 @@ describe('GlobalSmartLoadingView', () => {
const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } })
await flushPromises()
const removeAllButton = wrapper.findAll('button').find((button) => button.text().includes('全部移除'))
const removeAllButton = wrapper.findAll('button').find((button) => button.text().includes('移除未拣'))
expect(removeAllButton).toBeDefined()
await removeAllButton!.trigger('click')
await flushPromises()
@@ -317,6 +322,43 @@ describe('GlobalSmartLoadingView', () => {
expect(wrapper.text()).toContain('当前柜次暂无已装货物')
})
it('uses item capabilities to protect picked quantities and removal', async () => {
vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValue({
...plan,
status: 'LOADING',
pdaTaskId: '700',
pdaTaskStatus: 'LOADING',
pdaTaskVersion: 4,
items: [{ ...plan.items[0], pickedPackageCount: 3, minPackageCount: 3, canAdjust: true, canRemove: false }],
})
const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } })
await flushPromises()
expect(wrapper.text()).toContain('已拣 3 箱')
expect(wrapper.findAll('.row-actions .el-button--danger')).toHaveLength(0)
const quantityInput = wrapper.findAllComponents(ElInputNumber).find((input) => input.props('modelValue') === 6)
expect(quantityInput?.props('min')).toBe(3)
expect(wrapper.findAll('button').some((button) => button.text().includes('移除未拣'))).toBe(false)
})
it('renders final completion capability as read-only with the server reason', async () => {
vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValue({
...plan,
canAssign: false,
canEdit: false,
blockedReason: '正在校验装柜证据',
finalCompletionStatus: 'IN_PROGRESS',
})
const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } })
await flushPromises()
expect(wrapper.text()).toContain('正在最终确认,装柜方案暂时只读')
expect(wrapper.text()).toContain('正在校验装柜证据')
expect(wrapper.text()).not.toContain('请先完成拖车')
expect(wrapper.findAll('.row-actions button')).toHaveLength(0)
expect(wrapper.findAll('button').some((button) => button.text().includes('加入'))).toBe(false)
})
it('limits adjusted package count to the assigned count plus remaining availability', async () => {
vi.mocked(listSmartLoadingCandidates).mockResolvedValue([{ accountId: '1', inboundId: '2', inboundDetailId: '3', orderId: '4', orderNo: 'ORD-1', inboundCode: 'IN-1', warehouseId: '8', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '示例企业', goodsName: '服装', availablePackageCount: 4, unitWeightKg: 10, unitVolumeCbm: 0.2 }])
const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } })
@@ -360,7 +402,7 @@ describe('GlobalSmartLoadingView', () => {
expect(source).not.toContain('channel.containerCount')
expect(source).toContain('class="compact-summary" aria-label="当前渠道装柜统计"')
expect(source).toContain('<dt>候选货物</dt>')
expect(source).toContain('<dt>空柜</dt>')
expect(source).toContain('<dt>柜次</dt>')
expect(source).toContain('<dt>已装</dt>')
expect(source).toContain('<dt>总重量</dt>')
expect(source).toContain('<dt>总体积</dt>')