This commit is contained in:
2026-08-25 10:34:28 +08:00
72 changed files with 5664 additions and 300 deletions
@@ -1,13 +1,14 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { overseasHttp } from '@/shared/api/overseasHttp'
import { getDispatch, listDispatchCandidates, listDispatches, lockDispatchDraftPallet, preArrangeDispatch, releaseDispatchDraft, unlockDispatchDraftPallet } from './dispatches'
import { createDispatchDraftContainer, deleteDispatchDraftContainer, getDispatch, listDispatchCandidates, listDispatchDrafts, listDispatches, lockDispatchDraftPallet, preArrangeDispatch, releaseDispatchDraft, unlockDispatchDraftPallet } from './dispatches'
vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn(), post: vi.fn() } }))
vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn(), post: vi.fn(), delete: vi.fn() } }))
describe('dispatch overseas API', () => {
beforeEach(() => {
vi.mocked(overseasHttp.get).mockReset()
vi.mocked(overseasHttp.post).mockReset()
vi.mocked(overseasHttp.delete).mockReset()
})
it('uses the platform dispatch page without customer scope', async () => {
@@ -66,4 +67,18 @@ describe('dispatch overseas API', () => {
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' })
})
it('loads drafts and preserves long container ids in create and delete paths', async () => {
vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', message: 'success', data: [] })
vi.mocked(overseasHttp.post).mockResolvedValue({ code: '200', message: 'success', data: { id: '190000000000000099', capacity: 68, palletIds: [] } })
vi.mocked(overseasHttp.delete).mockResolvedValue({ code: '200', message: 'success', data: null })
await listDispatchDrafts()
await createDispatchDraftContainer('draft/1', 68)
await deleteDispatchDraftContainer('draft/1', '190000000000000099')
expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/drafts')
expect(overseasHttp.post).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/drafts/containers', { draftToken: 'draft/1', capacity: 68 })
expect(overseasHttp.delete).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/drafts/draft%2F1/containers/190000000000000099')
})
})
@@ -1,6 +1,6 @@
import { overseasHttp } from '@/shared/api/overseasHttp'
import { unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import type { OverseasPageResult } from './lastMileOrders'
import { normalizeOverseasPage, type OverseasPagePayload } from './lastMileOrders'
export interface DispatchQuery {
page: number
@@ -106,11 +106,11 @@ export interface DispatchItemDto {
}
export async function listDispatches(query: DispatchQuery) {
const response = await overseasHttp.get<ApiEnvelope<OverseasPageResult<DispatchSummaryDto>>, ApiEnvelope<OverseasPageResult<DispatchSummaryDto>>>(
const response = await overseasHttp.get<ApiEnvelope<OverseasPagePayload<DispatchSummaryDto>>, ApiEnvelope<OverseasPagePayload<DispatchSummaryDto>>>(
'/platform/v1/last-leg/dispatches',
{ params: query },
)
return unwrapApiResponse(response)
return normalizeOverseasPage(unwrapApiResponse(response))
}
export async function getDispatch(dispatchNo: string) {
@@ -1,6 +1,6 @@
import { overseasHttp } from '@/shared/api/overseasHttp'
import { unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import type { OverseasPageResult } from './lastMileOrders'
import { normalizeOverseasPage, type OverseasPagePayload } from './lastMileOrders'
export interface InboundOrderQuery {
page: number
@@ -68,10 +68,10 @@ export interface FbaAppointmentDto {
export type UpdateFbaAppointmentPayload = Omit<BatchFbaAppointmentPayload, 'lineIds' | 'appointmentNo'>
export async function listInboundOrders(query: InboundOrderQuery) {
const response = await overseasHttp.get<ApiEnvelope<OverseasPageResult<InboundOrderSummaryDto>>, ApiEnvelope<OverseasPageResult<InboundOrderSummaryDto>>>(
const response = await overseasHttp.get<ApiEnvelope<OverseasPagePayload<InboundOrderSummaryDto>>, ApiEnvelope<OverseasPagePayload<InboundOrderSummaryDto>>>(
'/platform/v1/last-leg/inbound-plans', { params: query },
)
return unwrapApiResponse(response)
return normalizeOverseasPage(unwrapApiResponse(response))
}
export async function createFbaAppointment(payload: BatchFbaAppointmentPayload) {
@@ -1,6 +1,6 @@
import { overseasHttp } from '@/shared/api/overseasHttp'
import { unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import type { OverseasPageResult } from './lastMileOrders'
import { normalizeOverseasPage, type OverseasPagePayload } from './lastMileOrders'
export interface InboundPlanQuery {
customerId: string
@@ -60,7 +60,7 @@ export interface InboundPlanDetailDto {
export async function listInboundPlans(query: InboundPlanQuery) {
const { customerId, ...params } = query
return unwrapApiResponse(await overseasHttp.get<ApiEnvelope<OverseasPageResult<InboundPlanSummaryDto>>, ApiEnvelope<OverseasPageResult<InboundPlanSummaryDto>>>('/platform/v1/last-leg/inbound-plans', { params: { customerId, ...params } }))
return normalizeOverseasPage(unwrapApiResponse(await overseasHttp.get<ApiEnvelope<OverseasPagePayload<InboundPlanSummaryDto>>, ApiEnvelope<OverseasPagePayload<InboundPlanSummaryDto>>>('/platform/v1/last-leg/inbound-plans', { params: { customerId, ...params } })))
}
export async function getInboundPlan(planNo: string, customerId: string) {
@@ -1,6 +1,6 @@
import { overseasHttp } from '@/shared/api/overseasHttp'
import { unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import type { OverseasPageResult } from './lastMileOrders'
import { normalizeOverseasPage, type OverseasPagePayload } from './lastMileOrders'
export interface InventoryBalanceDto {
id: string
@@ -110,10 +110,10 @@ export interface InventoryBalanceQuery {
function params(query: InventoryBalanceQuery) { return { ...query } }
export async function listInventoryBalances(query: InventoryBalanceQuery) {
const response = await overseasHttp.get<ApiEnvelope<OverseasPageResult<InventoryBalanceDto>>, ApiEnvelope<OverseasPageResult<InventoryBalanceDto>>>(
const response = await overseasHttp.get<ApiEnvelope<OverseasPagePayload<InventoryBalanceDto>>, ApiEnvelope<OverseasPagePayload<InventoryBalanceDto>>>(
'/platform/v1/last-leg/inventory/balances', { params: params(query) },
)
return unwrapApiResponse(response)
return normalizeOverseasPage(unwrapApiResponse(response))
}
export async function getInventorySummary(query: InventoryBalanceQuery) {
@@ -153,9 +153,9 @@ export interface InventoryLedgerQuery {
}
export async function listInventoryLedger(query: InventoryLedgerQuery) {
const response = await overseasHttp.get<ApiEnvelope<OverseasPageResult<InventoryLedgerDto>>, ApiEnvelope<OverseasPageResult<InventoryLedgerDto>>>(
const response = await overseasHttp.get<ApiEnvelope<OverseasPagePayload<InventoryLedgerDto>>, ApiEnvelope<OverseasPagePayload<InventoryLedgerDto>>>(
'/platform/v1/last-leg/inventory/ledger',
{ params: { ...query } },
)
return unwrapApiResponse(response)
return normalizeOverseasPage(unwrapApiResponse(response))
}
@@ -8,8 +8,8 @@ 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, orderType: 'FIRST_LEG', currentStatus: 'PENDING_ASSIGNMENT', warehouse: 'LAX' })).resolves.toMatchObject({ page: 2 })
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, orderType: 'FIRST_LEG', currentStatus: 'PENDING_ASSIGNMENT', warehouse: 'LAX' })).resolves.toEqual({ records: [], page: 2, size: 20, total: 0, pages: 0 })
expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/orders', {
params: { page: 2, size: 20, orderType: 'FIRST_LEG', currentStatus: 'PENDING_ASSIGNMENT', warehouse: 'LAX' },
})
@@ -9,6 +9,17 @@ export interface OverseasPageResult<T> {
pages: number
}
export type OverseasPagePayload<T> = Omit<OverseasPageResult<T>, 'page' | 'size' | 'total' | 'pages'> & {
page: number | string
size: number | string
total: number | string
pages: number | string
}
export function normalizeOverseasPage<T>(value: OverseasPagePayload<T>): OverseasPageResult<T> {
return { ...value, page: Number(value.page), size: Number(value.size), total: Number(value.total), pages: Number(value.pages) }
}
export interface LastMileOrderQuery {
page: number
size: number
@@ -118,11 +129,11 @@ export interface LastMileOrderDetailDto {
}
export async function listLastMileOrders(query: LastMileOrderQuery) {
const response = await overseasHttp.get<ApiEnvelope<OverseasPageResult<LastMileOrderSummaryDto>>, ApiEnvelope<OverseasPageResult<LastMileOrderSummaryDto>>>(
const response = await overseasHttp.get<ApiEnvelope<OverseasPagePayload<LastMileOrderSummaryDto>>, ApiEnvelope<OverseasPagePayload<LastMileOrderSummaryDto>>>(
'/platform/v1/last-leg/orders',
{ params: query },
)
return unwrapApiResponse(response)
return normalizeOverseasPage(unwrapApiResponse(response))
}
export async function getLastMileOrder(orderId: string) {
@@ -1,8 +1,8 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { overseasHttp } from '@/shared/api/overseasHttp'
import { createPallet, deletePallet, getPallet, listCustomerWarehouses, listPallets, updatePallet } from './pallets'
import { createPallet, getPallet, listCustomerWarehouses, listPallets, updatePallet } from './pallets'
vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn() } }))
vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn(), post: vi.fn(), put: vi.fn() } }))
describe('overseas pallet API', () => {
beforeEach(() => { vi.clearAllMocks() })
@@ -12,22 +12,20 @@ describe('overseas pallet API', () => {
vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', data: page })
vi.mocked(overseasHttp.post).mockResolvedValue({ code: '200', data: '9' })
vi.mocked(overseasHttp.put).mockResolvedValue({ code: '200', data: null })
vi.mocked(overseasHttp.delete).mockResolvedValue({ code: '200', data: null })
const payload = { palletCode: 'PLT-NEW', palletType: 'WOOD', fbaWarehouseCode: 'ONT8', capacityCbm: 2, unitPrice: 10, status: 'IDLE' }
await listCustomerWarehouses()
await listPallets('200', { page: 1, size: 20, keyword: 'PLT' })
await getPallet('200', '9')
await createPallet('200', payload)
await updatePallet('200', '9', payload)
await deletePallet('200', '9')
await updatePallet('200', '9', { ...payload, version: 7 })
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)
expect(overseasHttp.put).toHaveBeenCalledWith('/platform/v1/warehouse-admin/200/pallets/9', payload)
expect(overseasHttp.put).toHaveBeenCalledWith('/platform/v1/warehouse-admin/200/pallets/9', expect.objectContaining({ palletCode: 'PLT-NEW', fbaWarehouseCode: 'ONT8' }))
expect(overseasHttp.delete).toHaveBeenCalledWith('/platform/v1/warehouse-admin/200/pallets/9', {})
expect(overseasHttp.put).toHaveBeenCalledWith('/platform/v1/warehouse-admin/200/pallets/9', { ...payload, version: 7 })
expect(overseasHttp.put).toHaveBeenCalledWith('/platform/v1/warehouse-admin/200/pallets/9', expect.objectContaining({ palletCode: 'PLT-NEW', fbaWarehouseCode: 'ONT8', version: 7 }))
expect(overseasHttp.post).toHaveBeenCalledWith('/platform/v1/warehouse-admin/200/pallets', expect.not.objectContaining({ version: expect.anything() }))
})
})
+11 -14
View File
@@ -1,6 +1,6 @@
import { overseasHttp } from '@/shared/api/overseasHttp'
import { unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import type { OverseasPageResult } from './lastMileOrders'
import { normalizeOverseasPage, type OverseasPagePayload } from './lastMileOrders'
export interface CustomerWarehouseDto {
id: string
@@ -23,6 +23,7 @@ export interface PalletDto {
unitPrice: number
status: string
remark?: string | null
version: number
createdAt?: string | null
updatedAt?: string | null
}
@@ -38,20 +39,24 @@ export interface SavePalletPayload {
remark?: string | null
}
export interface UpdatePalletPayload extends SavePalletPayload {
version: number
}
export async function listCustomerWarehouses() {
const response = await overseasHttp.get<ApiEnvelope<OverseasPageResult<CustomerWarehouseDto>>, ApiEnvelope<OverseasPageResult<CustomerWarehouseDto>>>(
const response = await overseasHttp.get<ApiEnvelope<OverseasPagePayload<CustomerWarehouseDto>>, ApiEnvelope<OverseasPagePayload<CustomerWarehouseDto>>>(
'/platform/v1/warehouse-admin',
{ params: { page: 1, size: 200 } },
)
return unwrapApiResponse(response)
return normalizeOverseasPage(unwrapApiResponse(response))
}
export async function listPallets(warehouseId: string, query: { page: number; size: number; keyword?: string; palletType?: string; status?: string }) {
const response = await overseasHttp.get<ApiEnvelope<OverseasPageResult<PalletDto>>, ApiEnvelope<OverseasPageResult<PalletDto>>>(
const response = await overseasHttp.get<ApiEnvelope<OverseasPagePayload<PalletDto>>, ApiEnvelope<OverseasPagePayload<PalletDto>>>(
`/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/pallets`,
{ params: { ...query } },
)
return unwrapApiResponse(response)
return normalizeOverseasPage(unwrapApiResponse(response))
}
export async function getPallet(warehouseId: string, palletId: string) {
@@ -69,18 +74,10 @@ export async function createPallet(warehouseId: string, payload: SavePalletPaylo
return unwrapApiResponse(response)
}
export async function updatePallet(warehouseId: string, palletId: string, payload: SavePalletPayload) {
export async function updatePallet(warehouseId: string, palletId: string, payload: UpdatePalletPayload) {
const response = await overseasHttp.put<ApiEnvelope<null>, ApiEnvelope<null>>(
`/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/pallets/${encodeURIComponent(palletId)}`,
payload,
)
return unwrapApiResponse(response)
}
export async function deletePallet(warehouseId: string, palletId: string) {
const response = await overseasHttp.delete<ApiEnvelope<null>, ApiEnvelope<null>>(
`/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/pallets/${encodeURIComponent(palletId)}`,
{},
)
return unwrapApiResponse(response)
}
@@ -1,11 +1,15 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { overseasHttp } from '@/shared/api/overseasHttp'
import { changeRelabelOrderStatus, createRelabelOrder, getRelabelOrder, listRelabelOrders } from './relabelOrders'
import { changeRelabelOrderStatus, createRelabelOrder, getRelabelOrder, listRelabelOrders, listRelabelWarehouseOptions } from './relabelOrders'
vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn(), post: vi.fn(), put: vi.fn() } }))
describe('relabel overseas API', () => {
beforeEach(() => vi.mocked(overseasHttp.get).mockReset())
beforeEach(() => {
vi.mocked(overseasHttp.get).mockReset()
vi.mocked(overseasHttp.post).mockReset()
vi.mocked(overseasHttp.put).mockReset()
})
it('uses the platform page endpoint without customer scope', async () => {
vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', message: 'success', data: { records: [], page: 1, size: 20, total: 0, pages: 0 } })
@@ -23,9 +27,17 @@ describe('relabel overseas API', () => {
vi.mocked(overseasHttp.post).mockResolvedValue({ code: '200', data: {} })
vi.mocked(overseasHttp.put).mockResolvedValue({ code: '200', data: {} })
const payload = { customerOrderNo: 'CO-1', overseasWarehouseId: '9', labelDeadline: '2026-08-20T10:00:00', contactName: 'A', contactPhone: '1', idempotencyKey: 'k', items: [{ skuCode: 'SKU', productNameZh: 'P', totalPackageCount: 1, notifiedPackageCount: 1, shipmentNo: 'S', newSkuCode: 'SKU-2' }] }
await createRelabelOrder(payload)
await changeRelabelOrderStatus('RL-1', 'PROCESSING', 'ok')
expect(overseasHttp.post).toHaveBeenCalledWith('/platform/v1/last-leg/relabel-applications', payload)
expect(overseasHttp.put).toHaveBeenCalledWith('/platform/v1/last-leg/relabel-applications/RL-1/status', { status: 'PROCESSING', remark: 'ok' })
await createRelabelOrder('10001', payload)
await changeRelabelOrderStatus('RL/1', 'PROCESSING', 'ok')
expect(overseasHttp.post).toHaveBeenCalledWith('/platform/v1/last-leg/relabel-applications', payload, { params: { customerId: '10001' } })
expect(overseasHttp.put).toHaveBeenCalledWith('/platform/v1/last-leg/relabel-applications/RL%2F1/status', { status: 'PROCESSING', remark: 'ok' })
})
it('loads ACTIVE overseas warehouses for the explicitly selected customer', async () => {
vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', message: 'success', data: { records: [], page: 1, size: 200, total: 0, pages: 0 } })
await listRelabelWarehouseOptions('10001')
expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/warehouse-admin', {
params: { customerId: '10001', page: 1, size: 200, status: 'ACTIVE' },
})
})
})
@@ -1,6 +1,6 @@
import { overseasHttp } from '@/shared/api/overseasHttp'
import { unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import type { OverseasPageResult } from './lastMileOrders'
import { normalizeOverseasPage, type OverseasPagePayload } from './lastMileOrders'
export interface RelabelOrderQuery {
page: number
@@ -37,6 +37,15 @@ export interface CreateRelabelPayload {
items: CreateRelabelItemPayload[]
}
export interface RelabelWarehouseOptionDto {
id: string
customerId: string
warehouseCode: string
warehouseName: string
warehouseType: string
status: string
}
export interface RelabelSummaryDto {
id: string
relabelNo: string
@@ -83,10 +92,10 @@ export interface RelabelDetailDto {
}
export async function listRelabelOrders(query: RelabelOrderQuery) {
const response = await overseasHttp.get<ApiEnvelope<OverseasPageResult<RelabelSummaryDto>>, ApiEnvelope<OverseasPageResult<RelabelSummaryDto>>>(
const response = await overseasHttp.get<ApiEnvelope<OverseasPagePayload<RelabelSummaryDto>>, ApiEnvelope<OverseasPagePayload<RelabelSummaryDto>>>(
'/platform/v1/last-leg/relabel-applications', { params: query },
)
return unwrapApiResponse(response)
return normalizeOverseasPage(unwrapApiResponse(response))
}
export async function getRelabelOrder(relabelNo: string) {
@@ -96,9 +105,17 @@ export async function getRelabelOrder(relabelNo: string) {
return unwrapApiResponse(response)
}
export async function createRelabelOrder(payload: CreateRelabelPayload) {
export async function listRelabelWarehouseOptions(customerId: string) {
const response = await overseasHttp.get<ApiEnvelope<OverseasPagePayload<RelabelWarehouseOptionDto>>, ApiEnvelope<OverseasPagePayload<RelabelWarehouseOptionDto>>>(
'/platform/v1/warehouse-admin',
{ params: { customerId, page: 1, size: 200, status: 'ACTIVE' } },
)
return normalizeOverseasPage(unwrapApiResponse(response))
}
export async function createRelabelOrder(customerId: string, payload: CreateRelabelPayload) {
const response = await overseasHttp.post<ApiEnvelope<RelabelDetailDto>, ApiEnvelope<RelabelDetailDto>>(
'/platform/v1/last-leg/relabel-applications', payload,
'/platform/v1/last-leg/relabel-applications', payload, { params: { customerId } },
)
return unwrapApiResponse(response)
}