优化
This commit is contained in:
@@ -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', {})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user