diff --git a/src/modules/container-loading/components/ContainerCustomsPanel.test.ts b/src/modules/container-loading/components/ContainerCustomsPanel.test.ts index cba2c467..359b51c8 100644 --- a/src/modules/container-loading/components/ContainerCustomsPanel.test.ts +++ b/src/modules/container-loading/components/ContainerCustomsPanel.test.ts @@ -69,6 +69,22 @@ function buttonTexts(wrapper: Awaited>) { } describe('ContainerCustomsPanel', () => { + it('locks pre-entry uploads after customer confirmation before broker execution', async () => { + const value = customs('DECLARING', true) + value.documents[0]!.receiptStatus = 'CONFIRMED' + api.get.mockResolvedValue(value) + const wrapper = mount(ContainerCustomsPanel, { + props: { loadingPlanId: value.loadingPlanId, canList: true, canManage: true, departureState: 'WAITING_OTHER_GATES' }, + global: { plugins: [ElementPlus] }, + }) + await flushPromises() + expect(wrapper.text()).toContain('客户已确认预录单,待发送报关行执行') + for (const label of ['选择预录单', '提交预录单']) { + expect(wrapper.findAll('button').find(button => button.text() === label)?.attributes('disabled')).toBeDefined() + } + expect(wrapper.findAll('button').find(button => button.text() === '发送报关行执行')?.attributes('disabled')).toBeUndefined() + wrapper.unmount() + }) beforeEach(() => { vi.clearAllMocks() api.getDeclarationBatches.mockResolvedValue([]) @@ -264,7 +280,7 @@ describe('ContainerCustomsPanel', () => { await flushPromises() await wrapper.findAll('button').find(button => button.text() === '提交预录单')!.trigger('click') await flushPromises() - expect(api.upload).toHaveBeenCalledWith(document.documentId, selected, 7) + expect(api.upload).not.toHaveBeenCalled() wrapper.unmount() }) diff --git a/src/modules/container-loading/components/ContainerCustomsPanel.vue b/src/modules/container-loading/components/ContainerCustomsPanel.vue index 815f9a3b..ea5af30e 100644 --- a/src/modules/container-loading/components/ContainerCustomsPanel.vue +++ b/src/modules/container-loading/components/ContainerCustomsPanel.vue @@ -71,6 +71,7 @@ const allBuy = computed(() => customs.value?.hasActualCargo === true && customs. const brokerSubmission = computed(() => customs.value?.brokerSubmission || null) const brokerSubmitted = computed(() => Boolean(customs.value?.brokerSubmittedAt)) const allReceiptsConfirmed = computed(() => Boolean(customs.value?.documents.length) && customs.value?.documents.every((document) => document.receipt && document.receiptStatus === 'CONFIRMED') === true) +const receiptUploadLocked = computed(() => brokerSubmitted.value || customs.value?.documents.some((document) => document.receiptStatus === 'CONFIRMED') === true) const openDeclarationBatch = computed(() => declarationBatches.value.find((item) => ['READY', 'SUBMITTED', 'ACKNOWLEDGED', 'RESULT_PENDING'].includes(item.status)) || null) const statusText: Record = { WAITING_CUSTOMER_DOCUMENTS: '待客户补齐退税资料', READY_TO_START: '可开始报关', DECLARING: '报关中', COMPLETED: '报关已完成', @@ -85,7 +86,7 @@ function formatDateTime(value: string | null | undefined) { return Number.isNaN(date.getTime()) ? value : date.toLocaleString('zh-CN', { hour12: false }) } function selectReceipt(upload: UploadFile) { - if (!props.canManage || customs.value?.status !== 'DECLARING' || !brokerSubmission.value || brokerSubmitted.value) return + if (!props.canManage || customs.value?.status !== 'DECLARING' || !brokerSubmission.value || receiptUploadLocked.value || actionLoading.value) return receiptFile.value = upload.raw || null actionError.value = null } @@ -186,7 +187,7 @@ async function start() { } async function uploadReceipt() { - if (!props.canManage || customs.value?.status !== 'DECLARING' || !brokerSubmission.value || brokerSubmitted.value) return + if (!props.canManage || customs.value?.status !== 'DECLARING' || !brokerSubmission.value || receiptUploadLocked.value || actionLoading.value) return const selected = receiptFile.value if (!selected) { ElMessage.warning('请选择对应的预录单'); return } const documents = customs.value.documents @@ -325,12 +326,12 @@ onMounted(load)
客户订单号入仓码
{{ item.customerName }}{{ item.orderNo }}{{ item.inboundCode }}
-

已冻结退税资料、预录单与完税文件

{{ !brokerSubmission ? '首次发送后上传预录单' : '选择预录单' }}{{ receiptFile.name }}提交预录单
+

已冻结退税资料、预录单与完税文件

{{ !brokerSubmission ? '首次发送后上传预录单' : '选择预录单' }}{{ receiptFile.name }}提交预录单
- + diff --git a/src/modules/overseas-warehouse/pages/InboundDialogs.behavior.test.ts b/src/modules/overseas-warehouse/pages/InboundDialogs.behavior.test.ts index 4f5433e0..7f8d3b5d 100644 --- a/src/modules/overseas-warehouse/pages/InboundDialogs.behavior.test.ts +++ b/src/modules/overseas-warehouse/pages/InboundDialogs.behavior.test.ts @@ -140,6 +140,13 @@ describe('inbound appointment native dialogs', () => { }) describe('inbound plan and pallet move native dialogs', () => { + it('hides plan editing for first-leg generated inbound orders', async () => { + vi.mocked(listInboundOrders).mockResolvedValue({ records: [{ ...order, orderType: 'FIRST_LEG' }], total: 1, page: 1, size: 10, pages: 1 }) + const wrapper = await listPage() + expect(wrapper.findAll('.el-table__body-wrapper button').map(button => button.text())).not.toContain('编辑') + expect(wrapper.findAll('.el-table__body-wrapper button').map(button => button.text())).toContain('查看') + expect(listReceivingOverseasWarehouses).not.toHaveBeenCalled() + }) it('shows plan identity and saves the selected master warehouse with the internal version through fullscreen', async () => { const { dialog } = await openEditPlan() expect(dialog.get('[aria-label="入库计划来源"]').findAll('input')).toHaveLength(0) diff --git a/src/modules/overseas-warehouse/pages/InboundOrdersView.vue b/src/modules/overseas-warehouse/pages/InboundOrdersView.vue index b0931e8e..7a8d6d72 100644 --- a/src/modules/overseas-warehouse/pages/InboundOrdersView.vue +++ b/src/modules/overseas-warehouse/pages/InboundOrdersView.vue @@ -234,8 +234,9 @@ async function load(model: QueryModel = filters.value) { function search(model: QueryModel) { page.value = 1; void load(model) } function reset(model: QueryModel) { page.value = 1; void load(model) } function openDetail(source: object) { const row = source as InboundOrderListItem; void router.push({ name: 'overseas-inbound-order-detail', params: { orderId: row.orderNo }, query: { customerId: row.customerId } }) } -async function openPlanEdit(source: object) { const row = source as InboundOrderListItem; planEditTarget.value = row; planEditOpen.value = true; planEditLoading.value = true; mutationError.value = null; try { Object.assign(planEditForm, { warehouseId: row.warehouseId, warehouseCode: row.destinationWarehouseCode === '—' ? '' : row.destinationWarehouseCode, warehouseName: row.destinationWarehouseName === '—' ? '' : row.destinationWarehouseName, version: row.version }); if (!warehouses.value.length) warehouses.value = (await listReceivingOverseasWarehouses()).records } catch (error) { planEditOpen.value = false; mutationError.value = getApiErrorDetails(error, '海外仓列表加载失败,请重试') } finally { planEditLoading.value = false } } -async function submitPlanEdit() { if (planEditSaving.value || planEditLoading.value || !access.can(permission.inboundOrderEdit)) return; const target = planEditTarget.value; if (!target || !planEditForm.warehouseId) { ElMessage.warning('请选择目的仓'); return } planEditSaving.value = true; mutationError.value = null; try { await updateInboundPlan(target.orderNo, { customerId: target.customerId, overseasWarehouseId: planEditForm.warehouseId, warehouseCode: planEditForm.warehouseCode, warehouseName: planEditForm.warehouseName, version: planEditForm.version }); planEditOpen.value = false; ElMessage.success('入库单已更新'); await load() } catch (error) { mutationError.value = getApiErrorDetails(error, '入库单更新失败,请重试') } finally { planEditSaving.value = false } } +function canEditPlan(row: InboundOrderListItem) { return access.can(permission.inboundOrderEdit) && row.status === '待入库' && row.type !== '头程订单' } +async function openPlanEdit(source: object) { const row = source as InboundOrderListItem; if (!canEditPlan(row)) return; planEditTarget.value = row; planEditOpen.value = true; planEditLoading.value = true; mutationError.value = null; try { Object.assign(planEditForm, { warehouseId: row.warehouseId, warehouseCode: row.destinationWarehouseCode === '—' ? '' : row.destinationWarehouseCode, warehouseName: row.destinationWarehouseName === '—' ? '' : row.destinationWarehouseName, version: row.version }); if (!warehouses.value.length) warehouses.value = (await listReceivingOverseasWarehouses()).records } catch (error) { planEditOpen.value = false; mutationError.value = getApiErrorDetails(error, '海外仓列表加载失败,请重试') } finally { planEditLoading.value = false } } +async function submitPlanEdit() { if (planEditSaving.value || planEditLoading.value || !access.can(permission.inboundOrderEdit)) return; const target = planEditTarget.value; if (!target || !canEditPlan(target)) return; if (!planEditForm.warehouseId) { ElMessage.warning('请选择目的仓'); return } planEditSaving.value = true; mutationError.value = null; try { await updateInboundPlan(target.orderNo, { customerId: target.customerId, overseasWarehouseId: planEditForm.warehouseId, warehouseCode: planEditForm.warehouseCode, warehouseName: planEditForm.warehouseName, version: planEditForm.version }); planEditOpen.value = false; ElMessage.success('入库单已更新'); await load() } catch (error) { mutationError.value = getApiErrorDetails(error, '入库单更新失败,请重试') } finally { planEditSaving.value = false } } onMounted(() => { void load() }) @@ -250,7 +251,7 @@ onMounted(() => { void load() }) - +
操作关联入仓码报关行状态客户退税资料预录单真实报关完税文件客户预录单情况
{{ document.finalDocument ? '覆盖完税文件' : '上传完税文件' }}提交
只读
{{ document.inboundCodes.join('、') || '—' }}{{ brokerSubmitted ? '已发送报关行执行' : brokerSubmission ? '已首次发送,等待预录单确认' : '待首次发送报关行' }}{{ brokerSubmitted ? '已发送报关行执行' : document.receiptStatus === 'CONFIRMED' ? '客户已确认预录单,待发送报关行执行' : brokerSubmission ? '已首次发送,等待预录单确认' : '待首次发送报关行' }}
{{ document.source.fileName }}下载
{{ document.receipt.fileName }}下载
未上传
{{ document.finalDocument.fileName }}下载
未上传
{{ documentCustomers(document) }}{{ document.receipt ? (document.receiptStatus === 'CONFIRMED' ? '客户已确认预录单' : document.receiptStatus === 'REJECTED' ? '客户已打回预录单' : '待客户确认预录单') : '待上传预录单' }}