This commit is contained in:
2026-08-21 11:20:09 +08:00
parent 595c998214
commit 6e9bb6e00c
45 changed files with 1051 additions and 329 deletions
@@ -0,0 +1,39 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { overseasHttp } from '@/shared/api/overseasHttp'
import { listDispatchCandidates, listDispatches, preArrangeDispatch } from './dispatches'
vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn(), post: vi.fn() } }))
describe('dispatch overseas API', () => {
beforeEach(() => {
vi.mocked(overseasHttp.get).mockReset()
vi.mocked(overseasHttp.post).mockReset()
})
it('uses the platform dispatch page without customer scope', async () => {
vi.mocked(overseasHttp.get).mockResolvedValue({
code: '200', message: 'success', data: { records: [], page: 2, size: 50, total: 0, pages: 0 },
})
await listDispatches({
page: 2, size: 50, dispatchNo: 'PC-1',
vehiclePlate: 'CA-100', status: 'PENDING_DEPARTURE', fbaAppointmentNo: 'FBA-1',
})
expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches', {
params: {
page: 2, size: 50, dispatchNo: 'PC-1',
vehiclePlate: 'CA-100', status: 'PENDING_DEPARTURE', fbaAppointmentNo: 'FBA-1',
},
})
})
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 preArrangeDispatch({ planPalletIds: ['190000000000000099'], route: 'LAX -> ONT8' })
expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/candidates', { params: { fbaWarehouseCode: 'ONT8', sourceStage: 'PLAN' } })
expect(overseasHttp.post).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/pre-arrange', { planPalletIds: ['190000000000000099'], route: 'LAX -> ONT8' })
})
})
@@ -0,0 +1,97 @@
import { overseasHttp } from '@/shared/api/overseasHttp'
import { unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import type { OverseasPageResult } from './lastMileOrders'
export interface DispatchQuery {
page: number
size: number
dispatchNo?: string
vehiclePlate?: string
status?: string
fbaAppointmentNo?: string
}
export interface DispatchPalletCandidateDto {
planId: string
planPalletId: string
warehousePalletId?: string | null
orderId: string
overseasWarehouseId?: string | null
overseasWarehouseName?: string | null
orderNo?: string | null
orderRefs?: string | null
inboundCode?: string | null
palletNo?: string | null
destinationWarehouseId?: string | null
destinationWarehouseCode?: string | null
destinationWarehouseName?: string | null
fbaWarehouseCode?: string | null
packageCount?: number | null
volumeCbm?: number | null
sourceStage?: 'PLAN' | 'RECEIVED' | null
}
export interface DispatchCandidateQuery {
orderNo?: string
fbaWarehouseCode?: string
sourceStage?: 'PLAN' | 'RECEIVED'
}
export interface PreArrangeDispatchPayload {
planPalletIds: string[]
route?: string
fbaAppointmentNo?: string
fbaAppointmentStatus?: string
vehicleAppointmentStatus?: string
}
export interface DispatchSummaryDto {
id: string
dispatchNo: string
vehiclePlate?: string | null
driverName?: string | null
driverPhone?: string | null
carrierName?: string | null
orderNos?: string | null
route?: string | null
overseasWarehouseId?: string | null
overseasWarehouseName?: string | null
departAt?: string | null
fbaAppointmentNo?: string | null
fbaAppointmentStatus?: string | null
vehicleAppointmentStatus?: string | null
estimatedArrivalAt?: string | null
status?: string | null
itemCount?: number | null
palletCount?: number | null
packageCount?: number | null
pickedPackageCount?: number | null
shippedPackageCount?: number | null
remainingPackageCount?: number | null
partialCompletionReason?: string | null
createdAt?: string | null
}
export async function listDispatches(query: DispatchQuery) {
const response = await overseasHttp.get<ApiEnvelope<OverseasPageResult<DispatchSummaryDto>>, ApiEnvelope<OverseasPageResult<DispatchSummaryDto>>>(
'/platform/v1/last-leg/dispatches',
{ params: query },
)
return unwrapApiResponse(response)
}
export async function listDispatchCandidates(query: DispatchCandidateQuery) {
const response = await overseasHttp.get<ApiEnvelope<DispatchPalletCandidateDto[]>, ApiEnvelope<DispatchPalletCandidateDto[]>>(
'/platform/v1/last-leg/dispatches/candidates',
{ params: query },
)
return unwrapApiResponse(response)
}
export async function preArrangeDispatch(payload: PreArrangeDispatchPayload) {
const response = await overseasHttp.post<ApiEnvelope<DispatchSummaryDto>, ApiEnvelope<DispatchSummaryDto>>(
'/platform/v1/last-leg/dispatches/pre-arrange',
payload,
)
return unwrapApiResponse(response)
}
@@ -23,11 +23,11 @@ describe('inbound order overseas API', () => {
expect(overseasHttp.put).toHaveBeenCalledWith('/platform/v1/last-leg/inbound-plans/fba-appointments/APT%2F1', expect.objectContaining({ customerId: '190000000000000001' }))
})
it('loads the paginated list with an explicit customer scope', async () => {
it('loads the paginated list across all customers without a customer scope', async () => {
vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', message: 'success', data: { records: [], page: 1, size: 20, total: 0, pages: 0 } })
await listInboundOrders({ customerId: '190000000000000001', page: 1, size: 20, appointmentStatus: 'UNAPPOINTED' })
await listInboundOrders({ page: 1, size: 20, appointmentStatus: 'UNAPPOINTED' })
expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/inbound-plans', {
params: { customerId: '190000000000000001', page: 1, size: 20, appointmentStatus: 'UNAPPOINTED' },
params: { page: 1, size: 20, appointmentStatus: 'UNAPPOINTED' },
})
})
@@ -3,7 +3,6 @@ import { unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import type { OverseasPageResult } from './lastMileOrders'
export interface InboundOrderQuery {
customerId: string
page: number
size: number
planNo?: string
@@ -68,9 +67,8 @@ export interface FbaAppointmentDto {
export type UpdateFbaAppointmentPayload = Omit<BatchFbaAppointmentPayload, 'lineIds' | 'appointmentNo'>
export async function listInboundOrders(query: InboundOrderQuery) {
const { customerId, ...params } = query
const response = await overseasHttp.get<ApiEnvelope<OverseasPageResult<InboundOrderSummaryDto>>, ApiEnvelope<OverseasPageResult<InboundOrderSummaryDto>>>(
'/platform/v1/last-leg/inbound-plans', { params: { customerId, ...params } },
'/platform/v1/last-leg/inbound-plans', { params: query },
)
return unwrapApiResponse(response)
}
@@ -9,22 +9,22 @@ describe('overseas inventory API', () => {
it('keeps skuCode and nullable location filters on real balance queries', async () => {
vi.mocked(overseasHttp.get).mockResolvedValueOnce({ code: '200', data: { records: [], page: 1, size: 20, total: 0, pages: 0 } }).mockResolvedValueOnce({ code: '200', data: { totalPackages: 0 } })
const query = { customerId: '100', page: 1, size: 20, skuCode: 'SKU-1', warehouseLocationCode: 'A-01' }
const query = { page: 1, size: 20, skuCode: 'SKU-1', warehouseLocationCode: 'A-01' }
await listInventoryBalances(query)
await getInventorySummary(query)
expect(overseasHttp.get).toHaveBeenNthCalledWith(1, '/platform/v1/last-leg/inventory/balances', { params: query })
expect(overseasHttp.get).toHaveBeenNthCalledWith(2, '/platform/v1/last-leg/inventory/summary', { params: { customerId: '100', skuCode: 'SKU-1', warehouseLocationCode: 'A-01' } })
expect(overseasHttp.get).toHaveBeenNthCalledWith(2, '/platform/v1/last-leg/inventory/summary', { params: { skuCode: 'SKU-1', warehouseLocationCode: 'A-01' } })
})
it('uses the read-only position and ledger endpoints with explicit customer scope', async () => {
it('uses the read-only position and ledger endpoints without customer scope', async () => {
vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', data: [] })
await listInventoryPositions('190000000000000099', '100')
await listInventoryLedger({ customerId: '100', page: 1, size: 20, skuCode: 'SKU-1' })
await listInventoryPositions('190000000000000099')
await listInventoryLedger({ page: 1, size: 20, skuCode: 'SKU-1' })
expect(overseasHttp.get).toHaveBeenNthCalledWith(1, '/platform/v1/last-leg/inventory/balances/190000000000000099/positions', { params: { customerId: '100' } })
expect(overseasHttp.get).toHaveBeenNthCalledWith(2, '/platform/v1/last-leg/inventory/ledger', { params: { customerId: '100', page: 1, size: 20, skuCode: 'SKU-1' } })
expect(overseasHttp.get).toHaveBeenNthCalledWith(1, '/platform/v1/last-leg/inventory/balances/190000000000000099/positions', { params: {} })
expect(overseasHttp.get).toHaveBeenNthCalledWith(2, '/platform/v1/last-leg/inventory/ledger', { params: { page: 1, size: 20, skuCode: 'SKU-1' } })
})
})
@@ -15,10 +15,9 @@ export interface InventoryBalanceDto {
inboundOrderId: string
inboundOrderNo?: string | null
inboundCode: string
skuCodes: string[]
productNames: string[]
warehouseLocationIds: string[]
warehouseLocationCodes: string[]
skuCode?: string | null
productNameZh?: string | null
productNameEn?: string | null
totalPackages: number
availablePackages: number
holdPackages: number
@@ -46,7 +45,6 @@ export interface InventoryDashboardDto {
export interface InventoryPositionDto {
id: string
balanceId: string
customerId: string
overseasWarehouseId?: string | null
destinationWarehouseCode?: string | null
inboundOrderId?: string | null
@@ -93,7 +91,6 @@ export interface InventoryLedgerDto {
}
export interface InventoryBalanceQuery {
customerId: string
page: number
size: number
customerName?: string
@@ -129,18 +126,16 @@ export async function getInventorySummary(query: InventoryBalanceQuery) {
export async function listInventoryPositions(
balanceId: string,
customerId: string,
query: { palletNo?: string; batchNo?: string; inventoryStatus?: string } = {},
) {
const response = await overseasHttp.get<ApiEnvelope<InventoryPositionDto[]>, ApiEnvelope<InventoryPositionDto[]>>(
`/platform/v1/last-leg/inventory/balances/${encodeURIComponent(balanceId)}/positions`,
{ params: { customerId, ...query } },
{ params: query },
)
return unwrapApiResponse(response)
}
export interface InventoryLedgerQuery {
customerId: string
page: number
size: number
overseasWarehouseId?: string
@@ -1,30 +1,36 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { overseasHttp } from '@/shared/api/overseasHttp'
import { getLastMileOrder, listLastMileOrders } from './lastMileOrders'
import { getLastMileOrder, listLastMileOrders, transferLastMileOrders } from './lastMileOrders'
vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn() } }))
vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn(), put: 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 () => {
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({ customerId: '190000000000000001', page: 2, size: 20, status: 'PENDING_ASSIGNMENT' })).resolves.toMatchObject({ page: 2 })
await expect(listLastMileOrders({ 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' },
params: { 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' },
})
await getLastMileOrder('190000000000000099')
expect(overseasHttp.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('uses the real destination transfer endpoint and edit contract', async () => {
vi.mocked(overseasHttp.put).mockResolvedValue({ code: '200', message: 'success', data: 1 })
await transferLastMileOrders({
orderIds: ['190000000000000099'], warehouseId: 'WH-1', warehouseCode: 'ONT8', warehouseName: 'Ontario',
})
expect(overseasHttp.put).toHaveBeenCalledWith('/platform/v1/last-leg/orders/destination', expect.objectContaining({ orderIds: ['190000000000000099'] }))
})
})
@@ -10,24 +10,33 @@ export interface OverseasPageResult<T> {
}
export interface LastMileOrderQuery {
customerId: string
page: number
size: number
orderNo?: string
inboundCode?: string
containerNo?: string
status?: string
sourceOrderNo?: string
transferWarehouseCode?: string
abnormal?: boolean
detained?: boolean
createdFrom?: string
createdTo?: string
}
export interface LastMileOrderSummaryDto {
id: string
customerId: string
orderNo: string
orderType?: string | null
inboundCode?: string | null
sourceOrderNo?: string | null
sourceInboundId?: string | null
customerName?: string | null
overseasWarehouseName?: string | null
transferWarehouseName?: string | null
transferWarehouseId?: string | null
transferWarehouseCode?: string | null
fbaWarehouseCode?: string | null
containerNo?: string | null
containerStatus?: string | null
@@ -42,8 +51,9 @@ export interface LastMileOrderSummaryDto {
actualArrivalAt?: string | null
estimatedWarehouseAt?: string | null
inboundOrderNo?: string | null
outboundOrderNo?: string | null
/** Legacy read-only association retained for compatibility; absent from current service DTO. */
deliveryOrderNo?: string | null
outboundOrderNo?: string | null
relabelOrderNo?: string | null
relabelStatus?: string | null
palletMergeFlag?: string | null
@@ -81,11 +91,18 @@ export interface LastMileStatusHistoryDto {
id: string
fromStatus?: string | null
toStatus?: string | null
eventId?: string | null
remark?: string | null
createdAt?: string | null
}
export interface TransferDestinationPayload {
orderIds: string[]
warehouseId: string
warehouseCode: string
warehouseName: string
remark?: string
}
export interface LastMileOrderDetailDto {
order: LastMileOrderDto
cargo: LastMileCargoDto[]
@@ -93,18 +110,23 @@ export interface LastMileOrderDetailDto {
}
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 } },
{ params: query },
)
return unwrapApiResponse(response)
}
export async function getLastMileOrder(orderId: string, customerId: string) {
export async function getLastMileOrder(orderId: string) {
const response = await overseasHttp.get<ApiEnvelope<LastMileOrderDetailDto>, ApiEnvelope<LastMileOrderDetailDto>>(
`/platform/v1/last-leg/orders/${encodeURIComponent(orderId)}`,
{ params: { customerId } },
)
return unwrapApiResponse(response)
}
export async function transferLastMileOrders(payload: TransferDestinationPayload) {
const response = await overseasHttp.put<ApiEnvelope<number>, ApiEnvelope<number>>(
'/platform/v1/last-leg/orders/destination', payload,
)
return unwrapApiResponse(response)
}
@@ -7,24 +7,26 @@ vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn(), post
describe('overseas pallet API', () => {
beforeEach(() => { vi.clearAllMocks() })
it('uses the customer and warehouse scoped pallet CRUD contract', async () => {
it('uses the public warehouse scoped pallet CRUD contract', async () => {
const page = { records: [], page: 1, size: 20, total: 0, pages: 0 }
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-1', palletType: 'WOOD', capacityCbm: 2, unitPrice: 10, status: 'IDLE' }
const payload = { palletCode: 'PLT-NEW', palletType: 'WOOD', fbaWarehouseCode: 'ONT8', capacityCbm: 2, unitPrice: 10, status: 'IDLE' }
await listCustomerWarehouses('100')
await listPallets('100', '200', { page: 1, size: 20, keyword: 'PLT' })
await getPallet('100', '200', '9')
await createPallet('100', '200', payload)
await updatePallet('100', '200', '9', payload)
await deletePallet('100', '200', '9')
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')
expect(overseasHttp.get).toHaveBeenNthCalledWith(2, '/platform/v1/warehouse-admin/200/pallets', { params: { customerId: '100', page: 1, size: 20, keyword: 'PLT' } })
expect(overseasHttp.post).toHaveBeenCalledWith('/platform/v1/warehouse-admin/200/pallets', payload, { params: { customerId: '100' } })
expect(overseasHttp.put).toHaveBeenCalledWith('/platform/v1/warehouse-admin/200/pallets/9', payload, { params: { customerId: '100' } })
expect(overseasHttp.delete).toHaveBeenCalledWith('/platform/v1/warehouse-admin/200/pallets/9', { params: { customerId: '100' } })
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', {})
})
})
+14 -12
View File
@@ -18,6 +18,7 @@ export interface PalletDto {
locationId?: string | null
palletCode: string
palletType: string
fbaWarehouseCode: string
capacityCbm: number
unitPrice: number
status: string
@@ -29,6 +30,7 @@ export interface PalletDto {
export interface SavePalletPayload {
palletCode: string
palletType: string
fbaWarehouseCode: string
capacityCbm: number
unitPrice: number
status: string
@@ -36,49 +38,49 @@ export interface SavePalletPayload {
remark?: string | null
}
export async function listCustomerWarehouses(customerId: string) {
export async function listCustomerWarehouses() {
const response = await overseasHttp.get<ApiEnvelope<OverseasPageResult<CustomerWarehouseDto>>, ApiEnvelope<OverseasPageResult<CustomerWarehouseDto>>>(
'/platform/v1/warehouse-admin',
{ params: { customerId, page: 1, size: 200, status: 'ACTIVE' } },
{ params: { page: 1, size: 200, status: 'ACTIVE' } },
)
return unwrapApiResponse(response)
}
export async function listPallets(customerId: string, warehouseId: string, query: { page: number; size: number; keyword?: string; palletType?: string; status?: string }) {
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>>>(
`/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/pallets`,
{ params: { customerId, ...query } },
{ params: { ...query } },
)
return unwrapApiResponse(response)
}
export async function getPallet(customerId: string, warehouseId: string, palletId: string) {
export async function getPallet(warehouseId: string, palletId: string) {
const response = await overseasHttp.get<ApiEnvelope<PalletDto>, ApiEnvelope<PalletDto>>(
`/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/pallets/${encodeURIComponent(palletId)}`,
{ params: { customerId } },
{},
)
return unwrapApiResponse(response)
}
export async function createPallet(customerId: string, warehouseId: string, payload: SavePalletPayload) {
export async function createPallet(warehouseId: string, payload: SavePalletPayload) {
const response = await overseasHttp.post<ApiEnvelope<string>, ApiEnvelope<string>>(
`/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/pallets`, payload, { params: { customerId } },
`/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/pallets`, payload,
)
return unwrapApiResponse(response)
}
export async function updatePallet(customerId: string, warehouseId: string, palletId: string, payload: SavePalletPayload) {
export async function updatePallet(warehouseId: string, palletId: string, payload: SavePalletPayload) {
const response = await overseasHttp.put<ApiEnvelope<null>, ApiEnvelope<null>>(
`/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/pallets/${encodeURIComponent(palletId)}`,
payload, { params: { customerId } },
payload,
)
return unwrapApiResponse(response)
}
export async function deletePallet(customerId: string, warehouseId: string, palletId: string) {
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)}`,
{ params: { customerId } },
{},
)
return unwrapApiResponse(response)
}
@@ -1,21 +1,31 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { overseasHttp } from '@/shared/api/overseasHttp'
import { getRelabelOrder, listRelabelOrders } from './relabelOrders'
import { changeRelabelOrderStatus, createRelabelOrder, getRelabelOrder, listRelabelOrders } from './relabelOrders'
vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn() } }))
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())
it('uses explicit customer scope for the real page endpoint', async () => {
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 } })
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' } })
await listRelabelOrders({ page: 1, size: 20, skuCode: 'SKU-1' })
expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/relabel-applications', { params: { 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' } })
await getRelabelOrder('RL/1')
expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/relabel-applications/RL%2F1')
})
it('uses the real create and status endpoints', async () => {
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' })
})
})
@@ -3,13 +3,40 @@ 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 CreateRelabelItemPayload {
skuCode: string
productNameZh: string
totalPackageCount: number
notifiedPackageCount: number
shipmentNo: string
newSkuCode: string
labelRequirement?: string
productImageFileId?: string
productImageFileName?: string
productImageUrl?: string
trackingCode?: string
relabelFileId?: string
relabelFileName?: string
relabelFileUrl?: string
}
export interface CreateRelabelPayload {
customerOrderNo: string
overseasWarehouseId: string
labelDeadline: string
contactName: string
contactPhone: string
remark?: string
idempotencyKey: string
items: CreateRelabelItemPayload[]
}
export interface RelabelSummaryDto {
id: string
relabelNo: string
@@ -56,16 +83,30 @@ export interface RelabelDetailDto {
}
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 } },
'/platform/v1/last-leg/relabel-applications', { params: query },
)
return unwrapApiResponse(response)
}
export async function getRelabelOrder(relabelNo: string, customerId: string) {
export async function getRelabelOrder(relabelNo: string) {
const response = await overseasHttp.get<ApiEnvelope<RelabelDetailDto>, ApiEnvelope<RelabelDetailDto>>(
`/platform/v1/last-leg/relabel-applications/${encodeURIComponent(relabelNo)}`, { params: { customerId } },
`/platform/v1/last-leg/relabel-applications/${encodeURIComponent(relabelNo)}`,
)
return unwrapApiResponse(response)
}
export async function createRelabelOrder(payload: CreateRelabelPayload) {
const response = await overseasHttp.post<ApiEnvelope<RelabelDetailDto>, ApiEnvelope<RelabelDetailDto>>(
'/platform/v1/last-leg/relabel-applications', payload,
)
return unwrapApiResponse(response)
}
export async function changeRelabelOrderStatus(relabelNo: string, status: string, remark?: string) {
const response = await overseasHttp.put<ApiEnvelope<RelabelDetailDto>, ApiEnvelope<RelabelDetailDto>>(
`/platform/v1/last-leg/relabel-applications/${encodeURIComponent(relabelNo)}/status`,
{ status, remark },
)
return unwrapApiResponse(response)
}