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; 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 & { recordVersion: Numeric; details: Omit & { amount: Numeric; remark?: string | null } } type SummaryWire = Omit & { departureVersion?: Numeric | null; totals: Array & { directCost: Numeric; ownCost?: Numeric | null; externalCost?: Numeric | null; allocations: Array & { amount: Numeric; palletSpaces: Numeric }> }> } type PageWire = { 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(value: PageWire, transform: (row: T) => R): PageResult { 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 { type Wire = { page: PageWire; summary: SummaryWire } const result = unwrapApiResponse(await http.get, ApiEnvelope>(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 { return cost(unwrapApiResponse(await http.post, ApiEnvelope>(path(tripNo), { idempotencyKey, details }))) } export async function updateTripCost(tripNo: string, record: Pick, details: TripCostDraft, reason?: string): Promise { return cost(unwrapApiResponse(await http.put, ApiEnvelope>(`${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> { type HistoryWire = Omit & { recordVersion: Numeric; snapshot: { cost: CostWire; summary: SummaryWire } } type Wire = PageWire const result = unwrapApiResponse(await http.get, ApiEnvelope>(`${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) } })) }