Files
admin-web/src/modules/overseas-master-data/api/trips.ts
T

113 lines
10 KiB
TypeScript
Raw Normal View History

import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import { normalizeBooking, type WireBooking } from './tripBookings'
import type { PageResult } from './overseasMasterData'
import type {
FleetTrip, TripStatus, TripDetail, TripDraft, TripPlan, TripAssignment, TripBindingCommand, TripBinding,
TripCargo, TripLoad, TripMeasurement, TripConfirmedLoad, TripLoading, TripLoadConfirmation,
TripOutcome, TripCompletionInspection, TripComplete, TripResourceRelease, TripHistory, TripReviewState, TripReviewCommand, TripCapacityPlanning,
} from '../tripTypes'
const base = '/platform/v1/last-leg/fleet/trips'
const path = (tripNo: string) => `${base}/${encodeURIComponent(tripNo)}`
type Numeric = number | string
type WireTrip = Omit<FleetTrip, 'recordVersion' | 'maxPalletSpaces' | 'maxLoadKg' | 'maxVolumeCbm'> & {
recordVersion: Numeric; maxPalletSpaces: Numeric | null; maxLoadKg: Numeric | null; maxVolumeCbm: Numeric | null
}
type WireReview = Omit<TripReviewState, 'recordVersion'> & { recordVersion: Numeric | null }
type WireBinding = Omit<TripBinding, 'packageCount' | 'review'> & { packageCount: Numeric; review: WireReview }
type WireDetail = Omit<TripDetail, 'trip' | 'dispatches' | 'loadingReview'> & { trip: WireTrip; dispatches: WireBinding[]; loadingReview: WireReview }
type WirePage<T> = { records: T[]; page: Numeric; size: Numeric; total: Numeric; pages: Numeric }
type WireCargo = Omit<TripCargo, 'packages'> & { packages: Numeric }
type WireLoad = { [K in keyof TripLoad]: Numeric }
type WireMeasurement = Omit<TripMeasurement, 'palletSpaces' | 'weightKg' | 'volumeCbm' | 'deliveryIndex'> & WireLoad & { deliveryIndex: Numeric }
type WireConfirmed = Omit<TripConfirmedLoad, 'cargo' | 'pallets' | 'segments'> & {
cargo: WireCargo[]; pallets: WireMeasurement[]
segments: { fromIndex: Numeric; toIndex: Numeric; occupied: WireLoad; remaining: WireLoad }[]
}
type WireLoading = Omit<TripLoading, 'recordVersion' | 'cargo' | 'confirmedLoad'> & { recordVersion: Numeric; cargo: WireCargo[]; confirmedLoad: WireConfirmed | null }
type WirePlanning = Omit<TripCapacityPlanning, 'recordVersion' | 'cargo' | 'capacityPlan' | 'availableSegments'> & { recordVersion: Numeric; cargo: WireCargo[]; capacityPlan: WireConfirmed | null; availableSegments: WireConfirmed['segments'] }
type WireOutcome = Omit<TripOutcome, 'packages'> & { packages: Numeric }
type WireInspection = Omit<TripCompletionInspection, 'recordVersion' | 'outcomes'> & { recordVersion: Numeric; outcomes: WireOutcome[] }
type WireHistory = Omit<TripHistory, 'recordVersion' | 'snapshot'> & {
recordVersion: Numeric
snapshot: { externalBookings: WireBooking[]; capacityPlan: WireConfirmed | null; trip: WireTrip; dispatches: WireBinding[]; confirmedLoad: WireConfirmed | null; loadingReview: WireReview; completion: (Omit<NonNullable<TripHistory['snapshot']['completion']>, 'outcomes'> & { outcomes: WireOutcome[] }) | null }
}
function number(value: Numeric): number {
if (typeof value !== 'number' && typeof value !== 'string') throw new Error('车次接口缺少有效数值字段')
if (typeof value === 'string' && !value.trim()) throw new Error('车次接口返回了空的数值字段')
const result = Number(value)
if (!Number.isFinite(result) || result < 0) throw new Error('车次接口返回了无效数值')
return result
}
function integer(value: Numeric): number {
const result = number(value)
if (!Number.isSafeInteger(result)) throw new Error('车次接口返回了无效分页、数量或版本')
return result
}
const trip = (value: WireTrip): FleetTrip => ({ ...value, recordVersion: integer(value.recordVersion),
maxPalletSpaces: value.maxPalletSpaces == null ? null : integer(value.maxPalletSpaces),
maxLoadKg: value.maxLoadKg == null ? null : number(value.maxLoadKg), maxVolumeCbm: value.maxVolumeCbm == null ? null : number(value.maxVolumeCbm) })
const review = (value: WireReview): TripReviewState => ({ ...value, recordVersion: value.recordVersion == null ? null : integer(value.recordVersion) })
const binding = (value: WireBinding): TripBinding => ({ ...value, packageCount: integer(value.packageCount), review: review(value.review) })
const detail = (value: WireDetail): TripDetail => ({ ...value, trip: trip(value.trip), dispatches: value.dispatches.map(binding), loadingReview: review(value.loadingReview) })
const cargo = (value: WireCargo): TripCargo => ({ ...value, packages: integer(value.packages) })
const outcome = (value: WireOutcome): TripOutcome => ({ ...value, packages: integer(value.packages) })
const load = (value: WireLoad): TripLoad => ({ palletSpaces: integer(value.palletSpaces), weightKg: number(value.weightKg), volumeCbm: number(value.volumeCbm) })
function confirmed(value: WireConfirmed | null): TripConfirmedLoad | null {
return value == null ? null : { ...value, cargo: value.cargo.map(cargo),
pallets: value.pallets.map(row => ({ ...row, ...load(row), deliveryIndex: integer(row.deliveryIndex) })),
segments: value.segments.map(row => ({ fromIndex: integer(row.fromIndex), toIndex: integer(row.toIndex), occupied: load(row.occupied), remaining: load(row.remaining) })) }
}
const loading = (value: WireLoading): TripLoading => ({ ...value, recordVersion: integer(value.recordVersion), cargo: value.cargo.map(cargo), confirmedLoad: confirmed(value.confirmedLoad) })
function page<T, W>(value: WirePage<W>, map: (row: W) => T): PageResult<T> {
return { records: value.records.map(map), page: integer(value.page), size: integer(value.size), total: integer(value.total), pages: integer(value.pages) }
}
export interface TripQuery { page: number; size: number; keyword?: string; status?: TripStatus; warehouseId?: string }
export async function listTrips(query: TripQuery): Promise<PageResult<FleetTrip>> {
return page(unwrapApiResponse(await http.get<ApiEnvelope<WirePage<WireTrip>>, ApiEnvelope<WirePage<WireTrip>>>(base, { params: query })), trip)
}
export async function getTrip(tripNo: string): Promise<TripDetail> {
return detail(unwrapApiResponse(await http.get<ApiEnvelope<WireDetail>, ApiEnvelope<WireDetail>>(path(tripNo))))
}
async function postDetail(url: string, body: TripDraft | TripAssignment | TripBindingCommand | TripComplete | TripResourceRelease | TripReviewCommand | { recordVersion: number; reason: string }): Promise<TripDetail> {
return detail(unwrapApiResponse(await http.post<ApiEnvelope<WireDetail>, ApiEnvelope<WireDetail>>(url, body)))
}
export const createTrip = (body: TripDraft) => postDetail(base, body)
export const assignTrip = (tripNo: string, body: TripAssignment) => postDetail(`${path(tripNo)}/assignment`, body)
export const reviewTripDispatch = (tripNo: string, dispatchNo: string, body: TripReviewCommand) => postDetail(`${path(tripNo)}/dispatches/${encodeURIComponent(dispatchNo)}/review`, body)
export const reviewTripLoading = (tripNo: string, body: TripReviewCommand) => postDetail(`${path(tripNo)}/loading-review`, body)
export const bindTripDispatch = (tripNo: string, body: TripBindingCommand) => postDetail(`${path(tripNo)}/dispatches/bind`, body)
export const unbindTripDispatch = (tripNo: string, body: TripBindingCommand) => postDetail(`${path(tripNo)}/dispatches/unbind`, body)
export const cancelTrip = (tripNo: string, body: { recordVersion: number; reason: string }) => postDetail(`${path(tripNo)}/cancel`, body)
export const completeTrip = (tripNo: string, body: TripComplete) => postDetail(`${path(tripNo)}/complete`, body)
export const releaseTripResource = (tripNo: string, body: TripResourceRelease) => postDetail(`${path(tripNo)}/resources/release`, body)
export async function updateTripPlan(tripNo: string, body: TripPlan): Promise<TripDetail> {
return detail(unwrapApiResponse(await http.put<ApiEnvelope<WireDetail>, ApiEnvelope<WireDetail>>(`${path(tripNo)}/plan`, body)))
}
const planning = (value: WirePlanning): TripCapacityPlanning => ({ ...value, recordVersion: integer(value.recordVersion), cargo: value.cargo.map(cargo), capacityPlan: confirmed(value.capacityPlan), planValid: value.planValid === true, availableSegments: value.availableSegments.map(row => ({ fromIndex: integer(row.fromIndex), toIndex: integer(row.toIndex), occupied: load(row.occupied), remaining: load(row.remaining) })) })
export async function getTripCapacityPlan(tripNo: string): Promise<TripCapacityPlanning> {
return planning(unwrapApiResponse(await http.get<ApiEnvelope<WirePlanning>, ApiEnvelope<WirePlanning>>(`${path(tripNo)}/capacity-plan`)))
}
export async function confirmTripCapacityPlan(tripNo: string, body: TripLoadConfirmation): Promise<TripCapacityPlanning> {
return planning(unwrapApiResponse(await http.post<ApiEnvelope<WirePlanning>, ApiEnvelope<WirePlanning>>(`${path(tripNo)}/capacity-plan`, body)))
}
export async function getTripLoading(tripNo: string): Promise<TripLoading> {
return loading(unwrapApiResponse(await http.get<ApiEnvelope<WireLoading>, ApiEnvelope<WireLoading>>(`${path(tripNo)}/loading`)))
}
export async function confirmTripLoading(tripNo: string, body: TripLoadConfirmation): Promise<TripLoading> {
return loading(unwrapApiResponse(await http.post<ApiEnvelope<WireLoading>, ApiEnvelope<WireLoading>>(`${path(tripNo)}/loading`, body)))
}
export async function getTripCompletion(tripNo: string): Promise<TripCompletionInspection> {
const value = unwrapApiResponse(await http.get<ApiEnvelope<WireInspection>, ApiEnvelope<WireInspection>>(`${path(tripNo)}/completion`))
return { ...value, canComplete: value.canComplete === true, recordVersion: integer(value.recordVersion), outcomes: value.outcomes.map(outcome) }
}
export async function listTripHistory(tripNo: string, currentPage = 1, size = 20): Promise<PageResult<TripHistory>> {
const value = unwrapApiResponse(await http.get<ApiEnvelope<WirePage<WireHistory>>, ApiEnvelope<WirePage<WireHistory>>>(`${path(tripNo)}/history`, { params: { page: currentPage, size } }))
return page(value, row => ({ ...row, recordVersion: integer(row.recordVersion), snapshot: {
trip: trip(row.snapshot.trip), dispatches: row.snapshot.dispatches.map(binding), confirmedLoad: confirmed(row.snapshot.confirmedLoad),
externalBookings: row.snapshot.externalBookings.map(normalizeBooking), loadingReview: review(row.snapshot.loadingReview), capacityPlan: confirmed(row.snapshot.capacityPlan),
completion: row.snapshot.completion == null ? null : { ...row.snapshot.completion, outcomes: row.snapshot.completion.outcomes.map(outcome) },
} }))
}