feat(fleet): show authoritative trip receivables and receipts on demand

This commit is contained in:
2026-10-05 11:58:16 +08:00
parent 110c2e0be2
commit cb5f0d1bec
6 changed files with 112 additions and 2 deletions
@@ -0,0 +1,15 @@
import { beforeEach, expect, it, vi } from 'vitest'
import { http } from '@/shared/api/http'
import { getTripOperationsFinance } from './tripOperationsFinance'
vi.mock('@/shared/api/http', async original => ({ ...(await original<typeof import('@/shared/api/http')>()), http: { get: vi.fn() } }))
beforeEach(() => vi.clearAllMocks())
it('reads only the requested trip, separates currencies and keeps unknown receipts null', async () => {
vi.mocked(http.get).mockResolvedValue({ code: '200', data: { tripNo: 'TR/A', dispatchNos: ['DS-A'], receiptsVisible: true, attributionStatus: 'ATTRIBUTED', attributionReason: 'frozen', finance: { status: 'AVAILABLE', reason: 'authority', totals: [{ currency: 'CNY', receivableAmount: '0', receivedAmount: null }, { currency: 'USD', receivableAmount: '20.12', receivedAmount: '3.5' }], sources: [], documents: [] } } })
const result = await getTripOperationsFinance('TR/A')
expect(http.get).toHaveBeenCalledExactlyOnceWith('/platform/v1/last-leg/fleet/operations/TR%2FA/finance')
expect(result.finance.totals).toEqual([{ currency: 'CNY', receivableAmount: 0, receivedAmount: null }, { currency: 'USD', receivableAmount: 20.12, receivedAmount: 3.5 }])
})
it('rejects a response belonging to another trip', async () => {
vi.mocked(http.get).mockResolvedValue({ code: '200', data: { tripNo: 'OTHER' } })
await expect(getTripOperationsFinance('TR-A')).rejects.toThrow('车次不匹配')
})
@@ -0,0 +1,23 @@
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import { fleetMoney } from './fleetQuotes'
export interface TripFinanceMoney { currency: string; receivableAmount: number; receivedAmount: number | null }
export interface TripFinanceSource { customerId: string; businessNo: string; sourceNo: string | null; feeItemName: string; sourceVersion: number; currency: string; amount: number; status: string }
export interface TripFinanceDocument { billNo: string; currency: string; receivedAmount: number | null; allocationStatus: string; reason: string }
export interface TripFinanceResult {
tripNo: string; dispatchNos: string[]; receiptsVisible: boolean; attributionStatus: string; attributionReason: string
finance: { status: string; totals: TripFinanceMoney[]; sources: TripFinanceSource[]; documents: TripFinanceDocument[]; reason: string }
}
type Wire = Omit<TripFinanceResult, 'finance'> & { finance: Omit<TripFinanceResult['finance'], 'totals' | 'sources' | 'documents'> & {
totals: { currency: string; receivableAmount: string | number; receivedAmount: string | number | null }[]
sources: (Omit<TripFinanceSource, 'amount'> & { amount: string | number })[]
documents: (Omit<TripFinanceDocument, 'receivedAmount'> & { receivedAmount: string | number | null })[]
} }
export async function getTripOperationsFinance(tripNo: string): Promise<TripFinanceResult> {
const data = unwrapApiResponse(await http.get<ApiEnvelope<Wire>, ApiEnvelope<Wire>>(`/platform/v1/last-leg/fleet/operations/${encodeURIComponent(tripNo)}/finance`))
if (data.tripNo !== tripNo) throw new Error('财务响应车次不匹配')
return { ...data, finance: { ...data.finance,
totals: data.finance.totals.map(m => ({ ...m, receivableAmount: fleetMoney(m.receivableAmount), receivedAmount: m.receivedAmount == null ? null : fleetMoney(m.receivedAmount) })),
sources: data.finance.sources.map(s => ({ ...s, amount: fleetMoney(s.amount) })),
documents: data.finance.documents.map(d => ({ ...d, receivedAmount: d.receivedAmount == null ? null : fleetMoney(d.receivedAmount) })),
} }
}