This commit is contained in:
2026-10-30 11:19:57 +08:00
parent a2ef3542f4
commit 8cc8b4fb94
13 changed files with 218 additions and 74 deletions
@@ -1,7 +1,7 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import ElementPlus from 'element-plus'
import ElementPlus, { ElInputNumber } from 'element-plus'
import AppDialog from '@/shared/components/AppDialog.vue'
import { useAuthStore } from '@/core/auth/auth'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
@@ -9,14 +9,12 @@ import ProcurementWorkflowPanel from './ProcurementWorkflowPanel.vue'
import type { PlatformProcurement } from '../procurementTypes'
import type { ProcurementWorkflow } from '../api/procurementWorkflow'
import { submitProcurementWorkflow } from '../api/procurementWorkflow'
import { submitProcurementCommand, uploadProcurementFile } from '../api/procurementCommands'
import { listOverseasPurchaseWarehouseOptions } from '../api/platformOverseasBusiness'
import { uploadProcurementFile } from '../api/procurementCommands'
vi.mock('../api/procurementWorkflow', () => ({ submitProcurementWorkflow: vi.fn() }))
vi.mock('../api/procurementCommands', () => ({ submitProcurementCommand: vi.fn(), uploadProcurementFile: vi.fn() }))
vi.mock('../api/platformOverseasBusiness', () => ({ listOverseasPurchaseWarehouseOptions: vi.fn() }))
vi.mock('../api/procurementCommands', () => ({ uploadProcurementFile: vi.fn() }))
vi.mock('@/core/unsaved-work/useUnsavedWorkRegistration', () => ({ useUnsavedWorkRegistration: () => ({ captureBaseline: vi.fn() }) }))
enableAutoUnmount(afterEach)
const workflow = (status = 'RECEIVING'): ProcurementWorkflow => ({ orderNo: 'OP-1', version: 4, status, receivedQuantity: 5, stockQuantity: 5, reservedQuantity: 0, availableQuantity: 5, receipts: [], outbounds: [] })
const workflow = (status = 'RECEIVING'): ProcurementWorkflow => ({ orderNo: 'OP-1', version: 4, status, receivedQuantity: 5, stockQuantity: 5, reservedQuantity: 0, availableQuantity: 5, rejectionReason: null, photosNeedUpdate: false, receipts: [], outbounds: [] })
const detail = { customerView: { order: { purchaseQuantity: 20 } }, files: [] } as unknown as PlatformProcurement
const beforeOutbound = vi.fn<() => Promise<boolean | null>>()
function render(status = 'RECEIVING', permissions = ['receive-overseas', 'ship-customer']) {
@@ -24,8 +22,70 @@ function render(status = 'RECEIVING', permissions = ['receive-overseas', 'ship-c
const auth = useAuthStore(); auth.token = 'test'; auth.permissions = permissions.map(value => `platform:overseas-business:purchase:${value}`); useAccessPreviewStore().initializeAfterLogin()
return mount(ProcurementWorkflowPanel, { props: { detail, workflow: workflow(status), customerId: '1001', beforeOutbound }, global: { plugins: [pinia, ElementPlus] } })
}
beforeEach(() => { vi.clearAllMocks(); beforeOutbound.mockResolvedValue(null); vi.mocked(submitProcurementWorkflow).mockResolvedValue(workflow()); vi.mocked(listOverseasPurchaseWarehouseOptions).mockResolvedValue([]) })
beforeEach(() => { vi.clearAllMocks(); beforeOutbound.mockResolvedValue(null); vi.mocked(submitProcurementWorkflow).mockResolvedValue(workflow()) })
describe('simplified procurement operations', () => {
it('allows a rejected purchase to be resubmitted without uploading photos and clears the old reason after success', async () => {
const wrapper = render()
const rejected = { ...workflow(), rejectionReason: '请核对', photosNeedUpdate: true, receipts: [
{ id: 'r-1', quantity: 2, fileIds: ['old-1'], receivedAt: '' },
{ id: 'r-2', quantity: 3, fileIds: ['old-2'], receivedAt: '' },
] }
await wrapper.setProps({ workflow: rejected }); await flushPromises()
const button = wrapper.findAll('button').find(button => button.text() === '采购完成')!
expect(button.attributes('disabled')).toBeUndefined()
expect(wrapper.text()).toContain('请核对')
await button.trigger('click'); await flushPromises()
wrapper.findComponent(AppDialog).vm.$emit('confirm'); await flushPromises()
expect(uploadProcurementFile).not.toHaveBeenCalled()
expect(submitProcurementWorkflow).toHaveBeenCalledWith('OP-1', '1001', 'request-confirmation', { version: 4, idempotencyKey: expect.any(String) })
expect(wrapper.emitted('changed')).toHaveLength(1)
await wrapper.setProps({ workflow: { ...rejected, status: 'WAIT_CUSTOMER_CONFIRM', version: 5, rejectionReason: null, photosNeedUpdate: false } })
expect(wrapper.text()).not.toContain('客户驳回原因')
})
it('accepts the first and subsequent receipt batches using the remaining purchase quantity', async () => {
const wrapper = render()
const receive = () => wrapper.findAll('button').find(button => button.text() === '登记收货')!
await wrapper.setProps({ workflow: { ...workflow(), receivedQuantity: 0, stockQuantity: 0, availableQuantity: 0 } })
await receive().trigger('click'); await flushPromises()
const quantity = wrapper.findComponent(ElInputNumber)
const increment = quantity.find('.el-input-number__increase')
expect(increment.attributes('aria-disabled')).not.toBe('true')
await increment.trigger('mousedown', { button: 0 })
document.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }))
expect(quantity.props('modelValue')).toBe(2)
await quantity.find('input').setValue('10')
await quantity.find('input').trigger('blur')
expect(quantity.props('modelValue')).toBe(10)
const form = wrapper.findComponent({ name: 'ElForm' }).props('model')
form.fileIds = ['photo-1']
const dialog = wrapper.findComponent(AppDialog)
dialog.vm.$emit('confirm'); await flushPromises()
expect(submitProcurementWorkflow).toHaveBeenLastCalledWith('OP-1', '1001', 'receipts', expect.objectContaining({ version: 4, quantity: 10, fileIds: ['photo-1'] }))
expect(wrapper.emitted('changed')).toHaveLength(1)
await wrapper.setProps({ workflow: { ...workflow(), version: 5, receivedQuantity: 10, stockQuantity: 10, availableQuantity: 10 } })
await receive().trigger('click'); await flushPromises()
await wrapper.findComponent(ElInputNumber).find('input').setValue('10')
await wrapper.findComponent(ElInputNumber).find('input').trigger('blur')
form.fileIds = ['photo-2']
dialog.vm.$emit('confirm'); await flushPromises()
expect(submitProcurementWorkflow).toHaveBeenLastCalledWith('OP-1', '1001', 'receipts', expect.objectContaining({ version: 5, quantity: 10, fileIds: ['photo-2'] }))
expect(wrapper.emitted('changed')).toHaveLength(2)
const calls = vi.mocked(submitProcurementWorkflow).mock.calls
expect(calls[0]![3].idempotencyKey).not.toBe(calls[1]![3].idempotencyKey)
})
it.each([
{ received: 1, input: '10', expected: 10 },
{ received: 18, input: '3', expected: 2 },
])('limits receipts by remaining purchase quantity after $received units were received', async ({ received, input, expected }) => {
const wrapper = render()
await wrapper.setProps({ workflow: { ...workflow(), receivedQuantity: received, stockQuantity: received, availableQuantity: received } })
await wrapper.findAll('button').find(button => button.text() === '登记收货')!.trigger('click'); await flushPromises()
const quantity = wrapper.findComponent(ElInputNumber)
await quantity.find('input').setValue(input)
await quantity.find('input').trigger('blur')
expect(quantity.props('modelValue')).toBe(expected)
})
it('allows outbound when the fee check reports missing or unreviewed details', async () => {
const wrapper = render('CONFIRMED')
const button = wrapper.findAll('button').find(button => button.text() === '创建出库单')!
@@ -68,14 +128,6 @@ describe('simplified procurement operations', () => {
expect(form.trackingNo).toBe('TRACK-1'); expect(form.quantity).toBe(3)
form.quantity = 6; dialog.vm.$emit('confirm'); await flushPromises(); expect(submitProcurementWorkflow).toHaveBeenCalledTimes(2)
})
it('requires a rejection reason and preserves the current version and customer scope', async () => {
const wrapper = render('PENDING_REVIEW', ['review'])
await wrapper.findAll('button').find(button => button.text() === '审核')!.trigger('click'); await flushPromises()
const form = wrapper.findComponent({ name: 'ElForm' }).props('model'), dialog = wrapper.findComponent(AppDialog)
form.decision = 'REJECT'; dialog.vm.$emit('confirm'); await flushPromises(); expect(submitProcurementCommand).not.toHaveBeenCalled()
form.reason = '商品无法采购'; dialog.vm.$emit('confirm'); await flushPromises()
expect(submitProcurementCommand).toHaveBeenCalledWith('OP-1', '1001', 'review', expect.objectContaining({ version: 4, decision: 'REJECT', reason: '商品无法采购', purchaseWarehouseId: undefined }))
})
it('only completes the selected delivering batch after signature evidence is supplied', async () => {
const wrapper = render('DELIVERING')
await wrapper.setProps({ workflow: { ...workflow('DELIVERING'), outbounds: [{ id: '9007199254740993', outboundNo: 'OUT-1', status: 'DELIVERING', quantity: 3, trackingNo: 'TRACK-1', fileIds: [], createdAt: '', completedAt: null }] } })