All files / src/utils money.ts

100% Statements 22/22
93.75% Branches 15/16
100% Functions 6/6
100% Lines 21/21

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64                  37x 35x 35x   35x 35x 37x     3x             41x           3x     25x 25x 25x 25x 4x           4x   25x       25x         16x 16x 16x       3x    
/**
 * 四舍五入(HALF_UP,银行/保险佣金口径)。
 *
 * 不能直接用 `Math.round(value * factor)`:一方面 1.005 * 100 = 100.49999999999999
 * 会被舍成 1.00;另一方面 `Math.round(-100.5) === -100` 是向零取整,与 HALF_UP
 * 「远离零进位」的语义相反,负的调整项(otherAmount)会差一分钱。
 * 这里先取绝对值、用指数记数法移位('1.005e2' 精确等于 100.5)再按符号还原。
 */
export function roundHalfUp(value: number, decimals = 2): number {
  if (!Number.isFinite(value)) return 0
  const factor = 10 ** decimals
  const abs = Math.abs(value)
  // 移位用字符串指数,避开二进制浮点误差;超大数退化为乘法
  const shifted = Number(`${abs}e${decimals}`)
  const rounded = Number.isFinite(shifted) ? Math.round(shifted) : Math.round(abs * factor)
  return (value < 0 ? -rounded : rounded) / factor
}
 
const HKD = 'HKD'
 
/**
 * 币种归一:Intl.NumberFormat 对空串或非 3 位字母会抛 RangeError,
 * 后端 currency 缺失时会把整页渲染打断,故兜底到 HKD。
 */
function normalizeCurrency(currency: string | null | undefined): string {
  return typeof currency === 'string' && /^[A-Za-z]{3}$/.test(currency.trim())
    ? currency.trim().toUpperCase()
    : HKD
}
 
// Intl.NumberFormat 构造是慢路径,列表页每行要格式化 2~3 次,按 币种:小数位 缓存
const formatterCache = new Map<string, Intl.NumberFormat>()
 
function getFormatter(currency: string, decimals: number): Intl.NumberFormat {
  const code = normalizeCurrency(currency)
  const key = `${code}:${decimals}`
  let formatter = formatterCache.get(key)
  if (!formatter) {
    formatter = new Intl.NumberFormat('zh-HK', {
      style: 'currency',
      currency: code,
      minimumFractionDigits: decimals,
      maximumFractionDigits: decimals
    })
    formatterCache.set(key, formatter)
  }
  return formatter
}
 
export function formatMoney(value: number, currency: string, decimals = 2): string {
  return getFormatter(currency, decimals).format(roundHalfUp(value, decimals))
}
 
// 明细金额保留 4 位 HALF_UP;HKD 汇总展示 2 位
export function formatDetailMoney(value: number, currency: string): string {
  const code = normalizeCurrency(currency)
  const decimals = code === HKD ? 2 : 4
  return formatMoney(value, code, decimals)
}
 
export function formatHkd(value: number): string {
  return formatMoney(value, HKD, 2)
}