172 lines
8.5 KiB
TypeScript
172 lines
8.5 KiB
TypeScript
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<TruckAppointmentPage, 'page' | 'size' | 'total' | 'pages'> & {
|
|
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<string, unknown>
|
|
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<PagePayload>, ApiEnvelope<PagePayload>>(basePath, {
|
|
params: {
|
|
...query,
|
|
keyword: query.keyword?.trim() || undefined,
|
|
status: query.status || undefined,
|
|
oceanBookingId: query.oceanBookingId || undefined,
|
|
},
|
|
})
|
|
return normalizePage(unwrapApiResponse(response))
|
|
}
|
|
|
|
type CandidatePagePayload = Omit<TruckAppointmentCandidatePage, 'records' | 'page' | 'size' | 'total' | 'pages'> & {
|
|
records?: Array<TruckAppointmentCandidate & Record<string, unknown>>
|
|
page: number | string
|
|
size: number | string
|
|
total: number | string
|
|
pages?: number | string
|
|
}
|
|
|
|
function normalizeCandidate(candidate: TruckAppointmentCandidate & Record<string, unknown>): TruckAppointmentCandidate {
|
|
const normalized = { ...candidate } as Record<string, unknown>
|
|
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<CandidatePagePayload>, ApiEnvelope<CandidatePagePayload>>(`${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<TruckAppointmentResource>, ApiEnvelope<TruckAppointmentResource>>(`${basePath}/${id}`)
|
|
return normalizeResource(unwrapApiResponse(response))
|
|
}
|
|
|
|
export async function listTruckAppointmentHistory(id: string | number) {
|
|
const response = await http.get<ApiEnvelope<TruckAppointmentStatusHistory[] | { records?: TruckAppointmentStatusHistory[] }>, ApiEnvelope<TruckAppointmentStatusHistory[] | { records?: TruckAppointmentStatusHistory[] }>>(`${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<TruckAttachmentReference[]>, ApiEnvelope<TruckAttachmentReference[]>>(`${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<TruckAttachmentReference>, ApiEnvelope<TruckAttachmentReference>>(`${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<TruckAttachmentReference>, ApiEnvelope<TruckAttachmentReference>>(`${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<unknown>, ApiEnvelope<unknown>>(`${basePath}/${id}/attachments/${attachmentId}`)
|
|
}
|
|
|
|
export async function createTruckAppointment(payload: SaveTruckAppointmentRequest) {
|
|
const response = await http.post<ApiEnvelope<TruckAppointmentResource>, ApiEnvelope<TruckAppointmentResource>>(basePath, dtoPayload(payload))
|
|
return normalizeResource(unwrapApiResponse(response))
|
|
}
|
|
|
|
export async function updateTruckAppointment(id: string | number, payload: SaveTruckAppointmentRequest) {
|
|
const response = await http.put<ApiEnvelope<TruckAppointmentResource>, ApiEnvelope<TruckAppointmentResource>>(`${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<TruckAppointmentResource>, ApiEnvelope<TruckAppointmentResource>>(`${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<TruckAppointmentResource>, ApiEnvelope<TruckAppointmentResource>>(`${basePath}/${id}/reschedule`, { expectedVersion, reason: reason || null }, {
|
|
params: { plannedLoadingAt },
|
|
headers: { 'Idempotency-Key': operationId('RESCHEDULE', id) },
|
|
})
|
|
return unwrapApiResponse(response)
|
|
}
|