From 64649350141edb153e491ef729053b033c729b9c Mon Sep 17 00:00:00 2001 From: liuyihe <2513762445@qq.com> Date: Wed, 28 Oct 2026 13:59:39 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProcurementWorkflowPanel.test.ts | 15 ++++++------- .../components/ProcurementWorkflowPanel.vue | 8 ++++--- .../pages/PurchaseOrderDetailView.vue | 9 ++++---- .../procurementOutboundFees.test.ts | 21 +++++++------------ .../procurementOutboundFees.ts | 7 +++---- 5 files changed, 26 insertions(+), 34 deletions(-) diff --git a/src/modules/overseas-business/components/ProcurementWorkflowPanel.test.ts b/src/modules/overseas-business/components/ProcurementWorkflowPanel.test.ts index 8623cf85..761fafe9 100644 --- a/src/modules/overseas-business/components/ProcurementWorkflowPanel.test.ts +++ b/src/modules/overseas-business/components/ProcurementWorkflowPanel.test.ts @@ -18,7 +18,7 @@ vi.mock('@/core/unsaved-work/useUnsavedWorkRegistration', () => ({ useUnsavedWor enableAutoUnmount(afterEach) 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 beforeOutbound = vi.fn<() => Promise>() +const beforeOutbound = vi.fn<() => Promise>() function render(status = 'RECEIVING', permissions = ['receive-overseas', 'ship-customer']) { const pinia = createPinia(); setActivePinia(pinia) 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([]) }) 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 button = wrapper.findAll('button').find(button => button.text() === '创建出库单')! - beforeOutbound.mockRejectedValueOnce(new Error('请先添加费用明细并审核通过后再出库')) - 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) + beforeOutbound.mockResolvedValueOnce(true) await button.trigger('click'); await flushPromises() 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', () => { const receipt = render('RECEIVING', []) diff --git a/src/modules/overseas-business/components/ProcurementWorkflowPanel.vue b/src/modules/overseas-business/components/ProcurementWorkflowPanel.vue index 49bc293b..b83a33f5 100644 --- a/src/modules/overseas-business/components/ProcurementWorkflowPanel.vue +++ b/src/modules/overseas-business/components/ProcurementWorkflowPanel.vue @@ -14,7 +14,7 @@ import { dateTime, statusLabel } from '../procurementPresentation' import type { PlatformProcurement } from '../procurementTypes' import type { OverseasPurchaseWarehouseOption } from '../types' -const props = defineProps<{ detail: PlatformProcurement; workflow: ProcurementWorkflow; customerId: string; downloading?: string; beforeOutbound: () => Promise }>() +const props = defineProps<{ detail: PlatformProcurement; workflow: ProcurementWorkflow; customerId: string; downloading?: string; beforeOutbound: () => Promise }>() const emit = defineEmits<{ changed: []; download: [id: string] }>() const access = useAccessPreviewStore() const open = ref(false), saving = ref(false), uploading = ref(false), checkingFees = ref(false), error = ref(null) @@ -40,10 +40,12 @@ async function begin(name: string, batch?: ProcurementOutbound) { const current = ++dialogGeneration if (name === 'outbounds') { checkingFees.value = true - try { await props.beforeOutbound() } - catch (failure) { if (current === dialogGeneration) ElMessage.warning(getApiErrorDetails(failure, '费用校验失败').message); return } + let noFeeDetails = false + try { noFeeDetails = await props.beforeOutbound() === true } + catch { /* 费用查询仅用于提示,不能阻断采购出库。 */ } finally { checkingFees.value = false } if (current !== dialogGeneration) return + if (noFeeDetails) ElMessage.info('没有新增费用明细,仍可继续创建出库单') } 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 = [] diff --git a/src/modules/overseas-business/pages/PurchaseOrderDetailView.vue b/src/modules/overseas-business/pages/PurchaseOrderDetailView.vue index 246dbb0a..ad70ce24 100644 --- a/src/modules/overseas-business/pages/PurchaseOrderDetailView.vue +++ b/src/modules/overseas-business/pages/PurchaseOrderDetailView.vue @@ -22,7 +22,7 @@ import { overseasBusinessPermissions } from '../permissions' import { statusLabel, money, dateTime, text } from '../procurementPresentation' import type { PlatformProcurement } from '../procurementTypes' 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 purchaseId = computed(() => typeof route.params.purchaseId === 'string' ? route.params.purchaseId : '') const customerId = computed(() => typeof route.query.customerId === 'string' ? route.query.customerId : '') @@ -30,9 +30,10 @@ const detail = ref(null), workflow = ref(null), operationError = ref(null), downloading = ref('') const order = computed(() => detail.value?.customerView.order) const feePanel = ref>() -async function beforeOutbound() { - if (!feePanel.value) throw new Error('费用明细尚未加载完成,请稍后重试') - requireReviewedProcurementFees(await feePanel.value.getFees()) +async function beforeOutbound(): Promise { + if (!feePanel.value) return null + try { return !hasProcurementFeeDetails(await feePanel.value.getFees()) } + catch { return null } } const fields = computed(() => [ ['客户', order.value?.customerName], ['采购方向', directionLabel(order.value?.procurementDirection)], ['收货仓', order.value?.actualWarehouseName || order.value?.plannedWarehouseName], diff --git a/src/modules/overseas-business/procurementOutboundFees.test.ts b/src/modules/overseas-business/procurementOutboundFees.test.ts index 593f5d3d..e662a611 100644 --- a/src/modules/overseas-business/procurementOutboundFees.test.ts +++ b/src/modules/overseas-business/procurementOutboundFees.test.ts @@ -1,18 +1,11 @@ import { describe, expect, it } from 'vitest' -import { requireReviewedProcurementFees } from './procurementOutboundFees' +import { hasProcurementFeeDetails } from './procurementOutboundFees' -describe('procurement outbound fee gate', () => { - const approved = { direction: 'RECEIVABLE' as const, sourceStatus: 'ACTIVE', reviewStatus: 'APPROVED' } - it('requires a current receivable, excluding voided fees and payables', () => { - for (const rows of [[], [{ ...approved, sourceStatus: 'VOIDED' }], [{ ...approved, direction: 'PAYABLE' as const }]]) { - expect(() => requireReviewedProcurementFees(rows)).toThrow('请先添加费用明细') - } - }) - 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() +describe('procurement outbound fee notice', () => { + it('only reports when there is no current receivable fee detail', () => { + expect(hasProcurementFeeDetails([])).toBe(false) + expect(hasProcurementFeeDetails([{ direction: 'RECEIVABLE', sourceStatus: 'VOIDED' }])).toBe(false) + expect(hasProcurementFeeDetails([{ direction: 'PAYABLE', sourceStatus: 'ACTIVE' }])).toBe(false) + expect(hasProcurementFeeDetails([{ direction: 'RECEIVABLE', sourceStatus: 'ACTIVE' }])).toBe(true) }) }) diff --git a/src/modules/overseas-business/procurementOutboundFees.ts b/src/modules/overseas-business/procurementOutboundFees.ts index b542662f..cfe698ea 100644 --- a/src/modules/overseas-business/procurementOutboundFees.ts +++ b/src/modules/overseas-business/procurementOutboundFees.ts @@ -1,7 +1,6 @@ import type { OverseasFee } from './api/overseasFees' -export function requireReviewedProcurementFees(rows: Pick[]) { - const fees = rows.filter(fee => fee.direction === 'RECEIVABLE' && fee.sourceStatus !== 'VOIDED') - if (!fees.length) throw new Error('请先添加费用明细并审核通过后再出库') - if (fees.some(fee => fee.sourceStatus !== 'ACTIVE' || fee.reviewStatus !== 'APPROVED')) throw new Error('费用明细尚未全部审核通过,请先完成费用审核后再出库') +/** 缺少未作废应收时仅供页面提示,不作为创建出库单的门禁。 */ +export function hasProcurementFeeDetails(rows: Pick[]) { + return rows.some(fee => fee.direction === 'RECEIVABLE' && fee.sourceStatus !== 'VOIDED') }