This commit is contained in:
2026-08-06 13:56:04 +08:00
parent 6736893128
commit a813426588
35 changed files with 2822 additions and 489 deletions
@@ -2,8 +2,10 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { http } from '@/shared/api/http'
import {
changePlatformWarehouseStatus,
createFbaWarehouse,
createPlatformWarehouse,
getPlatformWarehouse,
importWarehouses,
listPlatformWarehouses,
updatePlatformWarehouse,
type SavePlatformWarehouseRequest,
@@ -15,12 +17,11 @@ vi.mock('@/shared/api/http', () => ({
}))
const payload: SavePlatformWarehouseRequest = {
tenantId: 10001,
warehouseCode: 'CN-YW-01',
warehouseName: '义乌收货仓',
warehouseType: 'DOMESTIC',
countryCode: 'CN',
timezone: 'Asia/Shanghai',
timezone: 'UTC+8',
contactName: '仓库客服',
contactPhone: '0579-12345678',
contactEmail: null,
@@ -55,20 +56,45 @@ describe('platform warehouse API', () => {
vi.mocked(http.patch).mockReset()
})
it('lists and loads warehouse details for the target tenant', async () => {
const page = { records: [], page: 1, size: 200, total: 0, pages: 0 }
it('lists server-paged platform warehouses and normalizes summary fields', async () => {
const page = {
records: [{
id: '1',
tenantId: 0,
warehouseCode: 'LAX9',
warehouseName: 'LAX9',
warehouseType: 'FBA',
countryCode: 'US',
timezone: 'UTC-8',
contactName: null,
contactPhone: null,
contactEmail: null,
status: 'ACTIVE',
remark: null,
}],
page: '1',
size: '20',
total: '1',
pages: '1',
}
const detail = { warehouse: {}, addresses: [], capabilities: [] }
vi.mocked(http.get)
.mockResolvedValueOnce({ code: '200', message: 'success', data: page })
.mockResolvedValueOnce({ code: '200', message: 'success', data: detail })
await expect(listPlatformWarehouses(10001)).resolves.toEqual(page)
await expect(getPlatformWarehouse(10001, '19001')).resolves.toEqual(detail)
await expect(listPlatformWarehouses({ page: 1, size: 20, keyword: 'LAX', warehouseType: 'FBA', status: 'ACTIVE' })).resolves.toEqual({
records: [{ ...page.records[0], primaryAddress: null, addressSummary: null }],
page: 1,
size: 20,
total: 1,
pages: 1,
})
await expect(getPlatformWarehouse('19001')).resolves.toEqual(detail)
expect(http.get).toHaveBeenNthCalledWith(1, '/platform/v1/warehouse-admin', {
params: { tenantId: 10001, page: 1, size: 200 },
params: { page: 1, size: 20, keyword: 'LAX', warehouseType: 'FBA', status: 'ACTIVE' },
})
expect(http.get).toHaveBeenNthCalledWith(2, '/platform/v1/warehouse-admin/19001', {
params: { tenantId: 10001 },
params: { tenantId: 0 },
})
})
@@ -89,15 +115,87 @@ describe('platform warehouse API', () => {
vi.mocked(http.patch).mockResolvedValue({ code: '200', message: 'success', data: null })
await expect(createPlatformWarehouse(payload)).resolves.toBe('19001')
await expect(updatePlatformWarehouse(10001, '19001', payload)).resolves.toBeNull()
await expect(changePlatformWarehouseStatus(10001, '19001', 'INACTIVE')).resolves.toBeNull()
await expect(updatePlatformWarehouse('19001', payload)).resolves.toBeNull()
await expect(changePlatformWarehouseStatus('19001', 'INACTIVE')).resolves.toBeNull()
expect(http.post).toHaveBeenCalledWith('/platform/v1/warehouse-admin', payload)
expect(http.put).toHaveBeenCalledWith('/platform/v1/warehouse-admin/19001', payload, {
params: { tenantId: 10001 },
expect(http.post).toHaveBeenCalledWith('/platform/v1/warehouse-admin', { ...payload, tenantId: 0 })
expect(http.put).toHaveBeenCalledWith('/platform/v1/warehouse-admin/19001', { ...payload, tenantId: 0 }, {
params: { tenantId: 0 },
})
expect(http.patch).toHaveBeenCalledWith('/platform/v1/warehouse-admin/19001/status', undefined, {
params: { tenantId: 10001, status: 'INACTIVE' },
params: { tenantId: 0, status: 'INACTIVE' },
})
})
it('uses the FBA fast-create defaults and implemented import routes without client-side success simulation', async () => {
vi.mocked(http.post)
.mockResolvedValueOnce({ code: '200', message: 'success', data: '19002' })
.mockResolvedValueOnce({
code: '200',
message: 'success',
data: {
fileName: 'fba.xlsx',
totalRows: '3',
createdRows: '1',
updatedRows: '1',
unchangedRows: '0',
success: false,
committed: false,
errors: [{ row: '3', field: '仓库编码', message: '编码重复' }],
},
})
await expect(createFbaWarehouse({
warehouseCode: 'LAX9',
stateProvince: 'CA',
city: 'Long Beach',
postalCode: '90802',
addressLine1: '2400 E Spring St',
})).resolves.toBe('19002')
const file = new File(['content'], 'warehouses.xlsx')
await expect(importWarehouses(file, true, 'CREATE_ONLY')).resolves.toMatchObject({
totalRows: 3,
validRows: 2,
createCount: 1,
updateCount: 1,
unchangedCount: 0,
errorCount: 1,
committed: false,
errors: [{ row: 3, field: '仓库编码', message: '编码重复' }],
})
expect(http.post).toHaveBeenNthCalledWith(1, '/platform/v1/warehouse-admin', {
warehouseCode: 'LAX9',
warehouseName: 'LAX9',
warehouseType: 'FBA',
countryCode: 'US',
timezone: '',
contactName: null,
contactPhone: null,
contactEmail: null,
status: 'ACTIVE',
remark: null,
address: {
addressType: 'OPERATING',
countryCode: 'US',
stateProvince: 'CA',
city: 'Long Beach',
district: null,
postalCode: '90802',
addressLine1: '2400 E Spring St',
addressLine2: null,
latitude: null,
longitude: null,
},
capabilities: [],
tenantId: 0,
})
expect(http.post).toHaveBeenNthCalledWith(
2,
'/platform/v1/warehouse-admin/import',
expect.any(FormData),
{ params: { tenantId: 0, dryRun: true, mode: 'CREATE_ONLY' } },
)
})
})
+172 -17
View File
@@ -54,14 +54,34 @@ export interface PlatformWarehouseDetailResource {
capabilities: PlatformWarehouseCapabilityResource[]
}
export interface PlatformWarehouseSummaryResource extends PlatformWarehouseResource {
primaryAddress?: PlatformWarehouseAddressResource | null
addressSummary?: string | null
}
export interface PlatformWarehousePage {
records: PlatformWarehouseResource[]
records: PlatformWarehouseSummaryResource[]
page: number
size: number
total: number
pages: number
}
type PlatformWarehousePagePayload = Omit<PlatformWarehousePage, 'page' | 'size' | 'total' | 'pages'> & {
page: number | string
size: number | string
total: number | string
pages: number | string
}
export interface PlatformWarehouseQuery {
page: number
size: number
keyword?: string
warehouseType?: WarehouseType
status?: PlatformWarehouseStatus
}
export interface PlatformWarehouseAddressRequest {
addressType: string
countryCode: string
@@ -84,7 +104,6 @@ export interface PlatformWarehouseCapabilityRequest {
}
export interface SavePlatformWarehouseRequest {
tenantId: number | string
warehouseCode: string
warehouseName: string
warehouseType: WarehouseType
@@ -99,42 +118,178 @@ export interface SavePlatformWarehouseRequest {
capabilities: PlatformWarehouseCapabilityRequest[]
}
const basePath = '/platform/v1/warehouse-admin'
export async function listPlatformWarehouses(tenantId?: number | string, page = 1, size = 200) {
const response = await http.get<ApiEnvelope<PlatformWarehousePage>, ApiEnvelope<PlatformWarehousePage>>(basePath, {
params: { ...(tenantId === undefined || tenantId === '' ? {} : { tenantId }), page, size },
})
return unwrapApiResponse(response)
export interface CreateFbaWarehouseRequest {
warehouseCode: string
stateProvince: string
city: string
postalCode: string
addressLine1: string
timezone?: string | null
}
export async function getPlatformWarehouse(tenantId: number | string, warehouseId: number | string) {
export type WarehouseImportMode = 'CREATE_ONLY' | 'UPSERT'
export interface WarehouseImportError {
row: number
field: string
code?: string | null
message: string
}
export interface WarehouseImportResult {
fileName: string
totalRows: number
validRows: number
createCount: number
updateCount: number
unchangedCount: number
errorCount: number
committed: boolean
errors: WarehouseImportError[]
}
const basePath = '/platform/v1/warehouse-admin'
const platformResourceScope = 0
type PlatformWarehouseApiSaveRequest = SavePlatformWarehouseRequest & { tenantId: number }
type WarehouseImportErrorPayload = Omit<WarehouseImportError, 'row'> & { row: number | string }
type WarehouseImportResultPayload = Partial<Omit<
WarehouseImportResult,
'totalRows' | 'validRows' | 'createCount' | 'updateCount' | 'unchangedCount' | 'errorCount' | 'committed' | 'errors'
>> & {
totalRows: number | string
validRows?: number | string
createCount?: number | string
createdRows?: number | string
updateCount?: number | string
updatedRows?: number | string
unchangedCount?: number | string
unchangedRows?: number | string
errorCount?: number | string
success?: boolean
committed?: boolean
errors?: WarehouseImportErrorPayload[] | null
}
function withPlatformScope(payload: SavePlatformWarehouseRequest): PlatformWarehouseApiSaveRequest {
return { ...payload, tenantId: platformResourceScope }
}
function toNumber(value: number | string | undefined) {
return Number(value ?? 0)
}
function normalizeWarehouseImportResult(result: WarehouseImportResultPayload, dryRun: boolean): WarehouseImportResult {
const errors = (result.errors || []).map((error) => ({ ...error, row: toNumber(error.row) }))
const errorCount = toNumber(result.errorCount) || errors.length
const totalRows = toNumber(result.totalRows)
return {
...result,
fileName: result.fileName || '',
totalRows,
validRows: toNumber(result.validRows) || Math.max(totalRows - errorCount, 0),
createCount: toNumber(result.createCount ?? result.createdRows),
updateCount: toNumber(result.updateCount ?? result.updatedRows),
unchangedCount: toNumber(result.unchangedCount ?? result.unchangedRows),
errorCount,
committed: result.committed ?? (!dryRun && Boolean(result.success) && errorCount === 0),
errors,
}
}
export async function listPlatformWarehouses(query?: PlatformWarehouseQuery): Promise<PlatformWarehousePage>
export async function listPlatformWarehouses(page?: number, size?: number): Promise<PlatformWarehousePage>
export async function listPlatformWarehouses(pageOrQuery: number | PlatformWarehouseQuery = 1, size = 200) {
const query = typeof pageOrQuery === 'number'
? { page: pageOrQuery, size }
: pageOrQuery
const response = await http.get<ApiEnvelope<PlatformWarehousePagePayload>, ApiEnvelope<PlatformWarehousePagePayload>>(basePath, {
params: query,
})
const result = unwrapApiResponse(response)
return {
...result,
records: result.records.map((warehouse) => ({
...warehouse,
primaryAddress: warehouse.primaryAddress ?? null,
addressSummary: warehouse.addressSummary ?? null,
})),
page: Number(result.page),
size: Number(result.size),
total: Number(result.total),
pages: Number(result.pages),
}
}
export async function getPlatformWarehouse(warehouseId: number | string) {
const response = await http.get<ApiEnvelope<PlatformWarehouseDetailResource>, ApiEnvelope<PlatformWarehouseDetailResource>>(
`${basePath}/${warehouseId}`,
{ params: { tenantId } },
{ params: { tenantId: platformResourceScope } },
)
return unwrapApiResponse(response)
}
export async function createPlatformWarehouse(payload: SavePlatformWarehouseRequest) {
const response = await http.post<ApiEnvelope<number | string>, ApiEnvelope<number | string>>(basePath, payload)
const response = await http.post<ApiEnvelope<number | string>, ApiEnvelope<number | string>>(basePath, withPlatformScope(payload))
return unwrapApiResponse(response)
}
export async function updatePlatformWarehouse(tenantId: number | string, warehouseId: number | string, payload: SavePlatformWarehouseRequest) {
const response = await http.put<ApiEnvelope<null>, ApiEnvelope<null>>(`${basePath}/${warehouseId}`, payload, {
params: { tenantId },
export async function createFbaWarehouse(payload: CreateFbaWarehouseRequest) {
return createPlatformWarehouse({
warehouseCode: payload.warehouseCode,
warehouseName: payload.warehouseCode,
warehouseType: 'FBA',
countryCode: 'US',
timezone: payload.timezone?.trim() || '',
contactName: null,
contactPhone: null,
contactEmail: null,
status: 'ACTIVE',
remark: null,
address: {
addressType: 'OPERATING',
countryCode: 'US',
stateProvince: payload.stateProvince,
city: payload.city,
district: null,
postalCode: payload.postalCode,
addressLine1: payload.addressLine1,
addressLine2: null,
latitude: null,
longitude: null,
},
capabilities: [],
})
}
export async function downloadWarehouseImportTemplate() {
return http.get<Blob, Blob>(`${basePath}/import-template`, { responseType: 'blob' })
}
export async function importWarehouses(file: File, dryRun: boolean, mode: WarehouseImportMode) {
const form = new FormData()
form.append('file', file)
const response = await http.post<ApiEnvelope<WarehouseImportResultPayload>, ApiEnvelope<WarehouseImportResultPayload>>(
`${basePath}/import`,
form,
{ params: { tenantId: platformResourceScope, dryRun, mode } },
)
return normalizeWarehouseImportResult(unwrapApiResponse(response), dryRun)
}
export async function updatePlatformWarehouse(warehouseId: number | string, payload: SavePlatformWarehouseRequest) {
const response = await http.put<ApiEnvelope<null>, ApiEnvelope<null>>(`${basePath}/${warehouseId}`, withPlatformScope(payload), {
params: { tenantId: platformResourceScope },
})
return unwrapApiResponse(response)
}
export async function changePlatformWarehouseStatus(
tenantId: number | string,
warehouseId: number | string,
status: PlatformWarehouseStatus,
) {
const response = await http.patch<ApiEnvelope<null>, ApiEnvelope<null>>(`${basePath}/${warehouseId}/status`, undefined, {
params: { tenantId, status },
params: { tenantId: platformResourceScope, status },
})
return unwrapApiResponse(response)
}