import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http' export const unifiedOrdersApiPath = '/platform/v1/orders' export type UnifiedOrderMainStatus = | 'PENDING_REVIEW' | 'WAIT_MEASURE' | 'WAIT_PALLETIZE' | 'WAIT_SPLIT' | 'LOADING' | 'LOADED' | 'DELIVERED' | 'COMPLETED' | 'WAIT_WEIGHT_CONFIRM' | 'WEIGHT_CONFIRMED' | 'WAIT_CUSTOMS_DOCS' | 'EXPORT_CUSTOMS' | 'SAILING_CLEARANCE' | 'WAIT_UNLOAD' | 'WAIT_DISPATCH' | 'OUTBOUND_DOCUMENT' | 'DELIVERING' | 'SIGNED' export const unifiedOrderStatusOptions: Array<{ value: UnifiedOrderMainStatus; label: string }> = [ { value: 'PENDING_REVIEW', label: '待审核' }, { value: 'WAIT_MEASURE', label: '待测量数据' }, { value: 'WAIT_WEIGHT_CONFIRM', label: '待客户确认' }, { value: 'WEIGHT_CONFIRMED', label: '已确认数据' }, { value: 'WAIT_CUSTOMS_DOCS', label: '待上传资料' }, { value: 'WAIT_PALLETIZE', label: '一件代发打托' }, { value: 'WAIT_SPLIT', label: '待分柜' }, { value: 'LOADING', label: '装柜中' }, { value: 'LOADED', label: '装柜完成' }, { value: 'EXPORT_CUSTOMS', label: '出口报关' }, { value: 'SAILING_CLEARANCE', label: '已开船/待清关' }, { value: 'WAIT_UNLOAD', label: '待拆柜' }, { value: 'WAIT_DISPATCH', label: '待排车' }, { value: 'OUTBOUND_DOCUMENT', label: '出库单' }, { value: 'DELIVERING', label: '派送中' }, { value: 'SIGNED', 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 UnifiedOrderCoLoadGroupSummary { status: string memberCount: number memberInboundCodes: string[] } export interface UnifiedOrderResource { orderId: number | string orderVersion?: number | null tenantId: number | string orderNo: string | null mainStatus: UnifiedOrderMainStatus nextAction: string | null relation: UnifiedOrderRelationSummary | null internalOrderStatus: string | null customerId: number | string | null customerName: string | null channelCode: string | null channelName: string | null channelChanged?: boolean 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 estimatedVolumeCbm?: number | null expectedVolumeCbm?: 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 coLoadGroup: UnifiedOrderCoLoadGroupSummary | null createdAt: string | null updatedAt: string | null expectedArrivalAt: string | null feeReviewStatus: string | null holdFlag: boolean | null currentHoldReason: string | null companyName?: string | null businessType?: string | null fulfillmentType?: 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 warehouseRemark?: string | null customerServiceRemark?: 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 version: number | 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> parties: Array> 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 } export type UnifiedOrderStatusCountQuery = Omit export interface UnifiedOrderStatusCounts { counts: Partial> } export async function getPlatformUnifiedOrderStatusCounts(query: UnifiedOrderStatusCountQuery): Promise { // Strip these fields at runtime too, so callers cannot accidentally scope the other tabs. const params = Object.fromEntries(Object.entries(query).filter(([key, value]) => !['page', 'size', 'mainStatus'].includes(key) && value !== undefined && value !== '')) const response = await http.get, ApiEnvelope>( `${unifiedOrdersApiPath}/status-counts`, { params }, ) const result = unwrapApiResponse(response) return { counts: Object.fromEntries(Object.entries(result.counts || {}).filter(([, count]) => typeof count === 'number' && Number.isSafeInteger(count) && count >= 0)) } } type PagePayload = Omit & { 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) } export interface UnifiedOrderDeclarationCargo { itemNo: number cargoName: string cargoNameEn?: string | null skuNo?: string | null model?: string | null packageCount: number totalQuantity: number lengthCm: number widthCm: number heightCm: number grossWeightKg: number volumeCbm: number netWeightKg?: number | null hsCode?: string | null customsHsCode?: string | null unitsPerPackage?: number | null unitDeclaredValue?: number | null declaredValue?: number | null currency?: string | null material?: string | null usage?: string | null brand?: string | null cargoRemark?: string | null } export interface UpdateUnifiedOrderDeclarationRequest { expectedVersion: number customerRemark?: string | null expectedArrivalAt?: string | null estimatedVolumeCbm?: number | null channelCode: string channelName: string cargoes: UnifiedOrderDeclarationCargo[] } export interface UnifiedOrderDeclarationChange { itemNo: number | null field: string beforeValue: string | null afterValue: string | null } export interface UpdateUnifiedOrderDeclarationResult { orderNo: string | null inboundCode: string | null previousVersion: number version: number channelChangedBefore: boolean channelChanged: boolean changes: UnifiedOrderDeclarationChange[] } export interface UpdateUnifiedOrderPlanningRequest { expectedVersion: number expectedArrivalAt?: string | null estimatedVolumeCbm?: number | null } function optionalText(value: string | null | undefined) { const normalized = value?.trim() return normalized || null } 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), orderVersion: nullableInteger(raw.orderVersion), tenantId: String(raw.tenantId), customerId: id(raw.customerId), channelCode: optionalText(raw.channelCode), receivingWarehouseId: id(raw.receivingWarehouseId), destinationWarehouseId: id(raw.destinationWarehouseId), inboundId: id(raw.inboundId), holdFlag: raw.holdFlag === null || raw.holdFlag === undefined ? null : Boolean(raw.holdFlag), currentHoldReason: optionalText(raw.currentHoldReason), 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), estimatedVolumeCbm: numberValue(raw.estimatedVolumeCbm), expectedVolumeCbm: numberValue(raw.expectedVolumeCbm), channelChanged: Boolean(raw.channelChanged), 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, coLoadGroup: raw.coLoadGroup ? { status: String(raw.coLoadGroup.status || ''), memberCount: integer(raw.coLoadGroup.memberCount, 0), memberInboundCodes: (raw.coLoadGroup.memberInboundCodes || []) .map((value) => typeof value === 'string' ? value.trim() : '') .filter(Boolean), } : null, } } export async function listPlatformUnifiedOrders(query: UnifiedOrderQuery) { const response = await http.get, ApiEnvelope>(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>( `${unifiedOrdersApiPath}/${orderId}`, { params: { tenantId } }, ) const result = unwrapApiResponse(response) return { ...result, summary: normalizeRow(result.summary), order: { ...result.order, order: result.order.order ? { ...result.order.order, version: nullableInteger(result.order.order.version) } : null, }, } } export function updatePlatformUnifiedOrderDeclaration( tenantId: string, orderId: string, request: UpdateUnifiedOrderDeclarationRequest, ) { return http.put, ApiEnvelope>( `${unifiedOrdersApiPath}/${orderId}/declaration`, request, { params: { tenantId } }, ).then(unwrapApiResponse) } export function updatePlatformUnifiedOrderPlanning( tenantId: string, orderId: string, request: UpdateUnifiedOrderPlanningRequest, ) { return http.put, ApiEnvelope>( `${unifiedOrdersApiPath}/${orderId}/planning`, request, { params: { tenantId } }, ).then(unwrapApiResponse) } export function downloadPlatformProductImage(tenantId: string, fileId: string) { return http.get( `/platform/v1/files/product-images/${encodeURIComponent(tenantId)}/${encodeURIComponent(fileId)}`, { responseType: 'blob' }, ) } export function approvePlatformUnifiedOrder(tenantId: string, orderId: string) { return http.post, ApiEnvelope>( `/platform/v1/orders/first-leg/${orderId}/approve`, undefined, { params: { tenantId } }, ).then(unwrapApiResponse) } export interface UnifiedOrderBatchApprovalResult { total: number successCount: number failureCount: number successOrderIds: Array failedOrders: Array<{ orderId: number | string; code: string; message: string }> } export interface UnifiedOrderBatchReviewItem { orderId: string orderVersion: number } export interface UnifiedOrderBatchApprovalPreviewItem extends UnifiedOrderBatchReviewItem { inboundCode: string | null customerId: string | null customerName: string | null chineseGoodsNames: string[] channelCode: string | null channelName: string | null destinationWarehouseCode: string | null destinationWarehouseName: string | null canApprove: boolean blockReason: string | null } export interface UnifiedOrderBatchApprovalPreview { total: number approvableCount: number items: UnifiedOrderBatchApprovalPreviewItem[] } function normalizeBatchApprovalPreview(result: UnifiedOrderBatchApprovalPreview): UnifiedOrderBatchApprovalPreview { return { ...result, total: integer(result.total, 0), approvableCount: integer(result.approvableCount, 0), items: (result.items || []).map((item) => ({ ...item, orderId: String(item.orderId), orderVersion: integer(item.orderVersion, 0), customerId: id(item.customerId), chineseGoodsNames: (item.chineseGoodsNames || []).map((name) => name.trim()).filter(Boolean), canApprove: Boolean(item.canApprove), })), } } export async function getPlatformUnifiedOrderBatchApprovalPreview(tenantId: string, items: UnifiedOrderBatchReviewItem[]) { const response = await http.post, ApiEnvelope>( '/platform/v1/orders/first-leg/batch-review-preview', { items }, { params: { tenantId } }, ) return normalizeBatchApprovalPreview(unwrapApiResponse(response)) } export function batchApprovePlatformUnifiedOrders(tenantId: string, items: UnifiedOrderBatchReviewItem[]) { return http.post, ApiEnvelope>( '/platform/v1/orders/first-leg/batch-approve', { items }, { params: { tenantId } }, ).then(unwrapApiResponse) } export interface UnifiedOrderRemarksUpdateRequest { expectedVersion: number internalRemark: string | null warehouseRemark: string | null customerServiceRemark: string | null } export interface LegacyOrderImportItem { excelRow: number success: boolean legacyInboundCode: string | null orderId: number | string | null orderNo: string | null errorMessage: string | null } export interface LegacyOrderImportResult { total: number succeeded: number failed: number items: LegacyOrderImportItem[] } export function importPlatformLegacyOrders(file: File, idempotencyKey: string) { const form = new FormData() form.append('file', file) return http.post, ApiEnvelope>( '/platform/v1/orders/first-leg/legacy-import', form, { headers: { 'Content-Type': 'multipart/form-data', 'Idempotency-Key': idempotencyKey } }, ).then(unwrapApiResponse) } export function createLegacyOrderImportIdempotencyKey() { const uuid = globalThis.crypto?.randomUUID?.() return uuid ? `legacy-order-import-${uuid}` : `legacy-order-import-${Date.now()}-${Math.random().toString(36).slice(2)}` } export interface UnifiedOrderRemarksUpdateResponse { version: number customerRemark: string | null internalRemark: string | null warehouseRemark: string | null customerServiceRemark: string | null } export function updatePlatformUnifiedOrderRemarks(tenantId: string, orderId: string, request: UnifiedOrderRemarksUpdateRequest) { return http.put, ApiEnvelope>( `${unifiedOrdersApiPath}/${orderId}/remarks`, request, { params: { tenantId } }, ).then(unwrapApiResponse) } export async function listPlatformUnifiedOrderFeeDetails(accountId: string, inboundId: string) { const response = await http.get, ApiEnvelope>( `/platform/v1/inbounds/${encodeURIComponent(inboundId)}/fee-details`, { params: { accountId } }, ) return unwrapApiResponse(response) } export function unifiedOrderStatusLabel(status: UnifiedOrderMainStatus) { if (status === 'DELIVERED') return '签收完成' if (status === 'COMPLETED') return '已完成' return unifiedOrderStatusOptions.find((item) => item.value === status)?.label || '未知状态' }