import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http' import { normalizeFleetQuoteSource, normalizeFleetQuotePage, type WireSnapshot, type WireQuotePage } from './fleetQuoteCandidates' import type { FleetCustomerOption, TripBooking, TripBookingCargo, TripBookingCreate, TripBookingUpdate, TripBookingCommand, TripBookingResult, TripBookingList, TripIncomingEvidence, TripIncomingPreview, TripIncomingStart, TripIncomingAccept, TripBookingBind, TripAcceptedReceipt } from '../bookingTypes' const base = '/platform/v1/last-leg/fleet/trips' const path = (tripNo: string) => `${base}/${encodeURIComponent(tripNo)}/bookings` type N = number | string type WireCargo = Omit & { packageCount: N | null; palletSpaces: N | null; weightKg: N | null; volumeCbm: N | null; unitPrice: N | null } export type WireBooking = Omit & { expectedPalletCount: N | null; recordVersion: N; agreedAmount: N | null; cargo: WireCargo; receipt: WireReceipt | null; quoteSource?: WireSnapshot | null } type WireIncoming = Omit & { receivedPackages: N; pallets: (Omit & { packages: N; weightKg: N | null; volumeCbm: N | null })[] } type WireReceipt = Omit & { source: WireIncoming; pallets: { warehousePalletId: string; palletSpaces: N }[] } type WireResult = { tripRecordVersion: N; booking: WireBooking; replayed: boolean } const number = (value: N) => { const result = Number(value); if (value == null || String(value).trim() === '' || !Number.isFinite(result) || result < 0) throw new Error('外接预订数值无效'); return result } const integer = (value: N) => { const result = number(value); if (!Number.isSafeInteger(result)) throw new Error('外接预订版本或数量无效'); return result } const nullable = (value: N | null) => value == null ? null : number(value) const incoming = (value: WireIncoming): TripIncomingEvidence => ({ ...value, receivedPackages: integer(value.receivedPackages), pallets: value.pallets.map(row => ({ ...row, packages: integer(row.packages), weightKg: nullable(row.weightKg), volumeCbm: nullable(row.volumeCbm) })) }) export const normalizeBooking = (value: WireBooking): TripBooking => ({ ...value, expectedPalletCount: value.expectedPalletCount == null ? null : integer(value.expectedPalletCount), quoteSource: value.quoteSource == null ? null : normalizeFleetQuoteSource(value.quoteSource), receipt: value.receipt == null ? null : { ...value.receipt, source: incoming(value.receipt.source), pallets: value.receipt.pallets.map(row => ({ ...row, palletSpaces: integer(row.palletSpaces) })) }, recordVersion: integer(value.recordVersion), agreedAmount: nullable(value.agreedAmount), cargo: { ...value.cargo, packageCount: value.cargo.packageCount == null ? null : integer(value.cargo.packageCount), palletSpaces: value.cargo.palletSpaces == null ? null : integer(value.cargo.palletSpaces), weightKg: nullable(value.cargo.weightKg), volumeCbm: nullable(value.cargo.volumeCbm), unitPrice: nullable(value.cargo.unitPrice) } }) const result = (value: WireResult): TripBookingResult => ({ tripRecordVersion: integer(value.tripRecordVersion), booking: normalizeBooking(value.booking), replayed: value.replayed === true }) export interface TripBookingQuoteCriteria { customerId: string; destinationKey: string; currency: 'CNY' | 'USD'; priceBasis: 'PALLET' | 'ORDER'; page?: number; size?: number } export async function listTripBookingQuoteCandidates(tripNo: string, criteria: TripBookingQuoteCriteria) { return normalizeFleetQuotePage(unwrapApiResponse(await http.get, ApiEnvelope>(`${path(tripNo)}/quote-candidates`, { params: criteria }))) } export async function listFleetCustomers(keyword?: string): Promise { return unwrapApiResponse(await http.get, ApiEnvelope>(`${base}/customer-options`, { params: { keyword } })) } export async function listTripBookings(tripNo: string): Promise { const value = unwrapApiResponse(await http.get, ApiEnvelope<{ tripRecordVersion: N; records: WireBooking[] }>>(path(tripNo))) return { tripRecordVersion: integer(value.tripRecordVersion), records: value.records.map(normalizeBooking) } } export async function createTripBooking(tripNo: string, body: TripBookingCreate): Promise { return result(unwrapApiResponse(await http.post, ApiEnvelope>(path(tripNo), body))) } export async function updateTripBooking(tripNo: string, bookingNo: string, body: TripBookingUpdate): Promise { return result(unwrapApiResponse(await http.put, ApiEnvelope>(`${path(tripNo)}/${encodeURIComponent(bookingNo)}`, body))) } async function command(tripNo: string, bookingNo: string, action: string, body: TripBookingCommand): Promise { return result(unwrapApiResponse(await http.post, ApiEnvelope>(`${path(tripNo)}/${encodeURIComponent(bookingNo)}/${action}`, body))) } export const confirmTripBooking = (tripNo: string, bookingNo: string, body: TripBookingCommand) => command(tripNo, bookingNo, 'confirm', body) export const cancelTripBooking = (tripNo: string, bookingNo: string, body: TripBookingCommand) => command(tripNo, bookingNo, 'cancel', body) const bookingPath = (tripNo: string, bookingNo: string) => `${path(tripNo)}/${encodeURIComponent(bookingNo)}` export async function getTripIncoming(tripNo: string, bookingNo: string): Promise { const value = unwrapApiResponse(await http.get, ApiEnvelope<{ tripRecordVersion: N; recordVersion: N; incoming: WireIncoming | null }>>(`${bookingPath(tripNo, bookingNo)}/incoming`)) return { tripRecordVersion: integer(value.tripRecordVersion), recordVersion: integer(value.recordVersion), incoming: value.incoming == null ? null : incoming(value.incoming) } } async function incomingCommand(tripNo: string, bookingNo: string, action: string, body: TripIncomingStart | TripIncomingAccept | TripBookingBind): Promise { return result(unwrapApiResponse(await http.post, ApiEnvelope>(`${bookingPath(tripNo, bookingNo)}/${action}`, body))) } export const startTripIncoming = (tripNo: string, bookingNo: string, body: TripIncomingStart) => incomingCommand(tripNo, bookingNo, 'incoming', body) export const acceptTripIncoming = (tripNo: string, bookingNo: string, body: TripIncomingAccept) => incomingCommand(tripNo, bookingNo, 'receipt-confirmation', body) export const bindTripBookingDispatch = (tripNo: string, bookingNo: string, body: TripBookingBind) => incomingCommand(tripNo, bookingNo, 'dispatch', body)