feat(admin): streamline appointments and fleet intake

This commit is contained in:
2026-10-09 22:05:51 +08:00
parent 88dc4852c6
commit e1457a8e57
39 changed files with 1215 additions and 165 deletions
@@ -1,3 +1,4 @@
import { normalizeBooking, normalizeIncoming, normalizeBookingResult } from './tripBookings'
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import type { CapacityOffer, CapacityInquiry, CapacityOfferDetail, CapacityLoad, OfferPage, OfferQuery, InquiryQuery, OfferWrite, OfferCommand, InquiryWrite, InquiryCommand, OfferResult, InquiryResult, OfferCustomerOption } from '../capacityOfferTypes'
import { normalizeFleetQuotePage, normalizeFleetQuoteSource, type WireQuotePage } from './fleetQuoteCandidates'
@@ -5,7 +6,7 @@ import type { TripBookingQuoteCriteria } from './tripBookings'
const base = '/platform/v1/last-leg/fleet/capacity-offers'
const path = (value: string) => `${base}/${encodeURIComponent(value)}`
const inquiryPath = (value: string) => `${base}/inquiries/${encodeURIComponent(value)}`
const number = (value: number) => { const n = Number(value); if (value == null || !Number.isFinite(n)) throw new Error('运力数据数值无效'); return n }
const number = (value: number | string) => { const n = Number(value); if (value == null || String(value).trim() === '' || !Number.isFinite(n)) throw new Error('运力数据数值无效'); return n }
const nullable = (value: number | null) => value == null ? null : number(value)
const load = (value: CapacityLoad | null) => value == null ? null : { palletSpaces: number(value.palletSpaces), weightKg: number(value.weightKg), volumeCbm: number(value.volumeCbm) }
export function normalizeOffer(value: CapacityOffer): CapacityOffer { return { ...value, recordVersion: number(value.recordVersion), tripRecordVersion: number(value.tripRecordVersion), destinations: value.destinations.map(d => ({ ...d, deliveryIndex: number(d.deliveryIndex), remaining: load(d.remaining) })), segments: value.segments.map(s => ({ ...s, fromIndex: number(s.fromIndex), toIndex: number(s.toIndex), planned: load(s.planned), reserved: load(s.reserved), remaining: load(s.remaining) })) } }
@@ -26,3 +27,13 @@ export async function saveCapacityInquiry(offerNo: string, bookingNo: string | n
export async function commandCapacityInquiry(bookingNo: string, action: 'confirm' | 'cancel', body: InquiryCommand) { return inquiryResult(await post<InquiryResult>(`${inquiryPath(bookingNo)}/${action}`, body)) }
export const listOfferCustomers = (keyword?: string) => get<OfferCustomerOption[]>(`${base}/customer-options`, { keyword })
export async function listOfferQuotes(offerNo: string, query: TripBookingQuoteCriteria) { return normalizeFleetQuotePage(await get<WireQuotePage>(`${path(offerNo)}/quote-candidates`, query)) }
export async function getInquiryFulfillment(bookingNo: string) {
const value = await get<{ booking: import('./tripBookings').WireBooking; preview: { tripRecordVersion: number | string; recordVersion: number | string; incoming: import('./tripBookings').WireIncoming | null }; plannedDepartureAt: string }>(`${inquiryPath(bookingNo)}/fulfillment`)
return { booking: normalizeBooking(value.booking), plannedDepartureAt: value.plannedDepartureAt, preview: { ...value.preview, tripRecordVersion: number(value.preview.tripRecordVersion), recordVersion: number(value.preview.recordVersion), incoming: value.preview.incoming ? normalizeIncoming(value.preview.incoming) : null } }
}
export async function commandInquiryFulfillment(bookingNo: string, action: 'incoming' | 'receipt-confirmation' | 'arrange-dispatch', body: import('../bookingTypes').TripIncomingStart | import('../bookingTypes').TripIncomingAccept | import('../bookingTypes').TripBookingBind | import('../bookingTypes').TripBookingCommand) {
return normalizeBookingResult(await post<import('./tripBookings').WireResult>(`${inquiryPath(bookingNo)}/${action}`, body))
}
export async function saveAndConfirmInquiry(bookingNo: string, body: InquiryWrite) { return inquiryResult(await post<InquiryResult>(`${inquiryPath(bookingNo)}/save-and-confirm`, body)) }