Files
admin-web/src/modules/unified-orders/api/unifiedOrders.ts
T
2026-08-25 22:08:59 +08:00

448 lines
14 KiB
TypeScript

import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
export const unifiedOrdersApiPath = '/platform/v1/orders'
export type UnifiedOrderMainStatus =
| 'PENDING_REVIEW'
| 'WAIT_MEASURE'
| 'WAIT_SPLIT'
| 'LOADING'
| 'LOADED'
| 'DELIVERED'
export const unifiedOrderStatusOptions: Array<{ value: UnifiedOrderMainStatus; label: string }> = [
{ value: 'PENDING_REVIEW', label: '待审核' },
{ value: 'WAIT_MEASURE', 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
estimatedSailingAt: string | null
actualSailingAt: string | null
effectiveSailingAt: string | null
effectiveSailingAtEstimated: boolean | null
estimatedPortArrivalAt: string | null
actualPortArrivalAt: string | null
effectivePortArrivalAt: string | null
effectivePortArrivalAtEstimated: boolean | null
}
export interface UnifiedOrderRelationSummary {
role: 'PARENT' | 'CHILD'
rootOrderId: number | string | null
parentOrderId: number | string | null
splitSequence: number | null
childCount: number
}
export interface UnifiedOrderResource {
orderId: number | string
tenantId: number | string
orderNo: string | null
mainStatus: UnifiedOrderMainStatus
nextAction: string | null
relation: UnifiedOrderRelationSummary | 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
locationCodes: string | null
actualGrossWeightKg: number | null
actualVolumeCbm: number | null
measurement: UnifiedOrderMeasurementSummary | null
preload: UnifiedOrderPreloadSummary | null
loading: UnifiedOrderLoadingSummary | null
delivery: UnifiedOrderDeliverySummary | null
transport: UnifiedOrderTransportSummary | null
createdAt: string | null
updatedAt: string | null
expectedArrivalAt: string | null
feeReviewStatus: string | null
holdCargo?: boolean | null
holdReason?: string | null
companyName?: string | null
businessType?: string | null
pickupMethod?: string | null
requestedPickupAt: string | null
pickupAt?: string | null
pickupContactPhone?: string | null
pickupAddress?: string | null
pickupPostalCode?: string | null
pickupContactEmail?: string | null
taxMode?: string | null
fullContainer?: boolean | null
customsInspection?: boolean | null
totalPalletCount: number | null
chargeableVolumeCbm: number | null
volumetricWeightKg: number | null
totalNetWeightKg: number | null
location?: string | null
destinationType?: string | null
destinationAddress?: string | null
destinationPostalCode?: string | null
destinationContact?: string | null
destinationContactPhone?: string | null
measurementCompletedAt: string | null
inboundCompletedAt: string | null
associatedFiles?: string[] | null
markFiles?: string[] | null
pcOrderFiles?: string[] | null
expressCompany: string | null
expressTrackingNo: string | null
skuNos: string[] | null
totalQuantity: number | null
shipmentId: string | null
referenceId: string | null
remark?: string | null
customerRemark?: string | null
latestInternalNote?: string | null
internalRemark?: string | null
createdBy?: 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
tradeTerm?: string | null
destinationCountryCode: string | null
destinationWarehouseId: number | string | null
destinationWarehouseName: string | null
expectedShipDate: string | null
customerRemark?: string | null
pickupMethod?: string | null
receivingWarehouseId: number | string | null
receivingWarehouseName: string | null
receivingWarehouseAddress?: string | null
receivingTime?: string | null
channelCode?: string | null
channelName: string | null
carrierId: number | string | null
carrierName: string | null
taxMode?: string | null
containerMode?: string | null
customsInspection?: boolean | null
destinationType?: string | null
amazonWarehouseCode?: string | null
shipmentId?: string | null
referenceId?: string | null
expressCompany?: string | null
expressTrackingNo?: string | null
submittedAt?: string | null
completedAt?: 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 UnifiedOrderFeeDetailResource {
id: number | string
sourceFeeId?: number | string | null
sourceFeeVersion?: number | null
inboundId: number | string
inboundCode: string | null
reviewStatus: string | null
direction: string | null
customerName: string | null
invoiceTitle: string | null
businessDate: string | null
channelName: string | null
destinationName: string | null
fbaNo?: string | null
packageCount: number | null
actualGrossWeightKg: number | null
actualVolumeCbm: number | null
feeTypeNameSnapshot: string | null
currency: string | null
amount: number | null
chargeableQuantity: number | null
chargeUnit: string | null
unitPrice: number | null
inboundedAt: string | null
outboundAt?: string | null
storageDays?: number | null
billNo?: string | null
settlementStartDate?: string | null
settlementEndDate?: string | null
}
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
inboundCode?: string
internalOrderStatus?: string
expectedArrivalDate?: string
feeReviewStatus?: string
channelName?: string
containerNo?: string
cabinetNo?: string
sailingDate?: string
loaded?: boolean
fullContainer?: boolean
measurementReviewStatus?: string
fbaCode?: 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),
relation: raw.relation ? {
...raw.relation,
rootOrderId: id(raw.relation.rootOrderId),
parentOrderId: id(raw.relation.parentOrderId),
splitSequence: nullableInteger(raw.relation.splitSequence),
childCount: integer(raw.relation.childCount, 0),
} : null,
totalPackageCount: nullableInteger(raw.totalPackageCount),
actualGrossWeightKg: numberValue(raw.actualGrossWeightKg),
actualVolumeCbm: numberValue(raw.actualVolumeCbm),
totalPalletCount: nullableInteger(raw.totalPalletCount),
chargeableVolumeCbm: numberValue(raw.chargeableVolumeCbm),
volumetricWeightKg: numberValue(raw.volumetricWeightKg),
totalNetWeightKg: numberValue(raw.totalNetWeightKg),
totalQuantity: nullableInteger(raw.totalQuantity),
skuNos: raw.skuNos?.filter((value) => value.trim()) ?? null,
locationCodes: raw.locationCodes ?? raw.location ?? null,
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 downloadPlatformProductImage(tenantId: string, fileId: string) {
return http.get<Blob, Blob>(
`/platform/v1/files/product-images/${encodeURIComponent(tenantId)}/${encodeURIComponent(fileId)}`,
{ responseType: 'blob' },
)
}
export function approvePlatformUnifiedOrder(tenantId: string, orderId: string) {
return http.post<ApiEnvelope<void>, ApiEnvelope<void>>(
`/platform/v1/orders/first-leg/${orderId}/approve`,
undefined,
{ params: { tenantId } },
).then(unwrapApiResponse)
}
export interface UnifiedOrderBatchApprovalResult {
total: number
successCount: number
failureCount: number
successOrderIds: Array<number | string>
failedOrders: Array<{ orderId: number | string; code: string; message: string }>
}
export function batchApprovePlatformUnifiedOrders(tenantId: string, orderIds: string[]) {
return http.post<ApiEnvelope<UnifiedOrderBatchApprovalResult>, ApiEnvelope<UnifiedOrderBatchApprovalResult>>(
'/platform/v1/orders/first-leg/batch-approve',
{ orderIds },
{ params: { tenantId } },
).then(unwrapApiResponse)
}
export function addPlatformUnifiedOrderNote(tenantId: string, orderId: string, content: string) {
return http.post<ApiEnvelope<number | string>, ApiEnvelope<number | string>>(
`${unifiedOrdersApiPath}/${orderId}/internal-notes`,
{ content },
{ params: { tenantId } },
).then(unwrapApiResponse)
}
export async function listPlatformUnifiedOrderFeeDetails(accountId: string, inboundId: string) {
const response = await http.get<ApiEnvelope<UnifiedOrderFeeDetailResource[]>, ApiEnvelope<UnifiedOrderFeeDetailResource[]>>(
`/platform/v1/inbounds/${encodeURIComponent(inboundId)}/fee-details`,
{ params: { accountId } },
)
return unwrapApiResponse(response)
}
export function unifiedOrderStatusLabel(status: UnifiedOrderMainStatus) {
return unifiedOrderStatusOptions.find((item) => item.value === status)?.label || '未知状态'
}