This commit is contained in:
2026-08-20 09:10:36 +08:00
parent 033b5df6b6
commit 508f76924e
58 changed files with 1790 additions and 628 deletions
@@ -0,0 +1,30 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { overseasHttp } from '@/shared/api/overseasHttp'
import { getLastMileOrder, listLastMileOrders } from './lastMileOrders'
vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn() } }))
describe('last-mile order overseas API', () => {
beforeEach(() => vi.mocked(overseasHttp.get).mockReset())
it('uses the gateway-relative platform list endpoint and explicit 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({ customerId: '190000000000000001', page: 2, size: 20, status: 'PENDING_ASSIGNMENT' })).resolves.toMatchObject({ page: 2 })
expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/orders', {
params: { customerId: '190000000000000001', page: 2, size: 20, status: 'PENDING_ASSIGNMENT' },
})
})
it('preserves a long order id as a string in the detail path', async () => {
vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', message: 'success', data: { order: { id: '190000000000000099' }, cargo: [], statusHistory: [] } })
await getLastMileOrder('190000000000000099', '190000000000000001')
expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/orders/190000000000000099', {
params: { customerId: '190000000000000001' },
})
})
it('does not call outbound or delivery endpoints', () => {
const source = `${listLastMileOrders}${getLastMileOrder}`
expect(source).not.toMatch(/outbound|delivery|dispatch/)
})
})
@@ -0,0 +1,110 @@
import { overseasHttp } from '@/shared/api/overseasHttp'
import { unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
export interface OverseasPageResult<T> {
records: T[]
page: number
size: number
total: number
pages: number
}
export interface LastMileOrderQuery {
customerId: string
page: number
size: number
orderNo?: string
inboundCode?: string
containerNo?: string
status?: string
}
export interface LastMileOrderSummaryDto {
id: string
orderNo: string
orderType?: string | null
inboundCode?: string | null
sourceOrderNo?: string | null
customerName?: string | null
overseasWarehouseName?: string | null
transferWarehouseName?: string | null
fbaWarehouseCode?: string | null
containerNo?: string | null
containerStatus?: string | null
abnormal?: boolean | null
detained?: boolean | null
palletCount?: number | null
packageCount?: number | null
volumeCbm?: number | null
productQuantity?: number | null
volumeWeightKg?: number | null
estimatedArrivalAt?: string | null
actualArrivalAt?: string | null
estimatedWarehouseAt?: string | null
inboundOrderNo?: string | null
outboundOrderNo?: string | null
deliveryOrderNo?: string | null
relabelOrderNo?: string | null
relabelStatus?: string | null
palletMergeFlag?: string | null
currentStatus?: string | null
privateRemark?: string | null
customerRemark?: string | null
warehouseRemark?: string | null
}
export interface LastMileOrderDto extends LastMileOrderSummaryDto {
customerId: string
sourceOrderNo?: string | null
subOrderStatus?: string | null
createdAt?: string | null
updatedAt?: string | null
}
export interface LastMileCargoDto {
id: string
skuCode?: string | null
productNameZh?: string | null
productNameEn?: string | null
packageCount?: number | null
productQuantity?: number | null
palletCount?: number | null
grossWeightKg?: number | null
dimensionalWeightKg?: number | null
volumeCbm?: number | null
destinationWarehouseName?: string | null
fbaTrackingNo?: string | null
shipmentNo?: string | null
}
export interface LastMileStatusHistoryDto {
id: string
fromStatus?: string | null
toStatus?: string | null
eventId?: string | null
remark?: string | null
createdAt?: string | null
}
export interface LastMileOrderDetailDto {
order: LastMileOrderDto
cargo: LastMileCargoDto[]
statusHistory: LastMileStatusHistoryDto[]
}
export async function listLastMileOrders(query: LastMileOrderQuery) {
const { customerId, ...params } = query
const response = await overseasHttp.get<ApiEnvelope<OverseasPageResult<LastMileOrderSummaryDto>>, ApiEnvelope<OverseasPageResult<LastMileOrderSummaryDto>>>(
'/platform/v1/last-leg/orders',
{ params: { customerId, ...params } },
)
return unwrapApiResponse(response)
}
export async function getLastMileOrder(orderId: string, customerId: string) {
const response = await overseasHttp.get<ApiEnvelope<LastMileOrderDetailDto>, ApiEnvelope<LastMileOrderDetailDto>>(
`/platform/v1/last-leg/orders/${encodeURIComponent(orderId)}`,
{ params: { customerId } },
)
return unwrapApiResponse(response)
}
@@ -0,0 +1,21 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { overseasHttp } from '@/shared/api/overseasHttp'
import { getRelabelOrder, listRelabelOrders } from './relabelOrders'
vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn() } }))
describe('relabel overseas API', () => {
beforeEach(() => vi.mocked(overseasHttp.get).mockReset())
it('uses explicit customer scope for the real page endpoint', async () => {
vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', message: 'success', data: { records: [], page: 1, size: 20, total: 0, pages: 0 } })
await listRelabelOrders({ customerId: '190000000000000001', page: 1, size: 20, skuCode: 'SKU-1' })
expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/relabel-applications', { params: { customerId: '190000000000000001', page: 1, size: 20, skuCode: 'SKU-1' } })
})
it('encodes the relabel number and does not call a mutation endpoint', async () => {
vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', message: 'success', data: { application: { id: '1', relabelNo: 'RL/1' }, items: [], history: [] } })
await getRelabelOrder('RL/1', '190000000000000001')
expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/relabel-applications/RL%2F1', { params: { customerId: '190000000000000001' } })
})
})
@@ -0,0 +1,71 @@
import { overseasHttp } from '@/shared/api/overseasHttp'
import { unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import type { OverseasPageResult } from './lastMileOrders'
export interface RelabelOrderQuery {
customerId: string
page: number
size: number
skuCode?: string
status?: string
}
export interface RelabelSummaryDto {
id: string
relabelNo: string
lastlegOrderId?: string | null
lastlegOrderNo?: string | null
customerOrderNo?: string | null
overseasWarehouseId?: string | null
labelDeadline?: string | null
contactName?: string | null
contactPhone?: string | null
status?: string | null
itemCount?: number | null
totalPackageCount?: number | null
notifiedPackageCount?: number | null
skuCodes?: string | null
createdAt?: string | null
}
export interface RelabelItemDto {
id: string
skuCode?: string | null
productNameZh?: string | null
totalPackageCount?: number | null
notifiedPackageCount?: number | null
newSkuCode?: string | null
labelRequirement?: string | null
relabelFileName?: string | null
relabelFileUrl?: string | null
}
export interface RelabelHistoryDto {
fromStatus?: string | null
toStatus?: string | null
operatorId?: string | null
remark?: string | null
createdAt?: string | null
}
export interface RelabelDetailDto {
application: RelabelSummaryDto
remark?: string | null
items: RelabelItemDto[]
history: RelabelHistoryDto[]
}
export async function listRelabelOrders(query: RelabelOrderQuery) {
const { customerId, ...params } = query
const response = await overseasHttp.get<ApiEnvelope<OverseasPageResult<RelabelSummaryDto>>, ApiEnvelope<OverseasPageResult<RelabelSummaryDto>>>(
'/platform/v1/last-leg/relabel-applications', { params: { customerId, ...params } },
)
return unwrapApiResponse(response)
}
export async function getRelabelOrder(relabelNo: string, customerId: string) {
const response = await overseasHttp.get<ApiEnvelope<RelabelDetailDto>, ApiEnvelope<RelabelDetailDto>>(
`/platform/v1/last-leg/relabel-applications/${encodeURIComponent(relabelNo)}`, { params: { customerId } },
)
return unwrapApiResponse(response)
}