优化
This commit is contained in:
@@ -18,7 +18,7 @@ vi.mock('@/core/unsaved-work/useUnsavedWorkRegistration', () => ({ useUnsavedWor
|
|||||||
enableAutoUnmount(afterEach)
|
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, receipts: [], outbounds: [] })
|
||||||
const detail = { customerView: { order: { purchaseQuantity: 20 } }, files: [] } as unknown as PlatformProcurement
|
const detail = { customerView: { order: { purchaseQuantity: 20 } }, files: [] } as unknown as PlatformProcurement
|
||||||
const beforeOutbound = vi.fn<() => Promise<void>>()
|
const beforeOutbound = vi.fn<() => Promise<boolean | null>>()
|
||||||
function render(status = 'RECEIVING', permissions = ['receive-overseas', 'ship-customer']) {
|
function render(status = 'RECEIVING', permissions = ['receive-overseas', 'ship-customer']) {
|
||||||
const pinia = createPinia(); setActivePinia(pinia)
|
const pinia = createPinia(); setActivePinia(pinia)
|
||||||
const auth = useAuthStore(); auth.token = 'test'; auth.permissions = permissions.map(value => `platform:overseas-business:purchase:${value}`); useAccessPreviewStore().initializeAfterLogin()
|
const auth = useAuthStore(); auth.token = 'test'; auth.permissions = permissions.map(value => `platform:overseas-business:purchase:${value}`); useAccessPreviewStore().initializeAfterLogin()
|
||||||
@@ -26,18 +26,15 @@ function render(status = 'RECEIVING', permissions = ['receive-overseas', 'ship-c
|
|||||||
}
|
}
|
||||||
beforeEach(() => { vi.clearAllMocks(); beforeOutbound.mockResolvedValue(undefined); vi.mocked(submitProcurementWorkflow).mockResolvedValue(workflow()); vi.mocked(listOverseasPurchaseWarehouseOptions).mockResolvedValue([]) })
|
beforeEach(() => { vi.clearAllMocks(); beforeOutbound.mockResolvedValue(undefined); vi.mocked(submitProcurementWorkflow).mockResolvedValue(workflow()); vi.mocked(listOverseasPurchaseWarehouseOptions).mockResolvedValue([]) })
|
||||||
describe('simplified procurement operations', () => {
|
describe('simplified procurement operations', () => {
|
||||||
it('blocks the outbound dialog when fees are missing or not approved and permits retry after approval', async () => {
|
it('allows outbound when the fee check reports missing or unreviewed details', async () => {
|
||||||
const wrapper = render('CONFIRMED')
|
const wrapper = render('CONFIRMED')
|
||||||
const button = wrapper.findAll('button').find(button => button.text() === '创建出库单')!
|
const button = wrapper.findAll('button').find(button => button.text() === '创建出库单')!
|
||||||
beforeOutbound.mockRejectedValueOnce(new Error('请先添加费用明细并审核通过后再出库'))
|
beforeOutbound.mockResolvedValueOnce(true)
|
||||||
await button.trigger('click'); await flushPromises()
|
|
||||||
expect(wrapper.findComponent(AppDialog).props('modelValue')).toBe(false)
|
|
||||||
expect(submitProcurementWorkflow).not.toHaveBeenCalled()
|
|
||||||
beforeOutbound.mockRejectedValueOnce(new Error('费用明细尚未全部审核通过'))
|
|
||||||
await button.trigger('click'); await flushPromises()
|
|
||||||
expect(wrapper.findComponent(AppDialog).props('modelValue')).toBe(false)
|
|
||||||
await button.trigger('click'); await flushPromises()
|
await button.trigger('click'); await flushPromises()
|
||||||
expect(wrapper.findComponent(AppDialog).props('modelValue')).toBe(true)
|
expect(wrapper.findComponent(AppDialog).props('modelValue')).toBe(true)
|
||||||
|
await button.trigger('click'); await flushPromises()
|
||||||
|
expect(wrapper.findComponent(AppDialog).props('modelValue')).toBe(true)
|
||||||
|
expect(beforeOutbound).toHaveBeenCalledTimes(2)
|
||||||
})
|
})
|
||||||
it('gates receipt and outbound actions with the existing operation permissions', () => {
|
it('gates receipt and outbound actions with the existing operation permissions', () => {
|
||||||
const receipt = render('RECEIVING', [])
|
const receipt = render('RECEIVING', [])
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { dateTime, statusLabel } from '../procurementPresentation'
|
|||||||
import type { PlatformProcurement } from '../procurementTypes'
|
import type { PlatformProcurement } from '../procurementTypes'
|
||||||
import type { OverseasPurchaseWarehouseOption } from '../types'
|
import type { OverseasPurchaseWarehouseOption } from '../types'
|
||||||
|
|
||||||
const props = defineProps<{ detail: PlatformProcurement; workflow: ProcurementWorkflow; customerId: string; downloading?: string; beforeOutbound: () => Promise<void> }>()
|
const props = defineProps<{ detail: PlatformProcurement; workflow: ProcurementWorkflow; customerId: string; downloading?: string; beforeOutbound: () => Promise<boolean | null> }>()
|
||||||
const emit = defineEmits<{ changed: []; download: [id: string] }>()
|
const emit = defineEmits<{ changed: []; download: [id: string] }>()
|
||||||
const access = useAccessPreviewStore()
|
const access = useAccessPreviewStore()
|
||||||
const open = ref(false), saving = ref(false), uploading = ref(false), checkingFees = ref(false), error = ref<ApiErrorDetails | null>(null)
|
const open = ref(false), saving = ref(false), uploading = ref(false), checkingFees = ref(false), error = ref<ApiErrorDetails | null>(null)
|
||||||
@@ -40,10 +40,12 @@ async function begin(name: string, batch?: ProcurementOutbound) {
|
|||||||
const current = ++dialogGeneration
|
const current = ++dialogGeneration
|
||||||
if (name === 'outbounds') {
|
if (name === 'outbounds') {
|
||||||
checkingFees.value = true
|
checkingFees.value = true
|
||||||
try { await props.beforeOutbound() }
|
let noFeeDetails = false
|
||||||
catch (failure) { if (current === dialogGeneration) ElMessage.warning(getApiErrorDetails(failure, '费用校验失败').message); return }
|
try { noFeeDetails = await props.beforeOutbound() === true }
|
||||||
|
catch { /* 费用查询仅用于提示,不能阻断采购出库。 */ }
|
||||||
finally { checkingFees.value = false }
|
finally { checkingFees.value = false }
|
||||||
if (current !== dialogGeneration) return
|
if (current !== dialogGeneration) return
|
||||||
|
if (noFeeDetails) ElMessage.info('没有新增费用明细,仍可继续创建出库单')
|
||||||
}
|
}
|
||||||
action.value = name; batchId.value = batch?.id || ''; error.value = null; warehouseLoading.value = false
|
action.value = name; batchId.value = batch?.id || ''; error.value = null; warehouseLoading.value = false
|
||||||
Object.assign(form, { quantity: 1, trackingNo: '', fileIds: [], warehouseId: '', decision: 'APPROVE', reason: '' }); uploadedNames.value = []
|
Object.assign(form, { quantity: 1, trackingNo: '', fileIds: [], warehouseId: '', decision: 'APPROVE', reason: '' }); uploadedNames.value = []
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { overseasBusinessPermissions } from '../permissions'
|
|||||||
import { statusLabel, money, dateTime, text } from '../procurementPresentation'
|
import { statusLabel, money, dateTime, text } from '../procurementPresentation'
|
||||||
import type { PlatformProcurement } from '../procurementTypes'
|
import type { PlatformProcurement } from '../procurementTypes'
|
||||||
import { directionLabel } from '../procurementDirection'
|
import { directionLabel } from '../procurementDirection'
|
||||||
import { requireReviewedProcurementFees } from '../procurementOutboundFees'
|
import { hasProcurementFeeDetails } from '../procurementOutboundFees'
|
||||||
const route = useRoute(); const router = useRouter(); const auth = useAuthStore(); const access = useAccessPreviewStore()
|
const route = useRoute(); const router = useRouter(); const auth = useAuthStore(); const access = useAccessPreviewStore()
|
||||||
const purchaseId = computed(() => typeof route.params.purchaseId === 'string' ? route.params.purchaseId : '')
|
const purchaseId = computed(() => typeof route.params.purchaseId === 'string' ? route.params.purchaseId : '')
|
||||||
const customerId = computed(() => typeof route.query.customerId === 'string' ? route.query.customerId : '')
|
const customerId = computed(() => typeof route.query.customerId === 'string' ? route.query.customerId : '')
|
||||||
@@ -30,9 +30,10 @@ const detail = ref<PlatformProcurement | null>(null), workflow = ref<Procurement
|
|||||||
const loadError = ref<ApiErrorDetails | null>(null), operationError = ref<ApiErrorDetails | null>(null), downloading = ref('')
|
const loadError = ref<ApiErrorDetails | null>(null), operationError = ref<ApiErrorDetails | null>(null), downloading = ref('')
|
||||||
const order = computed(() => detail.value?.customerView.order)
|
const order = computed(() => detail.value?.customerView.order)
|
||||||
const feePanel = ref<InstanceType<typeof BusinessDocumentFees>>()
|
const feePanel = ref<InstanceType<typeof BusinessDocumentFees>>()
|
||||||
async function beforeOutbound() {
|
async function beforeOutbound(): Promise<boolean | null> {
|
||||||
if (!feePanel.value) throw new Error('费用明细尚未加载完成,请稍后重试')
|
if (!feePanel.value) return null
|
||||||
requireReviewedProcurementFees(await feePanel.value.getFees())
|
try { return !hasProcurementFeeDetails(await feePanel.value.getFees()) }
|
||||||
|
catch { return null }
|
||||||
}
|
}
|
||||||
const fields = computed(() => [
|
const fields = computed(() => [
|
||||||
['客户', order.value?.customerName], ['采购方向', directionLabel(order.value?.procurementDirection)], ['收货仓', order.value?.actualWarehouseName || order.value?.plannedWarehouseName],
|
['客户', order.value?.customerName], ['采购方向', directionLabel(order.value?.procurementDirection)], ['收货仓', order.value?.actualWarehouseName || order.value?.plannedWarehouseName],
|
||||||
|
|||||||
@@ -1,18 +1,11 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import { requireReviewedProcurementFees } from './procurementOutboundFees'
|
import { hasProcurementFeeDetails } from './procurementOutboundFees'
|
||||||
|
|
||||||
describe('procurement outbound fee gate', () => {
|
describe('procurement outbound fee notice', () => {
|
||||||
const approved = { direction: 'RECEIVABLE' as const, sourceStatus: 'ACTIVE', reviewStatus: 'APPROVED' }
|
it('only reports when there is no current receivable fee detail', () => {
|
||||||
it('requires a current receivable, excluding voided fees and payables', () => {
|
expect(hasProcurementFeeDetails([])).toBe(false)
|
||||||
for (const rows of [[], [{ ...approved, sourceStatus: 'VOIDED' }], [{ ...approved, direction: 'PAYABLE' as const }]]) {
|
expect(hasProcurementFeeDetails([{ direction: 'RECEIVABLE', sourceStatus: 'VOIDED' }])).toBe(false)
|
||||||
expect(() => requireReviewedProcurementFees(rows)).toThrow('请先添加费用明细')
|
expect(hasProcurementFeeDetails([{ direction: 'PAYABLE', sourceStatus: 'ACTIVE' }])).toBe(false)
|
||||||
}
|
expect(hasProcurementFeeDetails([{ direction: 'RECEIVABLE', sourceStatus: 'ACTIVE' }])).toBe(true)
|
||||||
})
|
|
||||||
it('requires every current fee to be active and approved', () => {
|
|
||||||
for (const reviewStatus of ['PENDING', 'REJECTED']) {
|
|
||||||
expect(() => requireReviewedProcurementFees([approved, { ...approved, reviewStatus }])).toThrow('尚未全部审核通过')
|
|
||||||
}
|
|
||||||
expect(() => requireReviewedProcurementFees([approved, { ...approved, sourceStatus: 'VOIDING' }])).toThrow('尚未全部审核通过')
|
|
||||||
expect(() => requireReviewedProcurementFees([approved, { ...approved, sourceStatus: 'VOIDED', reviewStatus: 'REJECTED' }])).not.toThrow()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { OverseasFee } from './api/overseasFees'
|
import type { OverseasFee } from './api/overseasFees'
|
||||||
|
|
||||||
export function requireReviewedProcurementFees(rows: Pick<OverseasFee, 'direction' | 'sourceStatus' | 'reviewStatus'>[]) {
|
/** 缺少未作废应收时仅供页面提示,不作为创建出库单的门禁。 */
|
||||||
const fees = rows.filter(fee => fee.direction === 'RECEIVABLE' && fee.sourceStatus !== 'VOIDED')
|
export function hasProcurementFeeDetails(rows: Pick<OverseasFee, 'direction' | 'sourceStatus'>[]) {
|
||||||
if (!fees.length) throw new Error('请先添加费用明细并审核通过后再出库')
|
return rows.some(fee => fee.direction === 'RECEIVABLE' && fee.sourceStatus !== 'VOIDED')
|
||||||
if (fees.some(fee => fee.sourceStatus !== 'ACTIVE' || fee.reviewStatus !== 'APPROVED')) throw new Error('费用明细尚未全部审核通过,请先完成费用审核后再出库')
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user