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

65 lines
6.0 KiB
TypeScript
Raw Normal View History

import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import type { PageResult } from './overseasMasterData'
export type CostCategory = 'FUEL' | 'DRIVER' | 'TOLL' | 'PARKING' | 'OTHER'
export type CostCurrency = 'CNY' | 'USD'
export interface TripCostDraft { category: CostCategory; currency: CostCurrency; amount: number; incurredAt: string; remark: string | null }
export interface TripCost { costNo: string; tripNo: string; details: TripCostDraft; recordVersion: string; updatedAt: string }
export interface CostAllocation { businessNo: string; sourceType: 'OWN' | 'EXTERNAL'; dispatchNo: string; palletSpaces: number; amount: number }
export interface CostTotal { currency: CostCurrency; directCost: number; allocations: CostAllocation[]; ownCost: number | null; externalCost: number | null }
export interface CostSummary { departureVersion: string | null; manifestHash: string | null; allocationMethod: string | null; allocationBlockReason: string | null; totals: CostTotal[] }
export interface TripCosts { page: PageResult<TripCost>; summary: CostSummary }
export interface TripCostHistory { recordVersion: string; action: string; reason: string; occurredAt: string; snapshot: { cost: TripCost; summary: CostSummary } }
type Numeric = string | number
type CostWire = Omit<TripCost, 'recordVersion' | 'details'> & { recordVersion: Numeric; details: Omit<TripCostDraft, 'amount' | 'remark'> & { amount: Numeric; remark?: string | null } }
type SummaryWire = Omit<CostSummary, 'departureVersion' | 'totals'> & { departureVersion?: Numeric | null; totals: Array<Omit<CostTotal, 'directCost' | 'ownCost' | 'externalCost' | 'allocations'> & {
directCost: Numeric; ownCost?: Numeric | null; externalCost?: Numeric | null; allocations: Array<Omit<CostAllocation, 'amount' | 'palletSpaces'> & { amount: Numeric; palletSpaces: Numeric }>
}> }
type PageWire<T> = { records: T[]; page: Numeric; size: Numeric; total: Numeric; pages: Numeric }
const path = (tripNo: string) => `/platform/v1/last-leg/fleet/trips/${encodeURIComponent(tripNo)}/costs`
function decimal(value: Numeric): number {
if ((typeof value !== 'string' && typeof value !== 'number') || !/^\d+(\.\d{1,2})?$/.test(String(value))) throw new Error('车次成本接口返回了无效金额')
const [whole, fraction = ''] = String(value).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)
}
function integer(value: Numeric): number {
const number = Number(value)
if ((typeof value !== 'string' && typeof value !== 'number') || !/^\d+$/.test(String(value))) throw new Error('车次成本接口返回了无效分页或托位')
if (!Number.isSafeInteger(number)) throw new Error('车次成本接口返回了无效分页或托位')
return number
}
function version(value: Numeric): string {
if (!/^(0|[1-9]\d*)$/.test(String(value)) || (typeof value === 'number' && !Number.isSafeInteger(value))) throw new Error('车次成本版本无效')
return String(value)
}
function cost(value: CostWire): TripCost { return { ...value, recordVersion: version(value.recordVersion), details: { ...value.details, amount: decimal(value.details.amount), remark: value.details.remark ?? null } } }
function summary(value: SummaryWire): CostSummary {
return { departureVersion: value.departureVersion == null ? null : version(value.departureVersion), manifestHash: value.manifestHash ?? null,
allocationMethod: value.allocationMethod ?? null, allocationBlockReason: value.allocationBlockReason ?? null,
totals: value.totals.map(row => ({ ...row, directCost: decimal(row.directCost), ownCost: row.ownCost == null ? null : decimal(row.ownCost),
externalCost: row.externalCost == null ? null : decimal(row.externalCost),
allocations: row.allocations.map(part => ({ ...part, amount: decimal(part.amount), palletSpaces: integer(part.palletSpaces) })) })) }
}
function page<T, R>(value: PageWire<T>, transform: (row: T) => R): PageResult<R> {
return { records: value.records.map(transform), page: integer(value.page), size: integer(value.size), total: integer(value.total), pages: integer(value.pages) }
}
export async function listTripCosts(tripNo: string, currentPage = 1, size = 10): Promise<TripCosts> {
type Wire = { page: PageWire<CostWire>; summary: SummaryWire }
const result = unwrapApiResponse(await http.get<ApiEnvelope<Wire>, ApiEnvelope<Wire>>(path(tripNo), { params: { page: currentPage, size } }))
return { page: page(result.page, cost), summary: summary(result.summary) }
}
export async function createTripCost(tripNo: string, idempotencyKey: string, details: TripCostDraft): Promise<TripCost> {
return cost(unwrapApiResponse(await http.post<ApiEnvelope<CostWire>, ApiEnvelope<CostWire>>(path(tripNo), { idempotencyKey, details })))
}
export async function updateTripCost(tripNo: string, record: Pick<TripCost, 'costNo' | 'recordVersion'>, details: TripCostDraft, reason?: string): Promise<TripCost> {
return cost(unwrapApiResponse(await http.put<ApiEnvelope<CostWire>, ApiEnvelope<CostWire>>(`${path(tripNo)}/${encodeURIComponent(record.costNo)}`, { recordVersion: record.recordVersion, details, reason: reason?.trim() || '更正本趟直接成本' })))
}
export async function listTripCostHistory(tripNo: string, costNo: string, currentPage = 1, size = 10): Promise<PageResult<TripCostHistory>> {
type HistoryWire = Omit<TripCostHistory, 'recordVersion' | 'snapshot'> & { recordVersion: Numeric; snapshot: { cost: CostWire; summary: SummaryWire } }
type Wire = PageWire<HistoryWire>
const result = unwrapApiResponse(await http.get<ApiEnvelope<Wire>, ApiEnvelope<Wire>>(`${path(tripNo)}/${encodeURIComponent(costNo)}/history`, { params: { page: currentPage, size } }))
return page(result, row => ({ ...row, recordVersion: version(row.recordVersion), snapshot: { cost: cost(row.snapshot.cost), summary: summary(row.snapshot.summary) } }))
}