From c972ad408ab56f4ca694dba2e710a77cde0348ce Mon Sep 17 00:00:00 2001 From: liuyihe <2513762445@qq.com> Date: Fri, 21 Aug 2026 17:20:40 +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 --- .../overseas-warehouse/api/dispatches.test.ts | 28 +- .../overseas-warehouse/api/dispatches.ts | 32 ++ .../api/lastMileOrders.test.ts | 28 +- .../overseas-warehouse/api/lastMileOrders.ts | 14 + .../overseas-warehouse/api/pallets.test.ts | 1 + src/modules/overseas-warehouse/api/pallets.ts | 2 +- .../components/SmartDispatchDialog.vue | 281 +++++++++++++----- .../pages/InboundOrdersView.vue | 7 +- .../pages/LastMileOrdersView.vue | 97 +++++- .../overseas-warehouse/pages/PalletsView.vue | 29 +- .../pages/prototypeContract.test.ts | 63 +++- src/modules/overseas-warehouse/permissions.ts | 3 +- src/shared/components/DataTable.test.ts | 23 +- src/shared/components/DataTable.vue | 14 +- 14 files changed, 504 insertions(+), 118 deletions(-) diff --git a/src/modules/overseas-warehouse/api/dispatches.test.ts b/src/modules/overseas-warehouse/api/dispatches.test.ts index 261f58e9..9bfebc89 100644 --- a/src/modules/overseas-warehouse/api/dispatches.test.ts +++ b/src/modules/overseas-warehouse/api/dispatches.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { overseasHttp } from '@/shared/api/overseasHttp' -import { listDispatchCandidates, listDispatches, preArrangeDispatch } from './dispatches' +import { listDispatchCandidates, listDispatches, lockDispatchDraftPallet, preArrangeDispatch, releaseDispatchDraft, unlockDispatchDraftPallet } from './dispatches' vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn(), post: vi.fn() } })) @@ -31,9 +31,31 @@ describe('dispatch overseas API', () => { it('loads candidates and creates a pre-arrange dispatch in an explicit customer scope', async () => { vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', message: 'success', data: [] }) vi.mocked(overseasHttp.post).mockResolvedValue({ code: '200', message: 'success', data: { id: '1', dispatchNo: 'PC-1' } }) - await listDispatchCandidates({ fbaWarehouseCode: 'ONT8', sourceStage: 'PLAN' }) + await listDispatchCandidates({ planIds: '101,102', fbaWarehouseCode: 'ONT8', sourceStage: 'PLAN' }) await preArrangeDispatch({ planPalletIds: ['190000000000000099'], route: 'LAX -> ONT8' }) - expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/candidates', { params: { fbaWarehouseCode: 'ONT8', sourceStage: 'PLAN' } }) + expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/candidates', { params: { planIds: '101,102', fbaWarehouseCode: 'ONT8', sourceStage: 'PLAN' } }) expect(overseasHttp.post).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/pre-arrange', { planPalletIds: ['190000000000000099'], route: 'LAX -> ONT8' }) }) + + it('loads platform candidates without selected inbound plans', async () => { + vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', message: 'success', data: [] }) + + await listDispatchCandidates({ sourceStage: 'RECEIVED' }) + + expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/candidates', { + params: { sourceStage: 'RECEIVED' }, + }) + }) + + it('locks, unlocks, and releases intelligent dispatch drafts through dedicated endpoints', async () => { + vi.mocked(overseasHttp.post).mockResolvedValue({ code: '200', message: 'success', data: null }) + + await lockDispatchDraftPallet('99', 'draft-1') + await unlockDispatchDraftPallet('99', 'draft-1') + await releaseDispatchDraft('draft-1') + + expect(overseasHttp.post).toHaveBeenNthCalledWith(1, '/platform/v1/last-leg/dispatches/draft-locks/lock', { planPalletId: '99', draftToken: 'draft-1' }) + expect(overseasHttp.post).toHaveBeenNthCalledWith(2, '/platform/v1/last-leg/dispatches/draft-locks/unlock', { planPalletId: '99', draftToken: 'draft-1' }) + expect(overseasHttp.post).toHaveBeenNthCalledWith(3, '/platform/v1/last-leg/dispatches/draft-locks/release', { draftToken: 'draft-1' }) + }) }) diff --git a/src/modules/overseas-warehouse/api/dispatches.ts b/src/modules/overseas-warehouse/api/dispatches.ts index 05b5dfe3..0d7e2404 100644 --- a/src/modules/overseas-warehouse/api/dispatches.ts +++ b/src/modules/overseas-warehouse/api/dispatches.ts @@ -16,6 +16,14 @@ export interface DispatchPalletCandidateDto { planPalletId: string warehousePalletId?: string | null orderId: string + customerId?: string | null + customerName?: string | null + planNo?: string | null + orderType?: string | null + volumeWeightKg?: number | null + containerNo?: string | null + appointmentAt?: string | null + expectedLoadingAt?: string | null overseasWarehouseId?: string | null overseasWarehouseName?: string | null orderNo?: string | null @@ -29,9 +37,11 @@ export interface DispatchPalletCandidateDto { packageCount?: number | null volumeCbm?: number | null sourceStage?: 'PLAN' | 'RECEIVED' | null + cartonMarks?: string | null } export interface DispatchCandidateQuery { + planIds?: string orderNo?: string fbaWarehouseCode?: string sourceStage?: 'PLAN' | 'RECEIVED' @@ -43,6 +53,7 @@ export interface PreArrangeDispatchPayload { fbaAppointmentNo?: string fbaAppointmentStatus?: string vehicleAppointmentStatus?: string + draftToken?: string } export interface DispatchSummaryDto { @@ -95,3 +106,24 @@ export async function preArrangeDispatch(payload: PreArrangeDispatchPayload) { ) return unwrapApiResponse(response) } + +export async function lockDispatchDraftPallet(planPalletId: string, draftToken: string) { + const response = await overseasHttp.post, ApiEnvelope>( + '/platform/v1/last-leg/dispatches/draft-locks/lock', { planPalletId, draftToken }, + ) + return unwrapApiResponse(response) +} + +export async function unlockDispatchDraftPallet(planPalletId: string, draftToken: string) { + const response = await overseasHttp.post, ApiEnvelope>( + '/platform/v1/last-leg/dispatches/draft-locks/unlock', { planPalletId, draftToken }, + ) + return unwrapApiResponse(response) +} + +export async function releaseDispatchDraft(draftToken: string) { + const response = await overseasHttp.post, ApiEnvelope>( + '/platform/v1/last-leg/dispatches/draft-locks/release', { draftToken }, + ) + return unwrapApiResponse(response) +} diff --git a/src/modules/overseas-warehouse/api/lastMileOrders.test.ts b/src/modules/overseas-warehouse/api/lastMileOrders.test.ts index bf025d63..9f9c6ee6 100644 --- a/src/modules/overseas-warehouse/api/lastMileOrders.test.ts +++ b/src/modules/overseas-warehouse/api/lastMileOrders.test.ts @@ -1,17 +1,17 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { overseasHttp } from '@/shared/api/overseasHttp' -import { getLastMileOrder, listLastMileOrders, transferLastMileOrders } from './lastMileOrders' +import { convertLastMileOrderToInboundPlan, getLastMileOrder, listLastMileOrders, transferLastMileOrders } from './lastMileOrders' -vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn(), put: vi.fn() } })) +vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn(), put: vi.fn(), post: vi.fn() } })) describe('last-mile order overseas API', () => { beforeEach(() => vi.mocked(overseasHttp.get).mockReset()) it('loads all platform orders without a customer scope', async () => { vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', message: 'success', data: { records: [], page: 2, size: 20, total: 0, pages: 0 } }) - await expect(listLastMileOrders({ page: 2, size: 20, status: 'PENDING_ASSIGNMENT' })).resolves.toMatchObject({ page: 2 }) + await expect(listLastMileOrders({ page: 2, size: 20, orderType: 'FIRST_LEG', currentStatus: 'PENDING_ASSIGNMENT', warehouse: 'LAX' })).resolves.toMatchObject({ page: 2 }) expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/orders', { - params: { page: 2, size: 20, status: 'PENDING_ASSIGNMENT' }, + params: { page: 2, size: 20, orderType: 'FIRST_LEG', currentStatus: 'PENDING_ASSIGNMENT', warehouse: 'LAX' }, }) }) @@ -26,6 +26,19 @@ describe('last-mile order overseas API', () => { expect(source).not.toMatch(/outbound|delivery|dispatch/) }) + it('passes all supported list filters and pagination without customerId', async () => { + vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', message: 'success', data: { records: [], page: 2, size: 50, total: 0, pages: 0 } }) + await listLastMileOrders({ + page: 2, size: 50, orderNo: 'LM-1', inboundCode: 'IN-1', sourceOrderNo: 'SRC-1', + containerNo: 'CONT-1', status: 'PENDING_DELIVERY', transferWarehouseCode: 'ONT8', + abnormal: true, detained: false, createdFrom: '2026-08-01T00:00:00', createdTo: '2026-08-31T23:59:59', + }) + expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/orders', { + params: expect.objectContaining({ page: 2, size: 50, sourceOrderNo: 'SRC-1', transferWarehouseCode: 'ONT8', abnormal: true, detained: false }), + }) + expect(JSON.stringify(vi.mocked(overseasHttp.get).mock.calls[0]?.[1])).not.toContain('customerId') + }) + it('uses the real destination transfer endpoint and edit contract', async () => { vi.mocked(overseasHttp.put).mockResolvedValue({ code: '200', message: 'success', data: 1 }) await transferLastMileOrders({ @@ -33,4 +46,11 @@ describe('last-mile order overseas API', () => { }) expect(overseasHttp.put).toHaveBeenCalledWith('/platform/v1/last-leg/orders/destination', expect.objectContaining({ orderIds: ['190000000000000099'] })) }) + + it('uses the inbound-plan conversion endpoint without customerId', async () => { + vi.mocked(overseasHttp.post).mockResolvedValue({ code: '200', message: 'success', data: {} }) + await convertLastMileOrderToInboundPlan({ orderId: '190000000000000099' }) + expect(overseasHttp.post).toHaveBeenCalledWith('/platform/v1/last-leg/inbound-plans/convert', { orderId: '190000000000000099' }) + expect(JSON.stringify(vi.mocked(overseasHttp.post).mock.calls[0]?.[1])).not.toContain('customerId') + }) }) diff --git a/src/modules/overseas-warehouse/api/lastMileOrders.ts b/src/modules/overseas-warehouse/api/lastMileOrders.ts index 9e88d1ec..27a8dd7f 100644 --- a/src/modules/overseas-warehouse/api/lastMileOrders.ts +++ b/src/modules/overseas-warehouse/api/lastMileOrders.ts @@ -13,9 +13,12 @@ export interface LastMileOrderQuery { page: number size: number orderNo?: string + orderType?: string inboundCode?: string containerNo?: string + currentStatus?: string status?: string + warehouse?: string sourceOrderNo?: string transferWarehouseCode?: string abnormal?: boolean @@ -103,6 +106,10 @@ export interface TransferDestinationPayload { remark?: string } +export interface ConvertInboundPlanPayload { + orderId: string +} + export interface LastMileOrderDetailDto { order: LastMileOrderDto cargo: LastMileCargoDto[] @@ -130,3 +137,10 @@ export async function transferLastMileOrders(payload: TransferDestinationPayload ) return unwrapApiResponse(response) } + +export async function convertLastMileOrderToInboundPlan(payload: ConvertInboundPlanPayload) { + const response = await overseasHttp.post, ApiEnvelope>( + '/platform/v1/last-leg/inbound-plans/convert', payload, + ) + return unwrapApiResponse(response) +} diff --git a/src/modules/overseas-warehouse/api/pallets.test.ts b/src/modules/overseas-warehouse/api/pallets.test.ts index 8bca422c..5533fa6d 100644 --- a/src/modules/overseas-warehouse/api/pallets.test.ts +++ b/src/modules/overseas-warehouse/api/pallets.test.ts @@ -22,6 +22,7 @@ describe('overseas pallet API', () => { await updatePallet('200', '9', payload) await deletePallet('200', '9') + expect(overseasHttp.get).toHaveBeenNthCalledWith(1, '/platform/v1/warehouse-admin', { params: { page: 1, size: 200 } }) expect(overseasHttp.get).toHaveBeenNthCalledWith(2, '/platform/v1/warehouse-admin/200/pallets', { params: { page: 1, size: 20, keyword: 'PLT' } }) expect(overseasHttp.get).toHaveBeenNthCalledWith(3, '/platform/v1/warehouse-admin/200/pallets/9', {}) expect(overseasHttp.post).toHaveBeenCalledWith('/platform/v1/warehouse-admin/200/pallets', payload) diff --git a/src/modules/overseas-warehouse/api/pallets.ts b/src/modules/overseas-warehouse/api/pallets.ts index b8281e4f..dded493a 100644 --- a/src/modules/overseas-warehouse/api/pallets.ts +++ b/src/modules/overseas-warehouse/api/pallets.ts @@ -41,7 +41,7 @@ export interface SavePalletPayload { export async function listCustomerWarehouses() { const response = await overseasHttp.get>, ApiEnvelope>>( '/platform/v1/warehouse-admin', - { params: { page: 1, size: 200, status: 'ACTIVE' } }, + { params: { page: 1, size: 200 } }, ) return unwrapApiResponse(response) } diff --git a/src/modules/overseas-warehouse/components/SmartDispatchDialog.vue b/src/modules/overseas-warehouse/components/SmartDispatchDialog.vue index 81750a1a..1622bd6d 100644 --- a/src/modules/overseas-warehouse/components/SmartDispatchDialog.vue +++ b/src/modules/overseas-warehouse/components/SmartDispatchDialog.vue @@ -1,129 +1,270 @@ diff --git a/src/modules/overseas-warehouse/pages/InboundOrdersView.vue b/src/modules/overseas-warehouse/pages/InboundOrdersView.vue index e30ffea3..2e429b27 100644 --- a/src/modules/overseas-warehouse/pages/InboundOrdersView.vue +++ b/src/modules/overseas-warehouse/pages/InboundOrdersView.vue @@ -113,9 +113,6 @@ function openAppointment() { } function openSmartDispatch() { - const error = !selectedRows.value.length ? '请至少选择 1 条入库单' - : new Set(selectedRows.value.map((row) => row.fbaWarehouse)).size !== 1 ? '所选入库单必须属于同一 FBA 仓库' : '' - if (error) { ElMessage.warning(error); return } smartDispatchOpen.value = true } @@ -175,8 +172,8 @@ onMounted(() => { void load() })
- - + +
diff --git a/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue b/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue index ff1557ad..e0bb7f61 100644 --- a/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue +++ b/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue @@ -1,45 +1,65 @@