import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http' import { TRUCK_APPOINTMENT_SAVE_FIELDS, isTruckAttachmentType, normalizeTruckAppointmentCurrency } from '../types' import type { SaveTruckAppointmentRequest, TruckAppointmentPage, TruckAppointmentQuery, TruckAppointmentResource, TruckAppointmentActionRequest, TruckAppointmentCandidate, TruckAppointmentCandidatePage, TruckAppointmentCandidateQuery, TruckAppointmentStatusHistory, TruckAttachmentReference, TruckAttachmentType, } from '../types' const basePath = '/platform/v1/truck-appointments' function operationId(action: string, id: string | number) { return `truck-appointment-${action.toLowerCase()}-${id}-${Date.now()}-${Math.random().toString(36).slice(2)}` } function dtoPayload(payload: SaveTruckAppointmentRequest) { return Object.fromEntries(TRUCK_APPOINTMENT_SAVE_FIELDS.flatMap((key) => { const value = key === 'currency' ? normalizeTruckAppointmentCurrency(payload[key]) : payload[key] return value === undefined ? [] : [[key, value === '' ? null : value]] })) as unknown as SaveTruckAppointmentRequest } type PagePayload = Omit & { page: number | string size: number | string total: number | string pages?: number | string } function normalizePage(payload: PagePayload): TruckAppointmentPage { const page = Number(payload.page || 1) const size = Number(payload.size || 20) const total = Number(payload.total || 0) return { records: (payload.records || []).map(normalizeResource), page, size, total, pages: Number(payload.pages || Math.max(Math.ceil(total / size), 1)) } } function normalizeResource(resource: TruckAppointmentResource): TruckAppointmentResource { const idKeys = ['id', 'outboundOrderId', 'oceanBookingId', 'warehouseId', 'shippingCompanyId', 'bookingAgentId', 'carrierId'] const normalized = { ...resource } as Record for (const key of idKeys) { if (normalized[key] !== null && normalized[key] !== undefined) normalized[key] = String(normalized[key]) } normalized.attachmentCount = Number(normalized.attachmentCount ?? 0) return normalized as unknown as TruckAppointmentResource } export async function listTruckAppointments(query: TruckAppointmentQuery = {}) { const response = await http.get, ApiEnvelope>(basePath, { params: { ...query, keyword: query.keyword?.trim() || undefined, status: query.status || undefined, oceanBookingId: query.oceanBookingId || undefined, }, }) return normalizePage(unwrapApiResponse(response)) } type CandidatePagePayload = Omit & { records?: Array> page: number | string size: number | string total: number | string pages?: number | string } function normalizeCandidate(candidate: TruckAppointmentCandidate & Record): TruckAppointmentCandidate { const normalized = { ...candidate } as Record for (const key of ['oceanBookingId', 'warehouseId']) { if (normalized[key] !== null && normalized[key] !== undefined) normalized[key] = String(normalized[key]) } normalized.bookingDocumentReady = normalized.bookingDocumentReady === true return normalized as unknown as TruckAppointmentCandidate } export async function listTruckAppointmentCandidates(query: TruckAppointmentCandidateQuery = {}) { const response = await http.get, ApiEnvelope>(`${basePath}/candidates`, { params: { keyword: query.keyword?.trim() || undefined, page: query.page || 1, size: query.size || 200 }, }) const payload = unwrapApiResponse(response) const page = Number(payload.page || 1) const size = Number(payload.size || 200) const total = Number(payload.total || 0) return { records: (payload.records || []).map(normalizeCandidate), page, size, total, pages: Number(payload.pages || Math.max(Math.ceil(total / size), 1)), } satisfies TruckAppointmentCandidatePage } export async function getTruckAppointment(id: string | number) { const response = await http.get, ApiEnvelope>(`${basePath}/${id}`) return normalizeResource(unwrapApiResponse(response)) } export async function listTruckAppointmentHistory(id: string | number) { const response = await http.get, ApiEnvelope>(`${basePath}/${id}/history`) const payload = unwrapApiResponse(response) const records = Array.isArray(payload) ? payload : payload?.records || [] return records.map((item) => ({ ...item, id: item.id == null ? item.id : String(item.id), operatorId: item.operatorId == null ? item.operatorId : String(item.operatorId) })) } export async function listTruckAppointmentAttachments(id: string | number) { const response = await http.get, ApiEnvelope>(`${basePath}/${id}/attachments`) return unwrapApiResponse(response).map((item) => ({ ...item, id: item.id == null ? item.id : String(item.id), fileId: String(item.fileId), uploadedBy: item.uploadedBy == null ? item.uploadedBy : String(item.uploadedBy) })) } export async function referenceTruckAppointmentAttachment(id: string | number, payload: { fileId: string; attachmentType?: TruckAttachmentType | null; fileName?: string | null; fileHash?: string | null }) { const { attachmentType, ...rest } = payload const body = { ...rest, ...(isTruckAttachmentType(attachmentType) ? { attachmentType } : {}) } const response = await http.post, ApiEnvelope>(`${basePath}/${id}/attachments`, body) return unwrapApiResponse(response) } export async function uploadTruckAppointmentAttachment(id: string | number, file: File, attachmentType?: TruckAttachmentType | null) { const form = new FormData() form.append('file', file) if (isTruckAttachmentType(attachmentType)) form.append('attachmentType', attachmentType) const response = await http.post, ApiEnvelope>(`${basePath}/${id}/attachments/upload`, form, { headers: { 'Content-Type': 'multipart/form-data' } }) return unwrapApiResponse(response) } export async function deleteTruckAppointmentAttachment(id: string | number, attachmentId: string | number) { await http.delete, ApiEnvelope>(`${basePath}/${id}/attachments/${attachmentId}`) } export async function createTruckAppointment(payload: SaveTruckAppointmentRequest) { const response = await http.post, ApiEnvelope>(basePath, dtoPayload(payload)) return normalizeResource(unwrapApiResponse(response)) } export async function updateTruckAppointment(id: string | number, payload: SaveTruckAppointmentRequest) { const response = await http.put, ApiEnvelope>(`${basePath}/${id}`, dtoPayload(payload)) return unwrapApiResponse(response) } export type TruckAppointmentAction = 'SUBMIT' | 'DISPATCH' | 'PICKUP' | 'ARRIVE' | 'LOAD' | 'EXCEPTION' | 'CANCEL' export async function performTruckAppointmentAction( id: string | number, action: TruckAppointmentAction, payload: TruckAppointmentActionRequest, idempotencyKey?: string, requestHash?: string, ) { const operationKey = idempotencyKey || operationId(action, id) const body = { expectedVersion: payload.expectedVersion, reason: payload.reason || null, } const response = await http.post, ApiEnvelope>(`${basePath}/${id}/${action.toLowerCase()}`, body, { headers: { 'Idempotency-Key': operationKey, ...(requestHash ? { 'X-Request-Hash': requestHash } : {}) }, }) return unwrapApiResponse(response) } export async function rescheduleTruckAppointment(id: string | number, plannedLoadingAt: string, expectedVersion: number, reason?: string) { const response = await http.post, ApiEnvelope>(`${basePath}/${id}/reschedule`, { expectedVersion, reason: reason || null }, { params: { plannedLoadingAt }, headers: { 'Idempotency-Key': operationId('RESCHEDULE', id) }, }) return unwrapApiResponse(response) }