From 1afc7034cdd74210506523a452f2180497b42857 Mon Sep 17 00:00:00 2001 From: liuyihe <2513762445@qq.com> Date: Tue, 3 Nov 2026 16:15:24 +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 --- .../api/dispatchLooseboardExecution.ts | 7 ++-- .../DispatchDocumentsDialog.test.ts | 23 ++++++++++++ .../components/DispatchDocumentsDialog.vue | 5 +-- .../DispatchLoadingDocuments.test.ts | 4 +-- .../components/DispatchLoadingDocuments.vue | 2 -- .../DispatchLooseboardExecution.test.ts | 35 ++++++++++++++++--- .../DispatchLooseboardExecution.vue | 27 +++++++++----- 7 files changed, 83 insertions(+), 20 deletions(-) diff --git a/src/modules/overseas-warehouse/api/dispatchLooseboardExecution.ts b/src/modules/overseas-warehouse/api/dispatchLooseboardExecution.ts index 48a4e03d..8fbee116 100644 --- a/src/modules/overseas-warehouse/api/dispatchLooseboardExecution.ts +++ b/src/modules/overseas-warehouse/api/dispatchLooseboardExecution.ts @@ -2,13 +2,16 @@ import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http' export type LooseboardPhotoPhase = 'PICKUP' | 'ARRIVAL' export interface LooseboardPhoto { fileId: string; fileName: string; contentType: string; sizeBytes: string | number } -export interface LooseboardExecutionItem { itemId: string; customerId: string; orderNo: string; destination: string | null; packages: number; pickupPhotos: LooseboardPhoto[]; arrivalPhotos: LooseboardPhoto[]; canPickup: boolean; canArrive: boolean } -export interface LooseboardExecution { dispatchNo: string; recordVersion: string | number; stage: string; direct: boolean; items: LooseboardExecutionItem[] } +export interface LooseboardExecutionItem { itemId: string; customerId: string; orderNo: string; destination: string | null; packages: number; pickupPhotos: LooseboardPhoto[]; arrivalPhotos: LooseboardPhoto[]; canPickup: boolean; canArrive: boolean; warehouseNotice?: string | null; pickupRequired?: boolean } +export interface LooseboardExecution { dispatchNo: string; recordVersion: string | number; stage: string; direct: boolean; items: LooseboardExecutionItem[]; canCompletePickup?: boolean; pickupCompleted?: boolean } const path = (number: string) => `/platform/v1/last-leg/dispatches/${encodeURIComponent(number)}/looseboard-execution` const itemPath = (number: string, item: string, phase: LooseboardPhotoPhase) => `${path(number)}/${encodeURIComponent(item)}/${phase}` export async function getLooseboardExecution(number: string) { return unwrapApiResponse(await http.get, ApiEnvelope>(path(number))) } +export async function completeLooseboardPickup(number: string, body: { recordVersion: string | number; idempotencyKey: string }) { + return unwrapApiResponse(await http.post, ApiEnvelope>(`${path(number)}/pickup-complete`, body)) +} export async function uploadLooseboardPhoto(number: string, item: string, phase: LooseboardPhotoPhase, file: File) { const body = new FormData(); body.append('file', file) return unwrapApiResponse(await http.post, ApiEnvelope>(`${itemPath(number, item, phase)}/uploads`, body)) diff --git a/src/modules/overseas-warehouse/components/DispatchDocumentsDialog.test.ts b/src/modules/overseas-warehouse/components/DispatchDocumentsDialog.test.ts index 593f344c..21c79eca 100644 --- a/src/modules/overseas-warehouse/components/DispatchDocumentsDialog.test.ts +++ b/src/modules/overseas-warehouse/components/DispatchDocumentsDialog.test.ts @@ -6,10 +6,12 @@ import DispatchDocumentsDialog from './DispatchDocumentsDialog.vue' import DeliveryDocumentsDialog from './DeliveryDocumentsDialog.vue' import { destination, detail } from '../dispatchTestFixtures' import { getDispatch } from '../api/dispatches' +import { getLooseboardExecution, completeLooseboardPickup } from '../api/dispatchLooseboardExecution' import { deliveryDocumentCategories, getDeliveryDocuments, saveDeliveryDocuments, type DeliveryDocuments } from '../api/deliveryDocuments' const can = vi.hoisted(() => vi.fn(() => true)) vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can }) })) vi.mock('../api/dispatches', () => ({ getDispatch: vi.fn() })) +vi.mock('../api/dispatchLooseboardExecution', () => ({ getLooseboardExecution: vi.fn(), completeLooseboardPickup: vi.fn(), readLooseboardPhoto: vi.fn() })) vi.mock('../api/deliveryDocuments', async original => ({ ...await original(), getDeliveryDocuments: vi.fn(), saveDeliveryDocuments: vi.fn() })) const dialogStub = defineComponent({ props: ['modelValue', 'showConfirm'], emits: ['confirm'], setup(props, { slots, emit }) { return () => props.modelValue ? h('section', [slots.default?.(), props.showConfirm ? h('button', { class: 'save', onClick: () => emit('confirm') }, '保存资料') : null]) : null } }) function documents(no = 'DL-1', customerId = '101'): DeliveryDocuments { @@ -96,3 +98,24 @@ it('does not request delivery documents without delivery list permission', async expect(wrapper.text()).toContain('当前账号无派送资料查看权限') wrapper.unmount() }) +it.each([true, false])('closes pickup dialog only after successful confirmation (success=%s)', async success => { + const value = detail({ stage: 'DELIVERING' }) + value.orderDetails[0]!.sourceBusinessType = 'LOCAL_LOOSEBOARD' + vi.mocked(getDispatch).mockResolvedValue(value) + const snapshot = { dispatchNo: 'PC/1', recordVersion: '3', stage: 'DELIVERING', direct: false, items: [], canCompletePickup: true, pickupCompleted: false } + vi.mocked(getLooseboardExecution).mockResolvedValue(snapshot) + if (success) vi.mocked(completeLooseboardPickup).mockResolvedValue({ ...snapshot, canCompletePickup: false, pickupCompleted: true }) + else vi.mocked(completeLooseboardPickup).mockRejectedValue(new Error('取货确认失败')) + const wrapper = setup(); await flushPromises() + await wrapper.get('.save').trigger('click'); await flushPromises() + expect(completeLooseboardPickup).toHaveBeenCalledTimes(1) + if (success) { + expect(wrapper.emitted('update:modelValue')).toEqual([[false]]) + expect(wrapper.emitted('saved')).toHaveLength(1) + } else { + expect(wrapper.emitted('update:modelValue')).toBeUndefined() + expect(wrapper.emitted('saved')).toBeUndefined() + expect(wrapper.text()).toContain('取货确认失败') + } + wrapper.unmount() +}) diff --git a/src/modules/overseas-warehouse/components/DispatchDocumentsDialog.vue b/src/modules/overseas-warehouse/components/DispatchDocumentsDialog.vue index 4c630d48..5ffaf805 100644 --- a/src/modules/overseas-warehouse/components/DispatchDocumentsDialog.vue +++ b/src/modules/overseas-warehouse/components/DispatchDocumentsDialog.vue @@ -56,6 +56,7 @@ async function selectDelivery(value: string) { if (value !== selected.value && a async function beforeClose(done: () => void) { if (await canLeave()) done() } function selectTab(value: string) { if (!busy.value) { tab.value = value } } function saved() { emit('saved') } +function pickupCompleted() { open.value = false; emit('saved') } async function loadingSaved() { emit('saved'); await load() } watch(() => [open.value, props.dispatchNo] as const, ([visible]) => { sequence++; detail.value = null; selected.value = ''; tab.value = 'RECEIPT'; error.value = null; directLooseboard.value = false @@ -63,11 +64,11 @@ watch(() => [open.value, props.dispatchNo] as const, ([visible]) => { }, { immediate: true })