import { apiRequest, apiBlobRequest } from '@/api/http' import { createIdempotencyKey } from '@/utils/idempotencyKey' export type RelabelDeliveryMethod = 'TRUCK' | 'EXPRESS' | 'AMAZON_PICKUP' | 'CUSTOMER_PICKUP' export interface PageResult { records: T[]; page: number; size: number; total: number; pages: number } export interface LastlegOrderSummary { id: string; orderNo: string; inboundCode: string; orderType: string; packageCount: number; overseasWarehouseId?: string } export interface HistoricalOrderSummary { id: string | null; orderNo: string; sourceOrderNo?: string; outboundOrderNo?: string; relabelOrderNo?: string; orderType?: string; status?: string; packageCount?: number } export interface TransferSummary { id: string; transferNo: string; inboundCode: string; lastlegOrderId: string; lastlegOrderNo: string; operatingWarehouseId: string; fbaWarehouseCode?: string; packageCount: number; deliveryMethod: string; needRelabel: boolean; relabelNo?: string; relabelStatus?: string; relabelFulfillmentStatus?: string; status: string; estimatedArrivalAt: string; estimatedShipAt?: string; actualShipAt?: string; reviewedAt?: string; measuredAt?: string; outboundAt?: string; relabelCompletedAt?: string; createdAt: string } export interface RelabelSummary { relatedBusinessOrderNos?: string[]; operationTypes?: Array<'CARTON_LABEL' | 'SKU_LABEL' | 'CARTON_AND_SKU' | 'RETURN_BULK_SKU_LABEL'>; id: string; relabelNo: string; lastlegOrderId: string; lastlegOrderNo: string; customerOrderNo: string; overseasWarehouseId: string; status: string; itemCount: number; totalPackageCount: number; notifiedPackageCount: number; skuCodes: string; createdAt: string; reviewedAt?: string; relabelCompletedAt?: string; outboundAt?: string; totalFileId?: string; totalFileName?: string; totalFileUrl?: string; cartonTotalFileId?: string; cartonTotalFileName?: string; cartonTotalFileUrl?: string; skuTotalFileId?: string; skuTotalFileName?: string; skuTotalFileUrl?: string; deliveryMethod?: RelabelDeliveryMethod; printShippingLabel?: boolean; shippingLabelFileId?: string; shippingLabelFileName?: string; shippingLabelFileUrl?: string; fulfillmentStatus?: string; outboundOrderNo?: string; customerConfirmedAt?: string; reviewChannel?: 'TAIL_ORDER' | 'RELABEL_ORDER'; reviewRemark?: string } export interface RelabelItemDetail { id: string; skuCode?: string; productNameZh: string; totalPackageCount: number; notifiedPackageCount: number shipmentNo: string; trackingCode?: string; newSkuCode?: string; labelRequirement?: string; remark?: string relabelFileId?: string; relabelFileName?: string; relabelFileUrl?: string cartonMark: string; fbaCartonMark?: string; destinationFbaWarehouseCode?: string operationType: 'CARTON_LABEL' | 'SKU_LABEL' | 'CARTON_AND_SKU' skuItems: Array<{ skuCode: string; newSkuCode: string; quantity: number; remark?: string }> sourceInventoryBalanceId: string; sourceInventoryPositionId: string; sourceInboundOrderId: string sourceInboundOrderNo?: string; sourceInboundCode?: string; sourceWarehouseId: string; sourceWarehouseCode?: string; sourceWarehouseName?: string } export interface RelabelDetail { application: RelabelSummary & { labelDeadline?: string; contactName?: string; contactPhone?: string; chargeAmount?: number; chargeCurrency?: string; walletStatus?: string; walletErrorMessage?: string } remark?: string; items: RelabelItemDetail[] history: Array<{ fromStatus?: string; toStatus?: string; operatorId?: string; remark?: string; createdAt: string }> tailOrders?: Array<{ id: string; orderNo: string; overseasWarehouseId: string; packageCount: number; status: string }> completionEvidence?: { taskId: string; photoFileIds: string[]; completionRemark?: string; reviewerName?: string; reviewRemark?: string; completedAt?: string; customerConfirmedAt?: string } } export interface DiscardSummary { id: string; discardNo: string; outboundNo?: string; inboundCodes: string; overseasWarehouseId: string; overseasWarehouseName: string; totalPackages: number; totalFee: number; currency: string; status: string; createdAt: string; completedAt?: string; customerConfirmedAt?: string; skuCodes: string[]; skuCount: number; requestedPackages: number; actualPackages: number | null } export interface DiscardBox { cartonId: string; cartonMark: string; palletNo?: string; skus: Array<{ skuCode: string; productName: string; availablePackages: number; availablePieces?: number | null }> } export interface DiscardCandidate { cartons?: DiscardBox[]; cartonSelectionSupported?: boolean; cartonSelectionBlockReason?: string; orderId: string; sourceType?: 'DROPSHIP' | 'RETURN_CARTON' | 'INVENTORY'; inventoryPositionId?: string; palletNo?: string; returnCartonId?: string; cartonMark?: string; inboundCode: string; inboundOrderNo: string; overseasWarehouseId: string; overseasWarehouseName: string; availablePackages: number; skus: Array<{ skuCode: string; productName: string; availablePackages: number }> } export interface RelabelInventoryCandidate { cartonSkuItems: Array<{ skuCode: string; quantity: number }>; cartonAvailablePackages: number | string skuConversions?: import('./returnInventory').ReturnSkuConversion[] stockQuantity?: number; inventoryStatus?: string; occupiedRelabelNo?: string; mergedIntoCartonMarks?: string[]; hasOutbound?: boolean inventoryBalanceId: string; inboundOrderId: string; inboundOrderNo?: string; inboundCode?: string overseasWarehouseId: string; overseasWarehouseCode?: string; overseasWarehouseName?: string destinationFbaWarehouseCode?: string; destinationFbaWarehouseName?: string; skuCode?: string; productNameZh?: string availablePackages: number | string; version: number positions: Array<{ positionId: string; palletNo?: string; batchNo?: string; warehouseLocationCode?: string; availablePackages: number }> sourceType?: 'INBOUND' | 'RETURN_CARTON' | 'RETURN_PARCEL'; returnCartonId?: string; cartonMark?: string skuItems?: Array<{ skuCode: string; newSkuCode?: string; quantity: number; remark?: string }>; relabelledPackages?: number; relabelledQuantity?: number; relabelReservedQuantity?: number; relabelStatus?: string } export interface TransferCreate { inboundCode?: string; sourceLastlegOrderIds?: string[]; operatingWarehouseId: string; fbaWarehouseCode?: string; destinationType?: 'NONE' | 'FBA' | 'PRIVATE' destinationWarehouseId?: string; destinationWarehouseCode?: string; destinationWarehouseName?: string privateDestinationAddress?: string; privateDestinationPostalCode?: string; privateDestinationContactName?: string; privateDestinationContactPhone?: string deliveryAddress?: string; packageCount: number; volumeCbm: number; deliveryMethod?: string; estimatedArrivalAt?: string; estimatedShipAt?: string fbaShipmentNo?: string; trackingNo?: string; fbaBoxNos?: string; remark?: string; needRelabel: boolean; idempotencyKey: string labelDeadline?: string; pickupContactName?: string; pickupContactPhone?: string; relabelContactName?: string; relabelContactPhone?: string; relabelChargeAmount?: number; relabelDeliveryMethod?: RelabelDeliveryMethod items: Array<{ skuCode?: string; productNameZh?: string; quantity: number; packageCount: number; shippingMark?: string; cartonMarks?: string[] }> relabelItems?: Array<{ operationType: 'CARTON_LABEL' | 'SKU_LABEL' | 'CARTON_AND_SKU'; customerCartonMarks: string[]; originalSku?: string; newSku?: string; quantity: number; fbaCartonMark?: string; destinationWarehouseCode?: string; remark?: string }> } export interface RelabelCreate { customerOrderNo: string; operationWarehouseId: string; labelDeadline: string; contactName: string; contactPhone: string; chargeAmount?: number remark?: string; idempotencyKey: string; deliveryMethod?: RelabelDeliveryMethod; printShippingLabel?: boolean items: Array<{ sourceInventoryBalanceId?: string; sourceInventoryPositionId?: string; sourceInboundOrderId?: string; returnCartonId?: string; skuCode?: string; productNameZh: string; totalPackageCount: number; notifiedPackageCount: number; shipmentNo: string; trackingCode?: string; newSkuCode?: string; labelRequirement?: string cartonMark: string; fbaCartonMark?: string; destinationFbaWarehouseCode?: string; operationType: 'CARTON_LABEL' | 'SKU_LABEL' | 'CARTON_AND_SKU'; skuItems?: Array<{ skuCode: string; newSkuCode: string; quantity: number; remark?: string }>; remark?: string }> } export function listRelabelHistoricalOrders(operationWarehouseId: string, keyword?: string) { return apiRequest({ method: 'GET', url: '/merchant/v1/last-leg/relabel-applications/historical-orders', params: { operationWarehouseId, keyword: keyword || undefined } }) } export function listTransfers(params: Record) { return apiRequest>({ method: 'GET', url: '/merchant/v1/last-leg/warehouse-transfers', params }) } export function getTransfer(transferNo: string) { return apiRequest>({ method: 'GET', url: `/merchant/v1/last-leg/warehouse-transfers/${encodeURIComponent(transferNo)}` }) } export function createTransfer(payload: TransferCreate) { return apiRequest>({ method: 'POST', url: '/merchant/v1/last-leg/warehouse-transfers', data: payload }) } export function updateTransfer(transferNo: string, payload: TransferCreate) { return apiRequest>({ method: 'POST', url: `/merchant/v1/last-leg/warehouse-transfers/${encodeURIComponent(transferNo)}/save-draft`, data: payload }) } export function submitTransfer(transferNo: string) { return apiRequest>({ method: 'POST', url: `/merchant/v1/last-leg/warehouse-transfers/${encodeURIComponent(transferNo)}/submit` }) } /** 补充既有SKU的预报箱数,原箱数快照用于并发核对与安全重试。 */ export function increaseTransferForecastCartons(transferNo: string, payload: { expectedPackageCount: number; packageCount: number }) { return apiRequest>({ method: 'POST', url: `/merchant/v1/last-leg/warehouse-transfers/${encodeURIComponent(transferNo)}/increase-forecast-cartons`, data: payload }) } export function listRelabels(params: Record) { return apiRequest>({ method: 'GET', url: '/merchant/v1/last-leg/relabel-applications', params }) } export function getRelabel(relabelNo: string) { return apiRequest({ method: 'GET', url: `/merchant/v1/last-leg/relabel-applications/${encodeURIComponent(relabelNo)}` }) } export function confirmTransferMeasurement(transferNo: string, remark?: string) { return apiRequest>({ method: 'POST', url: `/merchant/v1/last-leg/warehouse-transfers/${encodeURIComponent(transferNo)}/confirm-measurement`, data: { idempotencyKey: createIdempotencyKey(), remark } }) } export interface TransferCartonLabel { transferNo: string; cartons: Array<{ cartonMark: string; customerCartonMark?: string; cartonIndex: number; totalCartons: number; status: string }>; printableHtml: string } export function getTransferCartonLabel(transferNo: string) { return apiRequest({ method: 'GET', url: `/merchant/v1/last-leg/warehouse-transfers/${encodeURIComponent(transferNo)}/carton-label` }) } export function downloadTransferMeasurementPhoto(transferNo: string, fileId: string | number) { return apiBlobRequest({ method: 'GET', url: `/merchant/v1/last-leg/warehouse-transfers/${encodeURIComponent(transferNo)}/measurement-photos/files/${encodeURIComponent(fileId)}/download` }) } export function listRelabelInventoryCandidates(operationWarehouseId: string, inboundCode?: string) { return apiRequest({ method: 'GET', url: '/merchant/v1/last-leg/relabel-applications/inventory-candidates', params: { operationWarehouseId, inboundCode: inboundCode || undefined }, }) } export function createRelabel(payload: RelabelCreate) { return apiRequest({ method: 'POST', url: '/merchant/v1/last-leg/relabel-applications', data: payload }) } export function resubmitRelabel(relabelNo: string, payload: { labelDeadline: string; contactName: string; contactPhone: string; remark?: string; idempotencyKey: string }) { return apiRequest({ method: 'PUT', url: `/merchant/v1/last-leg/relabel-applications/${encodeURIComponent(relabelNo)}/resubmit`, data: payload }) } export function uploadRelabelShippingLabel(relabelNo: string, file: File) { const data = new FormData(); data.append('file', file) return apiRequest({ method: 'POST', url: `/merchant/v1/last-leg/relabel-applications/${encodeURIComponent(relabelNo)}/shipping-label`, data, headers: { 'Content-Type': 'multipart/form-data' } }) } export function uploadRelabelFile(relabelNo: string, scope: 'CARTON_TOTAL' | 'SKU_TOTAL' | 'ITEM', file: File, itemId?: string) { const data = new FormData(); data.append('file', file) return apiRequest({ method: 'POST', url: `/merchant/v1/last-leg/relabel-applications/${encodeURIComponent(relabelNo)}/files`, params: { scope, itemId }, data, headers: { 'Content-Type': 'multipart/form-data' } }) } export function confirmRelabelCompletion(relabelNo: string, remark?: string) { return apiRequest({ method: 'POST', url: `/merchant/v1/last-leg/relabel-applications/${encodeURIComponent(relabelNo)}/confirm-completion`, data: { idempotencyKey: createIdempotencyKey(), remark } }) } export function downloadRelabelCompletionPhoto(relabelNo: string, fileId: string) { return apiBlobRequest({ method: 'GET', url: `/merchant/v1/last-leg/relabel-applications/${encodeURIComponent(relabelNo)}/completion-photos/${encodeURIComponent(fileId)}/download` }) } export function listDiscards(params: Record) { return apiRequest>({ method: 'GET', url: '/merchant/v1/last-leg/cargo-discards', params }) } export function getDiscard(discardNo: string) { return apiRequest>({ method: 'GET', url: `/merchant/v1/last-leg/cargo-discards/${encodeURIComponent(discardNo)}` }) } export function listDiscardCandidates(inboundCode?: string) { return apiRequest({ method: 'GET', url: '/merchant/v1/last-leg/cargo-discards/inventory-candidates', params: { inboundCode: inboundCode || undefined } }) } export function createDiscard(payload: { items: Array<{ orderId?: string; returnCartonId?: string; inventoryPositionId?: string; cartonIds?: string[]; skuCode: string; discardPackages: number }>; idempotencyKey: string; remark?: string }) { return apiRequest>({ method: 'POST', url: '/merchant/v1/last-leg/cargo-discards', data: payload }) } export function cancelDiscard(discardNo: string) { return apiRequest>({ method: 'POST', url: `/merchant/v1/last-leg/cargo-discards/${encodeURIComponent(discardNo)}/cancel` }) } export function confirmDiscardCompletion(discardNo: string) { return apiRequest>({ method: 'POST', url: `/merchant/v1/last-leg/cargo-discards/${encodeURIComponent(discardNo)}/confirm-completion` }) } export interface DiscardPhoto { fileId: string; palletNo: string; fileName: string; createdAt: string; submittedAt: string } export function downloadDiscardPhoto(discardNo: string, fileId: string) { return apiBlobRequest({ method: 'GET', url: `/merchant/v1/last-leg/cargo-discards/${encodeURIComponent(discardNo)}/photos/files/${encodeURIComponent(fileId)}/download` }) } export interface TransferSourceCarton { cartonMark: string; productNameZh?: string; quantity?: number; destinationWarehouseCode?: string; destinationWarehouseName?: string; items: Array<{skuCode?: string; quantity?: number}> } export function getTransferSourceCartons(operatingWarehouseId: string, inboundCode: string) { return apiRequest({method:'GET',url:'/merchant/v1/last-leg/warehouse-transfers/inventory-source-cartons',params:{operatingWarehouseId,inboundCode}}) } /** 通过客户授权入口读取申请绑定的资料,不使用平台文件地址。 */ export function downloadRelabelDocument(relabelNo: string, fileId: string, scope: string, itemId?: string) { const base = '/merchant/v1/last-leg/relabel-applications/' + encodeURIComponent(relabelNo) return apiBlobRequest({ method: 'GET', url: scope === 'SHIPPING' ? base + '/shipping-label/' + encodeURIComponent(fileId) + '/download' : base + '/files/' + encodeURIComponent(fileId) + '/download', params: scope === 'SHIPPING' ? undefined : { scope, itemId } }) }