export type FeeRowLike = { feeType?: string type?: string currency?: string amount?: number costItem?: string chargingStage?: string } export function normalizeFeeType(row: FeeRowLike): 'receivable' | 'payable' | '' { const raw = String(row?.feeType ?? row?.type ?? '').toLowerCase() if (raw === 'receivable' || raw === 'payable') return raw return '' } function getChargingStageRank(stage?: string) { const s = String(stage ?? '').toLowerCase() if (s === 'outbound') return 3 if (s === 'inbound') return 2 if (s === 'forecast') return 1 return 0 } /** * 每个费用类型 + 费用项只保留最高计费阶段(出库 > 入库 > 预报), * 避免预报与入库明细叠算进合计。 */ export function filterEffectiveStageFees(rows: T[]): T[] { const winners = new Map() for (const row of rows) { const feeType = normalizeFeeType(row) || 'unknown' const costItem = String(row?.costItem ?? '').trim() || '_' const key = `${feeType}|${costItem}` const current = winners.get(key) if (!current) { winners.set(key, row) continue } const rowStage = String((row as any)?.chargingStage ?? (row as any)?.ChargingStage ?? '') const curStage = String((current as any)?.chargingStage ?? (current as any)?.ChargingStage ?? '') if (getChargingStageRank(rowStage) > getChargingStageRank(curStage)) { winners.set(key, row) } } return [...winners.values()] } export function sumFeesByCurrency(list: FeeRowLike[]) { const totals = new Map() for (const row of list) { const currency = String(row?.currency ?? 'CNY').trim() || 'CNY' const amount = Number(row?.amount) if (!Number.isFinite(amount)) continue totals.set(currency, (totals.get(currency) ?? 0) + amount) } return totals } export function calcGrossProfitTotals( receivable: Map, payable: Map, ) { const currencies = new Set([...receivable.keys(), ...payable.keys()]) const result = new Map() for (const currency of currencies) { result.set(currency, (receivable.get(currency) ?? 0) - (payable.get(currency) ?? 0)) } return result } export function buildFeeTotalsFromSummaryItems( items: Array<{ feeType?: string; currency?: string; totalAmount?: number }>, feeType: 'receivable' | 'payable', ) { const totals = new Map() for (const item of items) { if (normalizeFeeType({ feeType: item.feeType }) !== feeType) continue const currency = String(item.currency ?? 'CNY').trim() || 'CNY' const amount = Number(item.totalAmount) if (!Number.isFinite(amount)) continue totals.set(currency, (totals.get(currency) ?? 0) + amount) } return totals } export function formatFeeTotals(totals: Map) { if (!totals.size) return '--' return [...totals.entries()] .map(([currency, amount]) => `${currency} ${amount.toFixed(2)}`) .join(' + ') } export function formatGrossProfitTotals(totals: Map) { if (!totals.size) return '--' return [...totals.entries()] .map(([currency, amount]) => { const sign = amount > 0 ? '+' : '' return `${sign}${currency} ${amount.toFixed(2)}` }) .join(' + ') }