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