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