Files
admin-web/src/modules/overseas-master-data/api/fleetQuotes.ts
T
2026-10-08 17:08:03 +08:00

57 lines
5.9 KiB
TypeScript

import { http, unwrapApiResponse, ApiResponseError, type ApiEnvelope } from '@/shared/api/http'
import type { PageResult } from './overseasMasterData'
export type FleetQuoteKind = 'PURCHASE' | 'SALE'
export interface FleetQuoteDraft {
kind: FleetQuoteKind; partyId: string; originCity: string; destinationCode: string; distanceMiles: number
currency: 'USD' | 'CNY'; selfPickupPrice: number | null; fullDeliveryPrice: number | null; looseDeliveryPrice: number | null
validFrom: string; validTo: string; remark: string | null
}
export interface FleetQuote { quoteNo: string; details: FleetQuoteDraft; partyName: string; status: 'ACTIVE' | 'INACTIVE' | 'DELETED'; recordVersion: string; updatedAt: string }
export interface FleetQuoteQuery { page?: number; size?: number; keyword?: string; kind?: FleetQuoteKind; status?: string; effectiveOn?: string }
type Numeric = string | number
type WireQuote = Omit<FleetQuote, 'details' | 'recordVersion'> & { recordVersion: Numeric; details: Omit<FleetQuoteDraft, 'distanceMiles' | 'selfPickupPrice' | 'fullDeliveryPrice' | 'looseDeliveryPrice'> & { distanceMiles: Numeric; selfPickupPrice: Numeric | null; fullDeliveryPrice: Numeric | null; looseDeliveryPrice: Numeric | null } }
export function fleetNumber(value: Numeric): number {
if ((typeof value !== 'string' && typeof value !== 'number') || String(value).trim() === '' || !Number.isFinite(Number(value)) || Number(value) < 0) throw new Error('接口返回了无效数值')
return Number(value)
}
export function fleetMoney(value: Numeric): number {
const text = String(value)
if (!/^\d+(\.\d{1,2})?$/.test(text)) throw new Error('金额格式无效')
const [whole, fraction = ''] = text.split('.')
const cents = BigInt(whole!) * 100n + BigInt(fraction.padEnd(2, '0'))
if (cents > BigInt(Number.MAX_SAFE_INTEGER) || BigInt(Number(value).toFixed(2).replace('.', '')) !== cents) throw new Error('金额超出精确展示范围')
return Number(value)
}
export function fleetVersion(value: Numeric): string {
if (!/^(0|[1-9]\d*)$/.test(String(value)) || (typeof value === 'number' && !Number.isSafeInteger(value))) throw new Error('接口版本无效')
return String(value)
}
export function isFleetCommandRejected(error: unknown): boolean {
return error instanceof ApiResponseError && ((error.status != null && error.status >= 400 && error.status < 500)
|| /^A04\d\d$/.test(error.code) || ['400', '409', '422', 'VALIDATION_FAILED', 'CONFLICT'].includes(error.code))
}
export function fleetPage<T>(value: { records: T[]; page: Numeric; size: Numeric; total: Numeric; pages: Numeric }): PageResult<T> {
const integer = (n: Numeric) => { const result = fleetNumber(n); if (!Number.isSafeInteger(result)) throw new Error('接口分页超出有效范围'); return result }
return { ...value, page: integer(value.page), size: integer(value.size), total: integer(value.total), pages: integer(value.pages) }
}
const base = '/platform/v1/last-leg/fleet/quotes'
const path = (no: string) => `${base}/${encodeURIComponent(no)}`
const quote = (v: WireQuote): FleetQuote => ({ ...v, recordVersion: fleetVersion(v.recordVersion), details: { ...v.details, partyId: String(v.details.partyId), distanceMiles: fleetNumber(v.details.distanceMiles),
selfPickupPrice: v.details.selfPickupPrice == null ? null : fleetNumber(v.details.selfPickupPrice), fullDeliveryPrice: v.details.fullDeliveryPrice == null ? null : fleetNumber(v.details.fullDeliveryPrice), looseDeliveryPrice: v.details.looseDeliveryPrice == null ? null : fleetNumber(v.details.looseDeliveryPrice) } })
export async function listFleetQuotes(query: FleetQuoteQuery) {
const result = unwrapApiResponse(await http.get<ApiEnvelope<PageResult<WireQuote>>, ApiEnvelope<PageResult<WireQuote>>>(base, { params: query }))
return fleetPage({ ...result, records: result.records.map(quote) })
}
export async function createFleetQuote(idempotencyKey: string, details: FleetQuoteDraft) { return quote(unwrapApiResponse(await http.post<ApiEnvelope<WireQuote>, ApiEnvelope<WireQuote>>(base, { idempotencyKey, details }))) }
export async function updateFleetQuote(value: FleetQuote, details: FleetQuoteDraft) { return quote(unwrapApiResponse(await http.put<ApiEnvelope<WireQuote>, ApiEnvelope<WireQuote>>(path(value.quoteNo), { recordVersion: value.recordVersion, details }))) }
export async function setFleetQuoteStatus(value: FleetQuote, status: string) { return quote(unwrapApiResponse(await http.patch<ApiEnvelope<WireQuote>, ApiEnvelope<WireQuote>>(`${path(value.quoteNo)}/status`, { recordVersion: value.recordVersion, status }))) }
export async function deleteFleetQuote(value: FleetQuote) { return quote(unwrapApiResponse(await http.delete<ApiEnvelope<WireQuote>, ApiEnvelope<WireQuote>>(path(value.quoteNo), { params: { recordVersion: value.recordVersion } }))) }
export async function importFleetQuotes(idempotencyKey: string, rows: FleetQuoteDraft[]) { return unwrapApiResponse(await http.post<ApiEnvelope<{ importedRows: number }>, ApiEnvelope<{ importedRows: number }>>(`${base}/import`, { idempotencyKey, rows })) }
export async function fleetQuoteCustomers(keyword: string) { return unwrapApiResponse(await http.get<ApiEnvelope<{ id: string; name: string; code: string }[]>, ApiEnvelope<{ id: string; name: string; code: string }[]>>(`${base}/customer-options`, { params: { keyword } })) }
export async function fleetQuoteSuppliers(keyword: string) {
const result = unwrapApiResponse(await http.get<ApiEnvelope<PageResult<{ id: string; name: string }>>, ApiEnvelope<PageResult<{ id: string; name: string }>>>('/platform/v1/suppliers', { params: { page: 1, size: 30, keyword, status: 'ACTIVE', category: 'TRUCKING', truckingScope: 'OVERSEAS' } }))
return result.records.map(row => ({ ...row, id: String(row.id) }))
}
export function saveFleetCsv(blob: Blob, name: string) { const url = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = url; anchor.download = name; anchor.click(); setTimeout(() => URL.revokeObjectURL(url), 1000) }