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_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) { const response = await http.post, ApiEnvelope>( `${path}/${encodeURIComponent(containerBookingId)}/${action}`, body, { headers: { 'Idempotency-Key': operationId(action) } }, ) return normalize(unwrapApiResponse(response)) } export function selectTerminalDrayageFleet(containerBookingId: string, body: { expectedVersion: number; fleetSupplierId: string; fleetSupplierName?: string; reason?: string }) { return command(containerBookingId, 'fleet', body) } export function scheduleTerminalDrayagePickup(containerBookingId: string, body: { expectedVersion: number; pickupAppointmentAt: string; reason?: string }) { return command(containerBookingId, 'pickup-appointment', body) } export function recordTerminalDrayagePickup(containerBookingId: string, body: { expectedVersion: number; actualAt: string; remark?: string }) { return command(containerBookingId, 'pickup', body) } export function recordTerminalDrayageEmptyReturn(containerBookingId: string, body: { expectedVersion: number; actualAt: string; remark?: string }) { return command(containerBookingId, 'empty-return', body) }