import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http' export type LoadingListMode = 'preload' | 'confirmed' export interface LoadingCustomer { accountId: string customerName: string | null } export type PreloadConfirmationStatus = 'PENDING' | 'PARTIALLY_CONFIRMED' | 'CONFIRMED' export type PreloadBlockingReason = | 'LOADING_PLAN_NOT_EDITING' | 'PRELOAD_LINK_MISSING' | 'OCEAN_BOOKING_NOT_LINKED' | 'OCEAN_BOOKING_NOT_RELEASED' | 'PDA_TASK_ALREADY_CREATED' | 'PRELOAD_ALREADY_CONFIRMED' export interface PreloadReadiness { containerNoReady: boolean preloadLinkCount: number confirmedPreloadCount: number oceanBookingId: string | null oceanBookingStatus: 'CONFIRMED' | 'CHANGE_PENDING' | 'RELEASED' | 'CANCELLED' | null pdaTaskId: string | null pdaTaskStatus: string | null confirmationStatus: PreloadConfirmationStatus canConfirm: boolean readyForPda: boolean blockingReasons: PreloadBlockingReason[] } export interface PreloadSummary { planId: string containerBookingId: string preloadNo: string containerNo: string | null cabinetNo: string | null customers: LoadingCustomer[] warehouseId: string | null warehouseName: string | null vesselName: string | null voyageNo: string | null containerType: string | null status: string skuCount: string totalPackageCount: string totalVolumeCbm: number totalGrossWeightKg: number createdAt: string version: number readiness: PreloadReadiness } export interface ConfirmedSummary { planId: string containerBookingId: string loadingListNo: string warehouseId: string | null warehouseName: string | null containerNo: string | null cabinetNo: string | null status: string totalPackageCount: string totalGrossWeightKg: number totalNetWeightKg: number totalVolumeCbm: number totalQuantity: string remark: string | null createdBy: string | null createdByName: string | null createdAt: string version: number } export type LoadingListSummary = PreloadSummary | ConfirmedSummary export interface LoadingItem { itemId: string itemVersion: number accountId: string customerName: string | null customsTaxMode: 'TAX_REFUND' | 'BUY_DECLARATION' | null taxRefundFlag: boolean | null orderId: string orderNo: string inboundId: string inboundDetailId: string inboundCode: string | null containerNo: string | null skuCode: string | null destinationWarehouse: string | null chineseName: string | null englishName: string | null goodsImageUrl: string | null unitType: 'PACKAGE' | 'SEALED_PALLET' palletId: string | null palletNo: string | null indivisible: boolean packageCount: number palletCount: number grossWeightKg: number netWeightKg: number volumeCbm: number dimensionalWeightKg: number productQuantity: string unitsPerPackage: number fbaTrackingNo: string | null shipmentNo: string | null shippingMark: string | null internalRemark: string | null customerRemark: string | null warehouseRemark: string | null customerServiceRemark: string | null loadSequence: number frozenAt: string | null } export interface LoadingHistory { historyId: string itemId: string | null operationId: string actionType: string fromStatus: string | null toStatus: string | null beforeSnapshot: string | null afterSnapshot: string | null operatorId: string operatorName: string | null operationReason: string | null occurredAt: string } export interface LoadingDetail { summary: T items: LoadingItem[] histories: LoadingHistory[] } export interface LoadingListPage { records: T[] page: number size: number total: number pages: number } export interface LoadingListQuery { page: number size: number customerId?: string warehouseId?: string keyword?: string createdFrom?: string createdTo?: string } export interface TransportUpdateRequest { containerNo: string | null voyageNo: string | null expectedVersion: number operationReason: string | null } export interface LoadingItemRemarksUpdateRequest { internalRemark: string | null warehouseRemark: string | null customerServiceRemark: string | null expectedPlanVersion: number expectedItemVersion: number operationId: string } export interface LoadingItemRemarksUpdateResponse { id: string version: number items: Array<{ id: string; version: number; internalRemark: string | null; warehouseRemark: string | null; customerServiceRemark: string | null }> } export interface PreloadConfirmationResponse { planId: string containerBookingId: string confirmationStatus: 'CONFIRMED' version: number readiness: PreloadReadiness } type RawId = string | number | null | undefined type RawPage = Omit, 'page' | 'size' | 'total' | 'pages'> & { page: string | number size: string | number total: string | number pages: string | number } const basePath = '/platform/v1/first-leg' function requiredId(value: RawId) { return String(value ?? '') } function optionalId(value: RawId) { return value === null || value === undefined ? null : String(value) } function normalizePreloadReadiness(raw: PreloadReadiness): PreloadReadiness { return { ...raw, oceanBookingId: optionalId(raw.oceanBookingId), pdaTaskId: optionalId(raw.pdaTaskId), blockingReasons: raw.blockingReasons || [], } } function normalizePreloadSummary(raw: PreloadSummary): PreloadSummary { return { ...raw, planId: requiredId(raw.planId), containerBookingId: requiredId(raw.containerBookingId), warehouseId: optionalId(raw.warehouseId), skuCount: String(raw.skuCount ?? '0'), totalPackageCount: String(raw.totalPackageCount ?? '0'), readiness: normalizePreloadReadiness(raw.readiness), customers: (raw.customers || []).map((customer) => ({ ...customer, accountId: requiredId(customer.accountId), })), } } function normalizeConfirmedSummary(raw: ConfirmedSummary): ConfirmedSummary { return { ...raw, planId: requiredId(raw.planId), containerBookingId: requiredId(raw.containerBookingId), warehouseId: optionalId(raw.warehouseId), createdBy: optionalId(raw.createdBy), totalPackageCount: String(raw.totalPackageCount ?? '0'), totalQuantity: String(raw.totalQuantity ?? '0'), } } function normalizeItem(raw: LoadingItem): LoadingItem { return { ...raw, itemId: requiredId(raw.itemId), itemVersion: Number(raw.itemVersion), accountId: requiredId(raw.accountId), orderId: requiredId(raw.orderId), inboundId: requiredId(raw.inboundId), inboundDetailId: requiredId(raw.inboundDetailId), palletId: optionalId(raw.palletId), palletNo: raw.palletNo || null, indivisible: Boolean(raw.indivisible), productQuantity: String(raw.productQuantity ?? '0'), } } function normalizeHistory(raw: LoadingHistory): LoadingHistory { return { ...raw, historyId: requiredId(raw.historyId), itemId: optionalId(raw.itemId), operationId: requiredId(raw.operationId), operatorId: requiredId(raw.operatorId), } } export interface PalletLabel { labelId: string itemId: string accountId: string inboundId: string inboundCode: string containerNo: string destinationWarehouseCode: string destinationWarehouseInternalCode: string destinationWarehouseName: string | null palletIndex: number palletCode: string } export interface PalletLabelGroup { destinationWarehouseCode: string destinationWarehouseInternalCode: string destinationWarehouseName: string | null totalCount: number labels: PalletLabel[] } export interface PalletLabelManifest { planId: string planNo: string containerNo: string totalCount: number groups: PalletLabelGroup[] } function normalizePage(raw: RawPage, normalize: (row: T) => T): LoadingListPage { return { records: (raw.records || []).map(normalize), page: Number(raw.page), size: Number(raw.size), total: Number(raw.total), pages: Number(raw.pages), } } function idempotencyKey(planId: string) { return `admin-preload-transport-${planId}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}` } export async function listPreloadPlans(query: LoadingListQuery) { const response = await http.get>, ApiEnvelope>>( `${basePath}/loading-plans`, { params: query }, ) return normalizePage(unwrapApiResponse(response), normalizePreloadSummary) } export async function getPreloadPlan(planId: string, warehouseId: string) { const response = await http.get>, ApiEnvelope>>( `${basePath}/loading-plans/${planId}`, { params: { warehouseId } }, ) const result = unwrapApiResponse(response) return { summary: normalizePreloadSummary(result.summary), items: (result.items || []).map(normalizeItem), histories: (result.histories || []).map(normalizeHistory), } } export async function updatePreloadTransport(planId: string, warehouseId: string, request: TransportUpdateRequest) { const response = await http.patch>, ApiEnvelope>>( `${basePath}/loading-plans/${planId}/transport`, request, { params: { warehouseId }, headers: { 'Idempotency-Key': idempotencyKey(planId) } }, ) const result = unwrapApiResponse(response) return { summary: normalizePreloadSummary(result.summary), items: (result.items || []).map(normalizeItem), histories: (result.histories || []).map(normalizeHistory), } } export async function updateLoadingItemRemarks(planId: string, itemId: string, request: LoadingItemRemarksUpdateRequest) { const response = await http.patch, ApiEnvelope>( `${basePath}/smart-loading/plans/${encodeURIComponent(planId)}/items/${encodeURIComponent(itemId)}/remarks`, request, ) const result = unwrapApiResponse(response) return { ...result, id: requiredId(result.id), items: (result.items || []).map((item) => ({ ...item, id: requiredId(item.id), version: Number(item.version) })), } } export async function confirmPreloadPlan(planId: string, expectedVersion: number, operationId: string) { const response = await http.post, ApiEnvelope>( `${basePath}/loading-plans/${encodeURIComponent(planId)}/confirm`, { expectedVersion }, { headers: { 'Idempotency-Key': operationId } }, ) const result = unwrapApiResponse(response) return { ...result, planId: requiredId(result.planId), containerBookingId: requiredId(result.containerBookingId), readiness: normalizePreloadReadiness(result.readiness), } } export async function listConfirmedLoadingLists(query: LoadingListQuery) { const response = await http.get>, ApiEnvelope>>( `${basePath}/loading-lists`, { params: query }, ) return normalizePage(unwrapApiResponse(response), normalizeConfirmedSummary) } export async function getConfirmedLoadingList(planId: string, warehouseId: string) { const response = await http.get>, ApiEnvelope>>( `${basePath}/loading-lists/${planId}`, { params: { warehouseId } }, ) const result = unwrapApiResponse(response) return { summary: normalizeConfirmedSummary(result.summary), items: (result.items || []).map(normalizeItem), histories: (result.histories || []).map(normalizeHistory), } } export async function preparePalletLabels(planId: string, warehouseId: string) { const response = await http.post, ApiEnvelope>( `${basePath}/loading-plans/${encodeURIComponent(planId)}/pallet-labels/prepare`, null, { params: { warehouseId } }, ) const result = unwrapApiResponse(response) return { ...result, planId: requiredId(result.planId), groups: (result.groups || []).map((group) => ({ ...group, labels: (group.labels || []).map((label) => ({ ...label, labelId: requiredId(label.labelId), itemId: requiredId(label.itemId), accountId: requiredId(label.accountId), inboundId: requiredId(label.inboundId) })), })), } } export function printPalletLabels(planId: string, warehouseId: string, destinationWarehouseCode?: string, count?: number) { return http.post( `${basePath}/loading-plans/${encodeURIComponent(planId)}/pallet-labels/print`, { destinationWarehouseCode: destinationWarehouseCode || null, count: count || null }, { params: { warehouseId }, responseType: 'blob', timeout: 60_000 }, ) }