import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http' import { normalizeOverseasPage, type OverseasPagePayload } from './lastMileOrders' import { normalizeWarehouseTransferQuantity } from './warehouseTransferQuantities' import type { ReceivingOverseasWarehouseDto } from './pallets' export interface WarehouseTransferQuery { page: number size: number transferNo?: string operatingWarehouseId?: string fbaWarehouseCode?: string status?: string needRelabel?: boolean } export interface WarehouseTransferSummaryDto { id: string transferNo: string inboundCode?: string | null lastlegOrderId?: string | null lastlegOrderNo?: string | null operatingWarehouseId?: string | null fbaWarehouseCode?: string | null packageCount: number deliveryMethod?: string | null needRelabel: boolean relabelNo?: string | null status: string estimatedArrivalAt?: string | null createdAt?: string | null } export interface WarehouseTransferItemDto { id: string skuCode: string productNameZh: string quantity?: number | string | null packageCount: number shippingMark?: string | null } export interface WarehouseTransferCartonDto { items?: Array<{ transferItemId: string; skuCode: string; quantity: number | string }> transferItemId?: string | null transferPalletId?: string | null length?: number | string | null width?: number | string | null height?: number | string | null weight?: number | string | null quantity?: number | string | null countedAt?: string | null discrepancyReason?: string | null cartonMark?: string | null customerCartonMark?: string | null cartonIndex: number totalCartons: number status: string } export interface WarehouseTransferCartonLabelDto { transferNo: string cartons: WarehouseTransferCartonDto[] printableHtml: string } export async function getWarehouseTransferCartonLabel(customerId: string, transferNo: string) { const response = await http.get, ApiEnvelope>( `/platform/v1/last-leg/warehouse-transfers/${encodeURIComponent(transferNo)}/carton-label`, { params: { customerId } }, ) return unwrapApiResponse(response) } export interface WarehouseTransferRelabelBindingDto { operationType: string customerCartonMark: string originalSku: string newSku?: string | null quantity: number fbaCartonMark?: string | null destinationWarehouseCode?: string | null remark?: string | null } export interface WarehouseTransferPalletDto { id: string sequenceNo: number warehouseLocationCode?: string | null palletNo?: string | null cartonCount: number grossWeightKg: number volumeCbm: number status: string } export interface WarehouseTransferDetailDto extends WarehouseTransferSummaryDto { inboundOrderId?: string | null orderType?: string | null orderStatus?: string | null relabelApplicationId?: string | null relabelStatus?: string | null deliveryAddress?: string | null estimatedShipAt?: string | null fbaShipmentNo?: string | null trackingNo?: string | null fbaBoxNos?: string | null remark?: string | null items: WarehouseTransferItemDto[] cartons: WarehouseTransferCartonDto[] pallets: WarehouseTransferPalletDto[] relabelBindings: WarehouseTransferRelabelBindingDto[] customerConfirmedAt?: string | null actualShipAt?: string | null destinationWarehouseId?: string | null destinationWarehouseCode?: string | null destinationWarehouseName?: string | null sourceOrders?: Array<{ lastlegOrderId: string; lastlegOrderNo?: string | null; inboundOrderId: string; inboundCode?: string | null }> allocations?: Array<{ inventoryPositionId: string; inboundOrderId: string; inboundCode?: string | null; palletId: string; palletNo?: string | null; packageCount: number; status: string; destinationInventoryPositionId?: string | null; skuCode?: string | null }> receiptCommands?: WarehouseTransferReceiptCommandDto[] } export interface WarehouseTransferReceiptCommandDto { sourceInventoryPositionId: string status: 'PREPARED' | 'RESERVED' | 'COMPLETED' requestHash: string request: ReceiveWarehouseTransferPayload target: { warehouseId: string; warehouseCode: string; warehouseName: string; palletId: string; palletNo: string; locationId: string; locationCode: string; fbaId?: string | null; fbaCode: string; fbaName?: string | null } } export interface AllocateWarehouseTransferPayload { idempotencyKey: string allocations: Array<{ inventoryPositionId: string; palletId: string; packageCount: number }> } export interface OutboundWarehouseTransferPayload { idempotencyKey: string; remark?: string } export interface ReceiveWarehouseTransferPayload { sourceInventoryPositionId: string destinationOverseasWarehouseId: string destinationOverseasWarehouseCode: string destinationOverseasWarehouseName: string destinationWarehouseId?: string destinationWarehouseCode: string destinationWarehouseName?: string destinationPalletId: string destinationPalletNo: string warehouseLocationId: string warehouseLocationCode: string batchNo: string idempotencyKey: string remark?: string } export async function outboundWarehouseTransfer(customerId: string, transferNo: string, payload: OutboundWarehouseTransferPayload) { const response = await http.post, ApiEnvelope>( `/platform/v1/last-leg/warehouse-transfers/${encodeURIComponent(transferNo)}/outbound`, payload, { params: { customerId } }, ) return normalizeTransferDetail(unwrapApiResponse(response)) } export async function receiveWarehouseTransfer(customerId: string, transferNo: string, payload: ReceiveWarehouseTransferPayload) { const response = await http.post, ApiEnvelope>( `/platform/v1/last-leg/warehouse-transfers/${encodeURIComponent(transferNo)}/receipts`, payload, { params: { customerId } }, ) return normalizeTransferDetail(unwrapApiResponse(response)) } export async function listTransferReceivingWarehouses() { const records: ReceivingOverseasWarehouseDto[] = [] let total: number | undefined for (let page = 1; page <= 100; page++) { const response = await http.get>, ApiEnvelope>>( '/platform/v1/warehouse-admin/overseas-receiving-options', { params: { page, size: 200 } }, ) const value = normalizeOverseasPage(unwrapApiResponse(response)) if (total !== undefined && total !== value.total) throw new Error('收货仓列表已变化,请重新查询') total = value.total records.push(...value.records.map(w => ({ ...w, id: String(w.id), customerId: String(w.customerId) }))) if (records.length === total) { if (new Set(records.map(w => w.id)).size !== records.length || records.some(w => w.customerId !== '0' || w.warehouseType !== 'OVERSEAS' || w.status !== 'ACTIVE')) throw new Error('收货仓返回范围不符合要求') return { records } } if (!value.records.length || records.length > total) throw new Error('收货仓分页不完整,请重新查询') } throw new Error('收货仓分页超出查询限制') } type WarehouseTransferDetailWire = Omit & { packageCount: number | string } function normalizeTransferDetail(value: WarehouseTransferDetailWire): WarehouseTransferDetailDto { return { ...value, packageCount: normalizeWarehouseTransferQuantity(value.packageCount, '本票转运箱数') } } /** The caller must use confirmed source positions, never aggregate inventory rows. */ export async function allocateWarehouseTransfer(customerId: string, transferNo: string, payload: AllocateWarehouseTransferPayload) { const response = await http.post, ApiEnvelope>( `/platform/v1/last-leg/warehouse-transfers/${encodeURIComponent(transferNo)}/allocations`, payload, { params: { customerId } }, ) return normalizeTransferDetail(unwrapApiResponse(response)) } export async function listWarehouseTransfers(customerId: string, query: WarehouseTransferQuery) { const response = await http.get>, ApiEnvelope>>( '/platform/v1/last-leg/warehouse-transfers', { params: { ...query, customerId } }, ) return normalizeOverseasPage(unwrapApiResponse(response)) } export async function getWarehouseTransfer(customerId: string, transferNo: string) { const response = await http.get, ApiEnvelope>( `/platform/v1/last-leg/warehouse-transfers/${encodeURIComponent(transferNo)}`, { params: { customerId } }, ) return normalizeTransferDetail(unwrapApiResponse(response)) } export async function reviewWarehouseTransfer( customerId: string, transferNo: string, decision: 'APPROVE' | 'REJECT', remark?: string, ) { const response = await http.post, ApiEnvelope>( `/platform/v1/last-leg/warehouse-transfers/${encodeURIComponent(transferNo)}/review`, { decision, remark }, { params: { customerId } }, ) return normalizeTransferDetail(unwrapApiResponse(response)) }