import { beforeEach, describe, expect, it, vi } from 'vitest' import { http } from '@/shared/api/http' import { reviewCargoDiscard, updateLastMileOrderPalletMerge } from './lastMileOrders' import { changeContainerReceivingWarehouse, convertLastMileContainerToInboundPlans, createManualInboundPlan, getContainerPlanProgress, getLastMileOrder, getLastMileOrderManualNodes, listContainerPlanProgress, listLastMileOrders, transferLastMileOrders, updateLastMileOrderHold, updateLastMileOrderManualNodes } from './lastMileOrders' vi.mock('@/shared/api/http', async (importOriginal) => ({ ...(await importOriginal()), http: { get: vi.fn(), put: vi.fn(), post: vi.fn() } })) describe('last-mile order overseas API', () => { it('reviews a discard by business number with an explicit string customer query', async () => { vi.mocked(http.post).mockResolvedValue({ code: '200', message: 'success', data: { status: 'REJECTED' } }) await expect(reviewCargoDiscard('CD/TEST-1', '2092618717861048321', 'REJECT', ' 箱数不符 ')).resolves.toEqual({ status: 'REJECTED' }) expect(http.post).toHaveBeenCalledExactlyOnceWith('/platform/v1/last-leg/cargo-discards/CD%2FTEST-1/review', { decision: 'REJECT', remark: '箱数不符' }, { params: { customerId: '2092618717861048321' } }) }) beforeEach(() => { vi.mocked(http.get).mockReset() vi.mocked(http.put).mockReset() vi.mocked(http.post).mockReset() }) it('loads all platform orders without a customer scope', async () => { const record = { id: '190000000000000099', customerId: '190000000000000001', sourceContainerBookingId: '190000000000000088', customerName: '华远贸易', orderNo: 'LMO-260827-0001' } vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: { records: [record], page: '2', size: '20', total: '1', pages: '1' } }) await expect(listLastMileOrders({ page: 2, size: 20, orderType: 'FIRST_LEG', currentStatus: 'PENDING_ASSIGNMENT', warehouse: 'LAX' })).resolves.toEqual({ records: [record], page: 2, size: 20, total: 1, pages: 1 }) expect(http.get).toHaveBeenCalledWith('/platform/v1/last-leg/orders', { params: { page: 2, size: 20, orderType: 'FIRST_LEG', currentStatus: 'PENDING_ASSIGNMENT', warehouse: 'LAX' }, }) }) it('preserves a long order id as a string in the detail path', async () => { vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: { order: { id: '190000000000000099' }, cargo: [], statusHistory: [] } }) await getLastMileOrder('190000000000000099') expect(http.get).toHaveBeenCalledWith('/platform/v1/last-leg/orders/190000000000000099') }) it('does not call outbound or delivery endpoints', () => { const source = `${listLastMileOrders}${getLastMileOrder}` expect(source).not.toMatch(/outbound|delivery|dispatch/) }) it('passes all supported list filters and pagination without customerId', async () => { vi.mocked(http.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(http.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(http.get).mock.calls[0]?.[1])).not.toContain('customerId') }) it('uses the real destination transfer endpoint and edit contract', async () => { vi.mocked(http.put).mockResolvedValue({ code: '200', message: 'success', data: 1 }) await transferLastMileOrders({ orderIds: ['190000000000000099'], warehouseId: 'WH-1', warehouseCode: 'ONT8', warehouseName: 'Ontario', }) expect(http.put).toHaveBeenCalledWith('/platform/v1/last-leg/orders/destination', expect.objectContaining({ orderIds: ['190000000000000099'] })) }) it('uses the manual inbound-plan endpoint with the complete form and idempotency evidence', async () => { vi.mocked(http.post).mockResolvedValue({ code: '200', message: 'success', data: {} }) const payload = { orderId: '190000000000000099', customerId: '190000000000000001', expectedOrderVersion: 4, palletCount: 8, palletType: 'STANDARD' as const, assigned: false, mergeRequired: false, urgent: true, remark: '加急' } await createManualInboundPlan(payload, 'plan-key-1') expect(http.post).toHaveBeenCalledWith('/platform/v1/last-leg/inbound-plans/manual', payload, { headers: { 'Idempotency-Key': 'plan-key-1' } }) expect(payload).not.toHaveProperty('inboundCode') expect(payload).not.toHaveProperty('plannedArrivalDate') expect(payload).not.toHaveProperty('plannedWarehouseId') expect(payload).not.toHaveProperty('plannedWarehouseCode') expect(payload).not.toHaveProperty('plannedWarehouseName') }) it('uses versioned idempotent order hold and complete manual-node snapshot endpoints', async () => { vi.mocked(http.put).mockResolvedValue({ code: '200', message: 'success', data: {} }) vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: { nodes: [] } }) await updateLastMileOrderHold('190000000000000099', { held: true, chargeable: true, reason: '海关查验', expectedVersion: 4 }, 'hold-key-1') expect(http.put).toHaveBeenLastCalledWith('/platform/v1/last-leg/orders/190000000000000099/hold', { held: true, chargeable: true, reason: '海关查验', expectedVersion: 4 }, { headers: { 'Idempotency-Key': 'hold-key-1' } }) await getLastMileOrderManualNodes('190000000000000099') expect(http.get).toHaveBeenLastCalledWith('/platform/v1/last-leg/orders/190000000000000099/manual-nodes') const nodes = [{ nodeCode: 'ESTIMATED_ARRIVAL' as const, nodeAt: '2026-09-10T10:00:00' }] await updateLastMileOrderManualNodes('190000000000000099', 5, nodes, 'node-key-1') expect(http.put).toHaveBeenLastCalledWith('/platform/v1/last-leg/orders/190000000000000099/manual-nodes', { expectedVersion: 5, nodes }, { headers: { 'Idempotency-Key': 'node-key-1' } }) }) it('uses one atomic container request without looping individual conversions', async () => { vi.mocked(http.post).mockResolvedValue({ code: '200', message: 'success', data: [] }) await expect(convertLastMileContainerToInboundPlans({ sourceContainerBookingId: '2092873463568597001', snapshotVersion: 9, snapshotHash: 'a'.repeat(64), }, 'container-plan-idempotency-key')).resolves.toEqual([]) expect(http.post).toHaveBeenCalledTimes(1) expect(http.post).toHaveBeenCalledWith('/platform/v1/last-leg/inbound-plans/convert-container', { sourceContainerBookingId: '2092873463568597001', snapshotVersion: 9, snapshotHash: 'a'.repeat(64), }, { headers: { 'Idempotency-Key': 'container-plan-idempotency-key' } }) }) it('loads server-side container progress with pagination and filters', async () => { const record = { sourceContainerBookingId: '2092873463568597001', containerNo: 'CONT-15', totalOrderCount: 15, inboundCodeCount: 13, missingInboundCodeCount: 2, planCount: 10, plannedInboundCodeCount: 10, pendingPlanCount: 5, resourcePendingPlanCount: 0, plannedArrivalAt: '2026-09-01T08:00:00', allPlansReady: false, blockingReason: '存在2票缺少入仓码', } vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: { records: [record], page: '2', size: '10', total: '21', pages: '3' }, }) await expect(listContainerPlanProgress({ page: 2, size: 10, containerNo: 'CONT-15', createdFrom: '2026-08-01T00:00:00', createdTo: '2026-08-31T23:59:59', })).resolves.toEqual({ records: [record], page: 2, size: 10, total: 21, pages: 3 }) expect(http.get).toHaveBeenCalledWith('/platform/v1/last-leg/orders/container-summaries', { params: { page: 2, size: 10, containerNo: 'CONT-15', createdFrom: '2026-08-01T00:00:00', createdTo: '2026-08-31T23:59:59', }, }) }) it('loads one container detail with the encoded stable long id', async () => { const detail = { summary: { sourceContainerBookingId: '2092873463568597001' }, orders: [] } vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: detail }) await expect(getContainerPlanProgress('2092873463568597001/unsafe')).resolves.toEqual(detail) expect(http.get).toHaveBeenCalledWith('/platform/v1/last-leg/orders/container-summaries/2092873463568597001%2Funsafe') }) it('changes one whole container receiving warehouse with version and idempotency evidence', async () => { const updated = { sourceContainerBookingId: '2092873463568597001', overseasWarehouseId: '9007199254740993' } vi.mocked(http.put).mockResolvedValue({ code: '200', message: 'success', data: updated }) await expect(changeContainerReceivingWarehouse('2092873463568597001/unsafe', { overseasWarehouseId: '9007199254740993', expectedVersion: 7, remark: '改派洛杉矶接柜', }, 'receiving-key-1')).resolves.toEqual(updated) expect(http.put).toHaveBeenCalledWith( '/platform/v1/last-leg/orders/container-summaries/2092873463568597001%2Funsafe/receiving-warehouse', { overseasWarehouseId: '9007199254740993', expectedVersion: 7, remark: '改派洛杉矶接柜' }, { headers: { 'Idempotency-Key': 'receiving-key-1' } }, ) }) }) it('sets and cancels a merge group with string IDs and explicit customer scope', async () => { vi.mocked(http.put).mockResolvedValue({ code: '200', message: 'success', data: null }) for (const required of [true, false]) { await updateLastMileOrderPalletMerge(['2092873463568596994'], '2092618717861048321', required) expect(http.put).toHaveBeenLastCalledWith('/platform/v1/last-leg/orders/pallet-merge-required/batch', { orderIds: ['2092873463568596994'], palletMergeRequired: required }, { params: { customerId: '2092618717861048321' } }) } })