优化
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { http } from '@/shared/api/http'
|
||||
import { getPlatformUnifiedOrder, listPlatformUnifiedOrders } from './unifiedOrders'
|
||||
|
||||
vi.mock('@/shared/api/http', () => ({
|
||||
http: { get: vi.fn() },
|
||||
unwrapApiResponse: <T>(response: { data: T }) => response.data,
|
||||
}))
|
||||
|
||||
describe('unified orders API contract', () => {
|
||||
beforeEach(() => vi.mocked(http.get).mockReset())
|
||||
|
||||
it('uses the C02 platform orders endpoint and contract filters', async () => {
|
||||
vi.mocked(http.get).mockResolvedValue({ data: { records: [], page: '2', size: '20', total: '0', pages: '0' } })
|
||||
await expect(listPlatformUnifiedOrders({
|
||||
keyword: 'CC', orderNo: 'CC-1', mainStatus: 'WAIT_SPLIT', customerId: '30001',
|
||||
receivingWarehouseId: '11', destinationWarehouseId: '22', page: 2, size: 20,
|
||||
})).resolves.toMatchObject({ page: 2, size: 20, total: 0, pages: 0 })
|
||||
expect(http.get).toHaveBeenCalledWith('/platform/v1/orders', {
|
||||
params: {
|
||||
keyword: 'CC', orderNo: 'CC-1', mainStatus: 'WAIT_SPLIT', customerId: '30001',
|
||||
receivingWarehouseId: '11', destinationWarehouseId: '22', page: 2, size: 20,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('normalizes C02 row IDs and keeps inboundId for measurement routing', async () => {
|
||||
vi.mocked(http.get).mockResolvedValue({ data: {
|
||||
records: [{ orderId: '90001', tenantId: '208', inboundId: '77', inboundVersion: 3, mainStatus: 'WAIT_MEASURE', measurement: null, preload: null, loading: null, delivery: null, transport: null }],
|
||||
page: 1, size: 20, total: 1, pages: 1,
|
||||
} })
|
||||
await expect(listPlatformUnifiedOrders({ page: 1, size: 20 })).resolves.toMatchObject({
|
||||
records: [{ orderId: '90001', tenantId: '208', inboundId: '77', inboundVersion: 3 }],
|
||||
})
|
||||
})
|
||||
|
||||
it('loads the C02 detail shape and passes tenantId only on the detail boundary', async () => {
|
||||
vi.mocked(http.get).mockResolvedValue({ data: {
|
||||
summary: { orderId: '90001', tenantId: '208', mainStatus: 'WAIT_SPLIT' },
|
||||
order: { order: {}, cargoes: [], parties: [], internalNotes: [], statusHistory: [] },
|
||||
timeline: [],
|
||||
} })
|
||||
await expect(getPlatformUnifiedOrder('208', '90001')).resolves.toMatchObject({
|
||||
summary: { orderId: '90001' }, order: { internalNotes: [] }, timeline: [],
|
||||
})
|
||||
expect(http.get).toHaveBeenCalledWith('/platform/v1/orders/90001', { params: { tenantId: '208' } })
|
||||
})
|
||||
|
||||
it('does not expose a guessed generic action endpoint', async () => {
|
||||
expect(http).not.toHaveProperty('post')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,264 @@
|
||||
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
|
||||
|
||||
export const unifiedOrdersApiPath = '/platform/v1/orders'
|
||||
|
||||
export type UnifiedOrderMainStatus =
|
||||
| 'PENDING_REVIEW'
|
||||
| 'WAIT_MEASURE'
|
||||
| 'INBOUNDED'
|
||||
| 'WAIT_SPLIT'
|
||||
| 'LOADING'
|
||||
| 'LOADED'
|
||||
| 'DELIVERED'
|
||||
|
||||
export const unifiedOrderStatusOptions: Array<{ value: UnifiedOrderMainStatus; label: string }> = [
|
||||
{ value: 'PENDING_REVIEW', label: '待审核' },
|
||||
{ value: 'WAIT_MEASURE', label: '待测量数据' },
|
||||
{ value: 'INBOUNDED', label: '已入库' },
|
||||
{ value: 'WAIT_SPLIT', label: '待分柜' },
|
||||
{ value: 'LOADING', label: '装柜中' },
|
||||
{ value: 'LOADED', label: '装柜完成' },
|
||||
{ value: 'DELIVERED', label: '派送完成' },
|
||||
]
|
||||
|
||||
export interface UnifiedOrderMeasurementSummary {
|
||||
reviewStatus: string | null
|
||||
actualGrossWeightKg: number | null
|
||||
actualVolumeCbm: number | null
|
||||
chargeableWeightKg: number | null
|
||||
completedAt: string | null
|
||||
}
|
||||
|
||||
export interface UnifiedOrderPreloadSummary {
|
||||
status: string | null
|
||||
version: number | null
|
||||
itemCount: number | null
|
||||
updatedAt: string | null
|
||||
}
|
||||
|
||||
export interface UnifiedOrderLoadingSummary {
|
||||
status: string | null
|
||||
loadingVersion: number | null
|
||||
pickingVersion: number | null
|
||||
expectedCount: number | null
|
||||
pickedCount: number | null
|
||||
evidenceCount: number | null
|
||||
sealNo: string | null
|
||||
updatedAt: string | null
|
||||
}
|
||||
|
||||
export interface UnifiedOrderDeliverySummary {
|
||||
status: string | null
|
||||
completedAt: string | null
|
||||
trackingNo: string | null
|
||||
}
|
||||
|
||||
export interface UnifiedOrderTransportSummary {
|
||||
loadingStatus: string | null
|
||||
containerNo: string | null
|
||||
cabinetNo: string | null
|
||||
vesselName: string | null
|
||||
voyageNo: string | null
|
||||
sailingAt: string | null
|
||||
portArrivalAt: string | null
|
||||
}
|
||||
|
||||
export interface UnifiedOrderResource {
|
||||
orderId: number | string
|
||||
tenantId: number | string
|
||||
orderNo: string | null
|
||||
mainStatus: UnifiedOrderMainStatus
|
||||
nextAction: string | null
|
||||
internalOrderStatus: string | null
|
||||
customerId: number | string | null
|
||||
customerName: string | null
|
||||
channelName: string | null
|
||||
receivingWarehouseId: number | string | null
|
||||
receivingWarehouseName: string | null
|
||||
destinationWarehouseId: number | string | null
|
||||
destinationWarehouseName: string | null
|
||||
inboundId: number | string | null
|
||||
inboundVersion: number | null
|
||||
inboundCode: string | null
|
||||
expectedShipDate: string | null
|
||||
totalPackageCount: number | null
|
||||
measurement: UnifiedOrderMeasurementSummary | null
|
||||
preload: UnifiedOrderPreloadSummary | null
|
||||
loading: UnifiedOrderLoadingSummary | null
|
||||
delivery: UnifiedOrderDeliverySummary | null
|
||||
transport: UnifiedOrderTransportSummary | null
|
||||
createdAt: string | null
|
||||
updatedAt: string | null
|
||||
}
|
||||
|
||||
export interface UnifiedOrderTimelineEvent {
|
||||
source: string | null
|
||||
eventType: string | null
|
||||
mainStatus: UnifiedOrderMainStatus | null
|
||||
action: string | null
|
||||
remark: string | null
|
||||
operatorId: number | string | null
|
||||
operatorName: string | null
|
||||
occurredAt: string | null
|
||||
}
|
||||
|
||||
export interface UnifiedOrderDetailOrderSnapshot {
|
||||
id: number | string | null
|
||||
tenantId: number | string | null
|
||||
orderNo: string | null
|
||||
customerId: number | string | null
|
||||
customerName: string | null
|
||||
transportMode: string | null
|
||||
destinationCountryCode: string | null
|
||||
destinationWarehouseId: number | string | null
|
||||
destinationWarehouseName: string | null
|
||||
expectedShipDate: string | null
|
||||
receivingWarehouseId: number | string | null
|
||||
receivingWarehouseName: string | null
|
||||
channelName: string | null
|
||||
createdAt: string | null
|
||||
updatedAt: string | null
|
||||
}
|
||||
|
||||
export interface UnifiedOrderDetailOrderResource {
|
||||
order: UnifiedOrderDetailOrderSnapshot | null
|
||||
cargoes: Array<Record<string, unknown>>
|
||||
parties: Array<Record<string, unknown>>
|
||||
internalNotes: UnifiedOrderInternalNoteResource[]
|
||||
statusHistory: UnifiedOrderStatusHistoryResource[]
|
||||
}
|
||||
|
||||
export interface UnifiedOrderInternalNoteResource {
|
||||
id: number | string
|
||||
content: string
|
||||
createdBy: number | string | null
|
||||
createdAt: string | null
|
||||
}
|
||||
|
||||
export interface UnifiedOrderStatusHistoryResource {
|
||||
id: number | string
|
||||
fromStatus: string | null
|
||||
toStatus: string | null
|
||||
action: string | null
|
||||
reason: string | null
|
||||
operatorId: number | string | null
|
||||
operatorName: string | null
|
||||
occurredAt: string | null
|
||||
}
|
||||
|
||||
export interface UnifiedOrderDetailResource {
|
||||
summary: UnifiedOrderResource
|
||||
order: UnifiedOrderDetailOrderResource
|
||||
timeline: UnifiedOrderTimelineEvent[]
|
||||
}
|
||||
|
||||
export interface UnifiedOrderPageResource {
|
||||
records: UnifiedOrderResource[]
|
||||
page: number
|
||||
size: number
|
||||
total: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
export interface UnifiedOrderQuery {
|
||||
tenantId?: string
|
||||
keyword?: string
|
||||
orderNo?: string
|
||||
mainStatus?: UnifiedOrderMainStatus
|
||||
customerId?: string
|
||||
receivingWarehouseId?: string
|
||||
destinationWarehouseId?: string
|
||||
createdFrom?: string
|
||||
createdTo?: string
|
||||
page: number
|
||||
size: number
|
||||
}
|
||||
|
||||
type PagePayload = Omit<UnifiedOrderPageResource, 'page' | 'size' | 'total' | 'pages'> & {
|
||||
page: number | string
|
||||
size: number | string
|
||||
total: number | string
|
||||
pages: number | string
|
||||
}
|
||||
|
||||
function integer(value: number | string | null | undefined, fallback: number) {
|
||||
const parsed = Number(value)
|
||||
return Number.isFinite(parsed) && parsed >= 0 ? Math.trunc(parsed) : fallback
|
||||
}
|
||||
|
||||
function nullableInteger(value: number | string | null | undefined) {
|
||||
if (value === null || value === undefined || value === '') return null
|
||||
return integer(value, 0)
|
||||
}
|
||||
|
||||
function numberValue(value: number | string | null | undefined) {
|
||||
if (value === null || value === undefined || value === '') return null
|
||||
const parsed = Number(value)
|
||||
return Number.isFinite(parsed) ? parsed : null
|
||||
}
|
||||
|
||||
function id(value: number | string | null | undefined) {
|
||||
return value === null || value === undefined || value === '' ? null : String(value)
|
||||
}
|
||||
|
||||
function queryParams(query: UnifiedOrderQuery) {
|
||||
return Object.fromEntries(Object.entries(query).filter(([, value]) => value !== undefined && value !== ''))
|
||||
}
|
||||
|
||||
function normalizeRow(raw: UnifiedOrderResource): UnifiedOrderResource {
|
||||
return {
|
||||
...raw,
|
||||
orderId: String(raw.orderId),
|
||||
tenantId: String(raw.tenantId),
|
||||
customerId: id(raw.customerId),
|
||||
receivingWarehouseId: id(raw.receivingWarehouseId),
|
||||
destinationWarehouseId: id(raw.destinationWarehouseId),
|
||||
inboundId: id(raw.inboundId),
|
||||
inboundVersion: nullableInteger(raw.inboundVersion),
|
||||
totalPackageCount: nullableInteger(raw.totalPackageCount),
|
||||
measurement: raw.measurement ? {
|
||||
...raw.measurement,
|
||||
actualGrossWeightKg: numberValue(raw.measurement.actualGrossWeightKg),
|
||||
actualVolumeCbm: numberValue(raw.measurement.actualVolumeCbm),
|
||||
chargeableWeightKg: numberValue(raw.measurement.chargeableWeightKg),
|
||||
} : null,
|
||||
preload: raw.preload ? { ...raw.preload, version: nullableInteger(raw.preload.version), itemCount: nullableInteger(raw.preload.itemCount) } : null,
|
||||
loading: raw.loading ? {
|
||||
...raw.loading,
|
||||
loadingVersion: nullableInteger(raw.loading.loadingVersion),
|
||||
pickingVersion: nullableInteger(raw.loading.pickingVersion),
|
||||
expectedCount: nullableInteger(raw.loading.expectedCount),
|
||||
pickedCount: nullableInteger(raw.loading.pickedCount),
|
||||
evidenceCount: nullableInteger(raw.loading.evidenceCount),
|
||||
} : null,
|
||||
transport: raw.transport ? { ...raw.transport } : null,
|
||||
}
|
||||
}
|
||||
|
||||
export async function listPlatformUnifiedOrders(query: UnifiedOrderQuery) {
|
||||
const response = await http.get<ApiEnvelope<PagePayload>, ApiEnvelope<PagePayload>>(unifiedOrdersApiPath, {
|
||||
params: queryParams(query),
|
||||
})
|
||||
const result = unwrapApiResponse(response)
|
||||
return {
|
||||
...result,
|
||||
records: (result.records || []).map(normalizeRow),
|
||||
page: integer(result.page, query.page),
|
||||
size: integer(result.size, query.size),
|
||||
total: integer(result.total, 0),
|
||||
pages: integer(result.pages, 0),
|
||||
} satisfies UnifiedOrderPageResource
|
||||
}
|
||||
|
||||
export async function getPlatformUnifiedOrder(tenantId: string, orderId: string) {
|
||||
const response = await http.get<ApiEnvelope<UnifiedOrderDetailResource>, ApiEnvelope<UnifiedOrderDetailResource>>(
|
||||
`${unifiedOrdersApiPath}/${orderId}`,
|
||||
{ params: { tenantId } },
|
||||
)
|
||||
const result = unwrapApiResponse(response)
|
||||
return { ...result, summary: normalizeRow(result.summary) }
|
||||
}
|
||||
|
||||
export function unifiedOrderStatusLabel(status: UnifiedOrderMainStatus) {
|
||||
return unifiedOrderStatusOptions.find((item) => item.value === status)?.label || status
|
||||
}
|
||||
Reference in New Issue
Block a user