import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http' export type TerminalDrayageStatus = 'WAITING_FOR_ARRIVAL' | 'ARRIVED' | 'PICKUP_SCHEDULED' | 'PICKED_UP' | 'UNLOADED' | 'EMPTY_RETURNED' export type TerminalDrayageAction = 'SELECT_FLEET' | 'SCHEDULE_PICKUP' | 'RECORD_EMPTY_RETURN' | 'SEND_PICKUP_EMAIL' export interface TerminalDrayageEmail { id: string fleetName: string recipient: string kind: 'REQUEST_PICKUP' | 'CANCEL_PICKUP' subject: string body: string status: 'PENDING' | 'SENDING' | 'SENT' | 'FAILED' attempts: number failureReason: string | null createdAt: string sentAt: string | null } export async function getTerminalDrayageEmails(containerId: string) { type Mailbox = { deliveryEnabled: boolean; records: TerminalDrayageEmail[] } const response = await http.get, ApiEnvelope>(`${path}/${encodeURIComponent(containerId)}/emails`) const result = unwrapApiResponse(response) return { ...result, records: result.records.map((mail) => ({ ...mail, id: String(mail.id) })) } } export function sendTerminalDrayageEmail(containerId: string, expectedVersion: number) { return command(containerId, 'pickup-email', { expectedVersion }) } export async function retryTerminalDrayageEmail(containerId: string, mailId: string) { const response = await http.post, ApiEnvelope>(`${path}/${encodeURIComponent(containerId)}/emails/${encodeURIComponent(mailId)}/retry`) unwrapApiResponse(response) } export type EmptyReturnWarningStatus = 'NOT_APPLICABLE' | 'PENDING' | 'OVERDUE' | 'RESOLVED' export interface EmptyReturnWarning { status: EmptyReturnWarningStatus deadlineAt: string | null overdueHours: number overdueDays: number } export interface TerminalDrayageView { containerBookingId: string containerNo: string | null oceanBookingId: string | null status: TerminalDrayageStatus version: number fleetSupplierId: string | null fleetSupplierName: string | null pickupAppointmentAt: string | null actualPickupAt: string | null estimatedEmptyReturnAt: string | null actualEmptyReturnAt: string | null unloadingCompletedAt: string | null emptyReturnWarning: EmptyReturnWarning allowedActions: TerminalDrayageAction[] updatedAt: string | null } export interface TerminalDrayageHistory { action: TerminalDrayageAction fromStatus: string toStatus: string fromVersion: number toVersion: number reason: string | null operatorId: string operatorName: string | null createdAt: string } const path = '/platform/v1/terminal-drayages' function operationId(action: string) { return `terminal-drayage-${action}-${Date.now()}-${Math.random().toString(36).slice(2)}` } function id(value: unknown): string { return value == null ? '' : String(value) } function optionalId(value: unknown): string | null { return value == null ? null : String(value) } function normalize(raw: TerminalDrayageView): TerminalDrayageView { return { ...raw, containerBookingId: id(raw.containerBookingId), oceanBookingId: optionalId(raw.oceanBookingId), fleetSupplierId: optionalId(raw.fleetSupplierId), version: Number(raw.version || 0), allowedActions: raw.allowedActions || [], emptyReturnWarning: { ...raw.emptyReturnWarning, overdueHours: Number(raw.emptyReturnWarning?.overdueHours || 0), overdueDays: Number(raw.emptyReturnWarning?.overdueDays || 0), }, } } export async function listTerminalDrayages(containerBookingIds: string[]) { const params = new URLSearchParams() containerBookingIds.forEach((containerBookingId) => params.append('containerBookingIds', containerBookingId)) const response = await http.get, ApiEnvelope>(path, { params }) return (unwrapApiResponse(response) || []).map(normalize) } export async function getTerminalDrayage(containerBookingId: string) { const response = await http.get, ApiEnvelope>(`${path}/${encodeURIComponent(containerBookingId)}`) return normalize(unwrapApiResponse(response)) } export async function getTerminalDrayageHistory(containerBookingId: string) { const response = await http.get, ApiEnvelope>(`${path}/${encodeURIComponent(containerBookingId)}/history`) return (unwrapApiResponse(response) || []).map((item) => ({ ...item, operatorId: id(item.operatorId) })) } async function command(containerBookingId: string, action: string, body: object, idempotencyKey = operationId(action)) { const response = await http.post, ApiEnvelope>( `${path}/${encodeURIComponent(containerBookingId)}/${action}`, body, { headers: { 'Idempotency-Key': idempotencyKey } }, ) return normalize(unwrapApiResponse(response)) } export function selectTerminalDrayageFleet(containerBookingId: string, body: { expectedVersion: number; fleetSupplierId: string; fleetSupplierName?: string; reason?: string }, idempotencyKey?: string) { return command(containerBookingId, 'fleet', body, idempotencyKey) } export function scheduleTerminalDrayagePickup(containerBookingId: string, body: { expectedVersion: number; pickupAppointmentAt: string; reason?: string }, idempotencyKey?: string) { return command(containerBookingId, 'pickup-appointment', body, idempotencyKey) } export function recordTerminalDrayageEmptyReturn(containerBookingId: string, body: { expectedVersion: number; actualAt: string; remark?: string }, idempotencyKey?: string) { return command(containerBookingId, 'empty-return', body, idempotencyKey) } export interface DirectDelivery { requirementKey: string; inboundCode: string | null; orderNo: string | null; destinationName: string | null packageCount: number | null; version: number; actualAt: string | null; timezone: string | null receivedBy: string | null; evidenceNo: string | null; remark: string | null } export interface DirectDeliveryCommand { expectedVersion: number; actualAt: string; timezone: string; receivedBy: string; evidenceNo: string; remark: string | null } export async function getDirectDeliveries(containerBookingId: string) { return unwrapApiResponse(await http.get, ApiEnvelope>(`${path}/${encodeURIComponent(containerBookingId)}/direct-deliveries`)) } export async function recordDirectDelivery(containerBookingId: string, requirementKey: string, body: DirectDeliveryCommand, idempotencyKey: string) { if (!/^INBOUND:\d+$/.test(requirementKey)) throw new Error('服务端入仓来源标识无效,请重新读取清单') const inboundId = requirementKey.slice('INBOUND:'.length) return unwrapApiResponse(await http.post, ApiEnvelope>(`${path}/${encodeURIComponent(containerBookingId)}/direct-deliveries/${encodeURIComponent(inboundId)}`, body, { headers: { 'Idempotency-Key': idempotencyKey } })) } export interface TerminalPickupCommand { standardNode: 'PICKED_UP'; observedAt: string; estimatedAt: null; actualAt: string; locationCode: string | null; locationName: string | null; reason: string } export async function recordTerminalPickup(containerBookingId: string, body: TerminalPickupCommand, idempotencyKey: string) { unwrapApiResponse(await http.post, ApiEnvelope>(`/platform/v1/container-shipping-nodes/${encodeURIComponent(containerBookingId)}`, body, { headers: { 'Idempotency-Key': idempotencyKey } })) }