2026-10-03 04:35:55 +08:00
|
|
|
import axios from 'axios'
|
|
|
|
|
import { http, unwrapApiResponse, ApiResponseError, type ApiEnvelope } from '@/shared/api/http'
|
|
|
|
|
import type { VehicleCareDraft, VehicleCareRecord, VehicleCareType, VehicleIncident } from '../vehicleCareTypes'
|
|
|
|
|
import type { PageResult } from './overseasMasterData'
|
|
|
|
|
|
|
|
|
|
type Numeric = number | string
|
|
|
|
|
type Wire = Omit<VehicleCareRecord, 'recordVersion' | 'details' | 'files'> & {
|
|
|
|
|
recordVersion: Numeric
|
2026-10-03 10:56:47 +08:00
|
|
|
details: Pick<VehicleCareDraft, 'recordType' | 'occurredAt'> & Partial<Omit<VehicleCareDraft, 'odometer' | 'lastServiceOdometer' | 'nextServiceOdometer' | 'fuelQuantity' | 'incident'>> & {
|
|
|
|
|
odometer?: Numeric | null; lastServiceOdometer?: Numeric | null; nextServiceOdometer?: Numeric | null; fuelQuantity?: Numeric | null
|
|
|
|
|
incident?: (Pick<VehicleIncident, 'description' | 'processingStatus'> & Partial<Omit<VehicleIncident, 'reportedCost'>> & { reportedCost?: Numeric | null }) | null
|
2026-10-03 04:35:55 +08:00
|
|
|
}
|
|
|
|
|
files: Array<Omit<VehicleCareRecord['files'][number], 'sizeBytes'> & { sizeBytes: Numeric }>
|
|
|
|
|
}
|
|
|
|
|
const path = (vehicleId: string, recordNo?: string) => `/platform/v1/last-leg/fleet/vehicles/${encodeURIComponent(vehicleId)}/records${recordNo ? `/${encodeURIComponent(recordNo)}` : ''}`
|
|
|
|
|
function integer(value: Numeric): number {
|
|
|
|
|
const result = Number(value)
|
|
|
|
|
if (!Number.isSafeInteger(result) || result < 0) throw new Error('运维接口返回了无效的分页或版本')
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
function decimal(value: Numeric): number {
|
|
|
|
|
const result = Number(value)
|
|
|
|
|
if (value === '' || !Number.isFinite(result) || result < 0) throw new Error('运维接口返回了无效数值')
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
function record(value: Wire): VehicleCareRecord {
|
2026-10-03 10:56:47 +08:00
|
|
|
const optional = (number: Numeric | null | undefined) => number == null ? null : decimal(number)
|
|
|
|
|
const details = value.details, incident = details.incident
|
|
|
|
|
return { ...value, recordVersion: integer(value.recordVersion), paymentStatus: value.paymentStatus ?? null, details: {
|
|
|
|
|
recordType: details.recordType, occurredAt: details.occurredAt, distanceUnit: details.distanceUnit ?? null,
|
|
|
|
|
odometer: optional(details.odometer), lastServiceOdometer: optional(details.lastServiceOdometer),
|
|
|
|
|
nextServiceOdometer: optional(details.nextServiceOdometer), fuelQuantity: optional(details.fuelQuantity),
|
|
|
|
|
fuelUnit: details.fuelUnit ?? null, verificationStatus: details.verificationStatus ?? null, remark: details.remark ?? null,
|
|
|
|
|
incident: incident ? { description: incident.description, processingStatus: incident.processingStatus,
|
|
|
|
|
item: incident.item ?? null, place: incident.place ?? null, handler: incident.handler ?? null,
|
|
|
|
|
exceptionType: incident.exceptionType ?? null, reportedCost: optional(incident.reportedCost), currency: incident.currency ?? null } : null },
|
2026-10-03 04:35:55 +08:00
|
|
|
files: value.files.map(file => ({ ...file, sizeBytes: integer(file.sizeBytes) })) }
|
|
|
|
|
}
|
|
|
|
|
export async function listVehicleCare(vehicleId: string, recordType: VehicleCareType, page = 1, size = 10): Promise<PageResult<VehicleCareRecord>> {
|
|
|
|
|
type PageWire = { records: Wire[]; page: Numeric; size: Numeric; total: Numeric; pages: Numeric }
|
|
|
|
|
const result = unwrapApiResponse(await http.get<ApiEnvelope<PageWire>, ApiEnvelope<PageWire>>(path(vehicleId), { params: { recordType, page, size } }))
|
|
|
|
|
return { records: result.records.map(record), page: integer(result.page), size: integer(result.size), total: integer(result.total), pages: integer(result.pages) }
|
|
|
|
|
}
|
|
|
|
|
export async function createVehicleCare(vehicleId: string, idempotencyKey: string, details: VehicleCareDraft): Promise<VehicleCareRecord> {
|
|
|
|
|
return record(unwrapApiResponse(await http.post<ApiEnvelope<Wire>, ApiEnvelope<Wire>>(path(vehicleId), { idempotencyKey, details })))
|
|
|
|
|
}
|
|
|
|
|
export async function getVehicleCare(vehicleId: string, recordNo: string): Promise<VehicleCareRecord> {
|
|
|
|
|
return record(unwrapApiResponse(await http.get<ApiEnvelope<Wire>, ApiEnvelope<Wire>>(path(vehicleId, recordNo))))
|
|
|
|
|
}
|
|
|
|
|
export async function updateVehicleCare(vehicleId: string, recordNo: string, recordVersion: number, reason: string, details: VehicleCareDraft): Promise<VehicleCareRecord> {
|
|
|
|
|
return record(unwrapApiResponse(await http.put<ApiEnvelope<Wire>, ApiEnvelope<Wire>>(path(vehicleId, recordNo), { recordVersion, reason, details })))
|
|
|
|
|
}
|
|
|
|
|
export async function uploadVehicleCarePhoto(vehicleId: string, recordNo: string, recordVersion: number, idempotencyKey: string, file: File): Promise<VehicleCareRecord> {
|
|
|
|
|
const form = new FormData(); form.append('recordVersion', String(recordVersion)); form.append('idempotencyKey', idempotencyKey); form.append('file', file)
|
|
|
|
|
return record(unwrapApiResponse(await http.post<ApiEnvelope<Wire>, ApiEnvelope<Wire>>(`${path(vehicleId, recordNo)}/files`, form)))
|
|
|
|
|
}
|
|
|
|
|
async function downloadError(value: unknown, status: number): Promise<void> {
|
|
|
|
|
if (value instanceof Blob && value.type.includes('json')) {
|
|
|
|
|
try { value = JSON.parse(await value.text()) as unknown } catch { throw new Error('图片接口返回了无法识别的错误') }
|
|
|
|
|
}
|
|
|
|
|
if (value && typeof value === 'object' && 'code' in value) {
|
|
|
|
|
const error = value as ApiEnvelope<unknown>
|
|
|
|
|
throw new ApiResponseError(error.message || '图片读取失败', error.code, status, error.data, error.requestId, error.traceId)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
export async function downloadVehicleCarePhoto(vehicleId: string, recordNo: string, fileId: string): Promise<Blob> {
|
|
|
|
|
try {
|
|
|
|
|
const blob = await http.get<Blob, Blob>(`${path(vehicleId, recordNo)}/files/${encodeURIComponent(fileId)}`, { responseType: 'blob' })
|
|
|
|
|
await downloadError(blob, 200)
|
|
|
|
|
if (!(blob instanceof Blob) || !blob.size || !['image/png', 'image/jpeg'].includes(blob.type)) throw new Error('照片为空或格式无效')
|
|
|
|
|
return blob
|
|
|
|
|
} catch (cause) {
|
|
|
|
|
if (axios.isAxiosError(cause)) await downloadError(cause.response?.data, cause.response?.status || 0)
|
|
|
|
|
throw cause
|
|
|
|
|
}
|
|
|
|
|
}
|