优化提交

This commit is contained in:
2026-08-20 18:04:38 +08:00
parent 4ae5e7c672
commit c4cd4b42b3
17 changed files with 814 additions and 105 deletions
@@ -0,0 +1,47 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { overseasHttp } from '@/shared/api/overseasHttp'
import { createFbaAppointment, getFbaAppointment, listInboundOrders, updateFbaAppointment } from './inboundOrders'
vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn(), post: vi.fn(), put: vi.fn() } }))
describe('inbound order overseas API', () => {
beforeEach(() => {
vi.mocked(overseasHttp.get).mockReset()
vi.mocked(overseasHttp.post).mockReset()
vi.mocked(overseasHttp.put).mockReset()
})
it('loads appointment details and updates the existing appointment without replacing its lines', async () => {
const detail = { id: '1', customerId: '190000000000000001', appointmentNo: 'APT/1', appointmentAt: '2026-08-20T10:30:00', expectedLoadingAt: '2026-08-20T09:00:00', appointmentStatus: 'SUCCESS', selectedCount: 2, fbaWarehouseCode: 'ONT8', items: [] }
vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', message: 'success', data: detail })
vi.mocked(overseasHttp.put).mockResolvedValue({ code: '200', message: 'success', data: detail })
await getFbaAppointment('190000000000000001', 'APT/1')
await updateFbaAppointment('APT/1', { customerId: '190000000000000001', appointmentAt: detail.appointmentAt, expectedLoadingAt: detail.expectedLoadingAt, appointmentStatus: 'SUCCESS' })
expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/inbound-plans/fba-appointments/APT%2F1', { params: { customerId: '190000000000000001' } })
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 () => {
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' })
expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/inbound-plans', {
params: { customerId: '190000000000000001', page: 1, size: 20, appointmentStatus: 'UNAPPOINTED' },
})
})
it('preserves selected line ids as strings when creating an appointment', async () => {
vi.mocked(overseasHttp.post).mockResolvedValue({ code: '200', message: 'success', data: { id: '1', appointmentNo: 'APT-1', appointmentStatus: 'SUCCESS', selectedCount: 1, fbaWarehouseCode: 'ONT8' } })
const payload = {
customerId: '190000000000000001',
lineIds: ['190000000000000099'],
appointmentNo: 'APT-1',
appointmentAt: '2026-08-20T10:30:00',
expectedLoadingAt: '2026-08-20T09:00:00',
appointmentStatus: 'SUCCESS' as const,
}
await createFbaAppointment(payload)
expect(overseasHttp.post).toHaveBeenCalledWith('/platform/v1/last-leg/inbound-plans/fba-appointments/batch', payload)
})
})
@@ -0,0 +1,99 @@
import { overseasHttp } from '@/shared/api/overseasHttp'
import { unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import type { OverseasPageResult } from './lastMileOrders'
export interface InboundOrderQuery {
customerId: string
page: number
size: number
planNo?: string
inboundCode?: string
status?: string
warehouse?: string
fbaWarehouseCode?: string
appointmentStatus?: string
dispatchStatus?: string
createdFrom?: string
createdTo?: string
}
export interface InboundOrderSummaryDto {
lineId: string
planId: string
planNo: string
customerId: string
orderId: string
inboundCode?: string | null
orderType?: string | null
warehouseId?: string | null
warehouseCode?: string | null
warehouseName?: string | null
fbaWarehouseCode?: string | null
customerName?: string | null
packageCount?: number | null
volumeCbm?: number | null
palletCount?: number | null
status?: string | null
appointmentNo?: string | null
appointmentStatus?: string | null
appointmentAt?: string | null
expectedLoadingAt?: string | null
dispatchStatus?: string | null
createdAt?: string | null
}
export interface BatchFbaAppointmentPayload {
customerId: string
lineIds: string[]
appointmentNo: string
appointmentAt: string
expectedLoadingAt: string
appointmentStatus: 'PENDING' | 'SUCCESS' | 'FAILED' | 'CANCELLED'
appointmentRemark?: string
}
export interface FbaAppointmentDto {
id: string
customerId: string
appointmentNo: string
appointmentAt: string
expectedLoadingAt: string
appointmentStatus: string
appointmentRemark?: string | null
selectedCount: number
fbaWarehouseCode: string
items: Array<{ id: string; planId: string; lineId: string; orderId: string; inboundCode: string; fbaWarehouseCode: string }>
}
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 } },
)
return unwrapApiResponse(response)
}
export async function createFbaAppointment(payload: BatchFbaAppointmentPayload) {
const response = await overseasHttp.post<ApiEnvelope<FbaAppointmentDto>, ApiEnvelope<FbaAppointmentDto>>(
'/platform/v1/last-leg/inbound-plans/fba-appointments/batch', payload,
)
return unwrapApiResponse(response)
}
export async function getFbaAppointment(customerId: string, appointmentNo: string) {
const response = await overseasHttp.get<ApiEnvelope<FbaAppointmentDto>, ApiEnvelope<FbaAppointmentDto>>(
`/platform/v1/last-leg/inbound-plans/fba-appointments/${encodeURIComponent(appointmentNo)}`,
{ params: { customerId } },
)
return unwrapApiResponse(response)
}
export async function updateFbaAppointment(appointmentNo: string, payload: UpdateFbaAppointmentPayload) {
const response = await overseasHttp.put<ApiEnvelope<FbaAppointmentDto>, ApiEnvelope<FbaAppointmentDto>>(
`/platform/v1/last-leg/inbound-plans/fba-appointments/${encodeURIComponent(appointmentNo)}`,
payload,
)
return unwrapApiResponse(response)
}
@@ -0,0 +1,20 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { overseasHttp } from '@/shared/api/overseasHttp'
import { getInventorySummary, listInventoryBalances } from './inventory'
vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn() } }))
describe('overseas inventory API', () => {
beforeEach(() => { vi.clearAllMocks() })
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' }
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' } })
})
})
@@ -0,0 +1,74 @@
import { overseasHttp } from '@/shared/api/overseasHttp'
import { unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import type { OverseasPageResult } from './lastMileOrders'
export interface InventoryBalanceDto {
id: string
customerId: string
customerName?: string | null
overseasWarehouseId: string
overseasWarehouseCode?: string | null
overseasWarehouseName?: string | null
destinationWarehouseId?: string | null
destinationWarehouseCode?: string | null
destinationWarehouseName?: string | null
inboundOrderId: string
inboundOrderNo?: string | null
inboundCode: string
skuCodes: string[]
productNames: string[]
warehouseLocationIds: string[]
warehouseLocationCodes: string[]
totalPackages: number
availablePackages: number
holdPackages: number
allocatedPackages: number
pickedPackages: number
inTransitPackages: number
receivingPackages: number
procurementPackages: number
updatedAt?: string | null
}
export interface InventoryDashboardDto {
inboundOrderCount: number
palletCount: number
totalPackages: number
availablePackages: number
holdPackages: number
allocatedPackages: number
pickedPackages: number
inTransitPackages: number
receivingPackages: number
procurementPackages: number
}
export interface InventoryBalanceQuery {
customerId: string
page: number
size: number
customerName?: string
overseasWarehouseId?: string
skuCode?: string
productName?: string
batchNo?: string
warehouseLocationCode?: string
inventoryStatus?: string
}
function params(query: InventoryBalanceQuery) { return { ...query } }
export async function listInventoryBalances(query: InventoryBalanceQuery) {
const response = await overseasHttp.get<ApiEnvelope<OverseasPageResult<InventoryBalanceDto>>, ApiEnvelope<OverseasPageResult<InventoryBalanceDto>>>(
'/platform/v1/last-leg/inventory/balances', { params: params(query) },
)
return unwrapApiResponse(response)
}
export async function getInventorySummary(query: InventoryBalanceQuery) {
const { page: _page, size: _size, ...summaryParams } = params(query)
const response = await overseasHttp.get<ApiEnvelope<InventoryDashboardDto>, ApiEnvelope<InventoryDashboardDto>>(
'/platform/v1/last-leg/inventory/summary', { params: summaryParams },
)
return unwrapApiResponse(response)
}
@@ -0,0 +1,30 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { overseasHttp } from '@/shared/api/overseasHttp'
import { createPallet, deletePallet, getPallet, listCustomerWarehouses, listPallets, updatePallet } from './pallets'
vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn() } }))
describe('overseas pallet API', () => {
beforeEach(() => { vi.clearAllMocks() })
it('uses the customer and 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' }
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')
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' } })
})
})
@@ -0,0 +1,84 @@
import { overseasHttp } from '@/shared/api/overseasHttp'
import { unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import type { OverseasPageResult } from './lastMileOrders'
export interface CustomerWarehouseDto {
id: string
customerId: string
warehouseCode: string
warehouseName: string
warehouseType: string
status: string
}
export interface PalletDto {
id: string
customerId: string
warehouseId: string
locationId?: string | null
palletCode: string
palletType: string
capacityCbm: number
unitPrice: number
status: string
remark?: string | null
createdAt?: string | null
updatedAt?: string | null
}
export interface SavePalletPayload {
palletCode: string
palletType: string
capacityCbm: number
unitPrice: number
status: string
locationId?: string | null
remark?: string | null
}
export async function listCustomerWarehouses(customerId: string) {
const response = await overseasHttp.get<ApiEnvelope<OverseasPageResult<CustomerWarehouseDto>>, ApiEnvelope<OverseasPageResult<CustomerWarehouseDto>>>(
'/platform/v1/warehouse-admin',
{ params: { customerId, 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 }) {
const response = await overseasHttp.get<ApiEnvelope<OverseasPageResult<PalletDto>>, ApiEnvelope<OverseasPageResult<PalletDto>>>(
`/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/pallets`,
{ params: { customerId, ...query } },
)
return unwrapApiResponse(response)
}
export async function getPallet(customerId: string, 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) {
const response = await overseasHttp.post<ApiEnvelope<string>, ApiEnvelope<string>>(
`/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/pallets`, payload, { params: { customerId } },
)
return unwrapApiResponse(response)
}
export async function updatePallet(customerId: string, 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 } },
)
return unwrapApiResponse(response)
}
export async function deletePallet(customerId: string, 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)
}