import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http' import type { FbaAppointmentDetailsPayload, FbaAppointmentDto } from '@/modules/overseas-warehouse' export interface PlanningCargo { sourceKey: string orderId: string customerId: string customerName: string | null inboundCode: string | null containerBookingId: string containerNo: string | null originWarehouseId: string originWarehouseCode: string originWarehouseName: string destinationWarehouseCode: string packages: number | null receivedPackages: number | null shippedPackages: number | null sourcePalletCount: number | null volumeCbm: number | null estimatedArrivalAt: string | null estimatedWarehouseAt: string | null orderVersion: number standardNode: string shippingVersion: string | null shippingObservedAt: string | null shippingProvider: string | null hasInboundPlan: boolean hasActiveAppointment: boolean detained: boolean mixedDestinations: boolean } export interface PlanningCargoPage { records: PlanningCargo[]; page: number; size: number; total: number; pages: number } export async function listPlanningCargo(query: { page: number; size: number; keyword?: string }, signal?: AbortSignal) { const response = await http.get, ApiEnvelope>( '/platform/v1/last-leg/appointment-planning/cargo', { params: query, signal }, ) const page = unwrapApiResponse(response) return { ...page, page: Number(page.page), size: Number(page.size), total: Number(page.total), pages: Number(page.pages) } } export type FollowupStage = 'DRAYAGE_CONTACT' | 'TERMINAL_APPOINTMENT' | 'WAREHOUSE_RECEIVING' | 'FBA_RECEIVING' export interface FollowupRecord { stage: FollowupStage; version: number; contactedParty: string | null; result: string; nextAction: string | null; followupDate: string | null; operatorName: string | null; createdAt: string } export interface FollowupHistory { history: { records: FollowupRecord[]; page: number; size: number; total: number; pages: number }; versions: Record } export interface SaveFollowup { stage: FollowupStage; expectedVersion: number; idempotencyKey: string; contactedParty?: string; result: string; nextAction?: string; followupDate: string | null } export async function getFollowups(sourceKey: string, customerId: string, page: number, size: number) { const result = unwrapApiResponse(await http.get, ApiEnvelope>( `/platform/v1/last-leg/appointment-planning/sources/${encodeURIComponent(sourceKey)}/follow-ups`, { params: { customerId, page, size } }, )) return { ...result, history: { ...result.history, total: Number(result.history.total) }, versions: Object.fromEntries(Object.entries(result.versions).map(([stage, version]) => [stage, Number(version)])) as Record } } export async function saveFollowup(sourceKey: string, customerId: string, body: SaveFollowup) { return unwrapApiResponse(await http.post, ApiEnvelope>( `/platform/v1/last-leg/appointment-planning/sources/${encodeURIComponent(sourceKey)}/follow-ups`, body, { params: { customerId } }, )) } export interface PlanningSource { sourceKey: string; customerId: string; orderId: string | null; inboundCode: string | null; bookingNo: string | null; containerNo: string | null; originWarehouseId: string; originWarehouseName: string; destinationKey: string; destinationWarehouseCode: string; packages: number | null; sourceVersion: string | null; canFollowup: boolean; blockReasons: string[]; } export interface AppointmentCoverage { status: string; missingFields: string[]; changeReviewRequired: boolean; appointments: Array<{ appointmentNo: string; confirmationNo: string | null; status: string; confirmedAt: string | null; confirmedEndAt: string | null; timezone: string | null }>; } export interface ResolvedPlanningSource { source: PlanningSource; coverage: AppointmentCoverage } export interface ExternalAppointmentCandidate extends ResolvedPlanningSource { customerName: string | null; tripNo: string } export async function listExternalAppointmentCandidates(query: { page: number; size: number; keyword?: string }, signal?: AbortSignal) { const result = unwrapApiResponse(await http.get, ApiEnvelope<{ records: ExternalAppointmentCandidate[]; total: number }>>( '/platform/v1/last-leg/appointment-planning/external-candidates', { params: query, signal }, )) return { ...result, total: Number(result.total) } } export interface PlanningBatchPreview { group: { originWarehouseId: string; destinationKey: string; packages: number | null; sourceCount: number; customerCount: number; readinessIncomplete: boolean; readyFrom: string | null; readyTo: string | null; unknownQuantitySources: number } originWarehouseName: string destinationWarehouseCode: string sources: PlanningSource[] missingFields: string[] } export async function previewPlanningBatch(sources: Array<{sourceKey: string; customerId: string; destinationKey: string}>) { const result = unwrapApiResponse(await http.post, ApiEnvelope>( '/platform/v1/last-leg/appointment-planning/batches/preview', { sources }, )) return { ...result, group: { ...result.group, packages: result.group.packages == null ? null : Number(result.group.packages), sourceCount: Number(result.group.sourceCount), customerCount: Number(result.group.customerCount) } } } export interface SourceAppointmentPayload { originWarehouseId: string; fbaWarehouseCode: string; sources: Array<{sourceKey: string; customerId: string; destinationKey: string}>; details: FbaAppointmentDetailsPayload; idempotencyKey: string } export async function createSourceAppointment(payload: SourceAppointmentPayload) { return unwrapApiResponse(await http.post, ApiEnvelope>('/platform/v1/last-leg/appointment-planning/appointments', payload)) } export async function listUnscheduledAppointments(page: number, size: number) { const result = unwrapApiResponse(await http.get, ApiEnvelope<{records: FbaAppointmentDto[]; total: number}>>('/platform/v1/last-leg/appointment-planning/appointments/unscheduled', { params: { page, size } })) return { ...result, total: Number(result.total) } } export async function resolvePlanningSource(query: { sourceKey: string; customerId: string; destinationKey?: string }) { return unwrapApiResponse(await http.get, ApiEnvelope>( '/platform/v1/last-leg/appointment-planning/sources/resolve', { params: query }, )) } export interface PlanningContainer { containerBookingId: string; containerNo: string | null; orderCount: number; customerCount: number; packages: number | null; unknownPackageOrders: number; earliestArrivalAt: string | null; latestArrivalAt: string | null; standardNode: string; sourceVersion: string | null; incompleteScope: boolean; warehouses: Array<{ warehouseId: string; warehouseName: string; orderCount: number; customerCount: number }>; } export interface ContainerFollowupContext { containerBookingId: string; containerNo: string | null; warehouseId: string | null; warehouseName: string | null; canFollowup: boolean; blockReasons: string[]; } const containerBase = '/platform/v1/last-leg/appointment-planning/containers' export async function listPlanningContainers(query: { page: number; size: number; keyword?: string }, signal?: AbortSignal) { const result = unwrapApiResponse(await http.get, ApiEnvelope<{ records: PlanningContainer[]; total: number }>>(containerBase, { params: query, signal })) return { ...result, total: Number(result.total), records: result.records.map(row => ({ ...row, orderCount: Number(row.orderCount), customerCount: Number(row.customerCount), packages: row.packages == null ? null : Number(row.packages), unknownPackageOrders: Number(row.unknownPackageOrders), warehouses: row.warehouses.map(warehouse => ({ ...warehouse, orderCount: Number(warehouse.orderCount), customerCount: Number(warehouse.customerCount) })), })) } } export async function resolvePlanningContainer(containerId: string, warehouseId?: string) { return unwrapApiResponse(await http.get, ApiEnvelope>(`${containerBase}/${encodeURIComponent(containerId)}`, { params: { warehouseId } })) } export async function getContainerFollowups(context: ContainerFollowupContext, page: number, size: number) { const result = unwrapApiResponse(await http.get, ApiEnvelope>(`${containerBase}/${encodeURIComponent(context.containerBookingId)}/follow-ups`, { params: { warehouseId: context.warehouseId || undefined, page, size } })) return { ...result, history: { ...result.history, total: Number(result.history.total) }, versions: Object.fromEntries(Object.entries(result.versions).map(([stage, version]) => [stage, Number(version)])) as Record } } export async function saveContainerFollowup(context: ContainerFollowupContext, body: SaveFollowup) { return unwrapApiResponse(await http.post, ApiEnvelope>(`${containerBase}/${encodeURIComponent(context.containerBookingId)}/follow-ups`, body, { params: { warehouseId: context.warehouseId || undefined } })) }