Commit e926e5ca by lihu-001 Committed by GitHub

fix: 修复兑换码额度精度损失 (#6685)

* fix: 修复兑换码额度精度损失(#6680)

* fix(redemption): guard update data integrity
parent 1da23d6b
...@@ -19,13 +19,16 @@ For commercial licensing, please contact support@quantumnous.com ...@@ -19,13 +19,16 @@ For commercial licensing, please contact support@quantumnous.com
import type { TFunction } from 'i18next' import type { TFunction } from 'i18next'
import { z } from 'zod' import { z } from 'zod'
import { parseQuotaFromDollars, quotaUnitsToDollars } from '@/lib/format' import {
parseQuotaFromDollars,
quotaUnitsToEditableAmount,
} from '@/lib/format'
import { import {
REDEMPTION_VALIDATION, REDEMPTION_VALIDATION,
getRedemptionFormErrorMessages, getRedemptionFormErrorMessages,
} from '../constants' } from '../constants'
import { type RedemptionFormData, type Redemption } from '../types' import type { RedemptionFormData, Redemption } from '../types'
// ============================================================================ // ============================================================================
// Form Schema (use getRedemptionFormSchema(t) in components for i18n messages) // Form Schema (use getRedemptionFormSchema(t) in components for i18n messages)
...@@ -94,7 +97,7 @@ export function transformRedemptionToFormDefaults( ...@@ -94,7 +97,7 @@ export function transformRedemptionToFormDefaults(
): RedemptionFormValues { ): RedemptionFormValues {
return { return {
name: redemption.name, name: redemption.name,
quota_dollars: quotaUnitsToDollars(redemption.quota), quota_dollars: quotaUnitsToEditableAmount(redemption.quota),
expired_time: expired_time:
redemption.expired_time > 0 redemption.expired_time > 0
? new Date(redemption.expired_time * 1000) ? new Date(redemption.expired_time * 1000)
......
...@@ -244,6 +244,23 @@ function mergeOptions( ...@@ -244,6 +244,23 @@ function mergeOptions(
} }
} }
function getFractionDigits(
value: number,
digitsLarge: number,
digitsSmall: number
): number {
return Math.abs(value) >= 1 ? digitsLarge : digitsSmall
}
/** Return the configured fraction digits for a plain currency value. */
export function getCurrencyFractionDigits(
value: number,
options?: CurrencyFormatOptions
): number {
const merged = mergeOptions(options)
return getFractionDigits(value, merged.digitsLarge, merged.digitsSmall)
}
function removeTrailingZeros(str: string): string { function removeTrailingZeros(str: string): string {
if (!str.includes('.')) return str if (!str.includes('.')) return str
return str.replace(/(\.[0-9]*?)0+$/, '$1').replace(/\.$/, '') return str.replace(/(\.[0-9]*?)0+$/, '$1').replace(/\.$/, '')
...@@ -261,7 +278,7 @@ function formatNumberWithSuffix( ...@@ -261,7 +278,7 @@ function formatNumberWithSuffix(
return `${removeTrailingZeros(result.toFixed(1))}k` return `${removeTrailingZeros(result.toFixed(1))}k`
} }
const digits = abs >= 1 ? digitsLarge : digitsSmall const digits = getFractionDigits(value, digitsLarge, digitsSmall)
return removeTrailingZeros(value.toFixed(digits)) return removeTrailingZeros(value.toFixed(digits))
} }
...@@ -300,8 +317,11 @@ function formatCurrencyValue( ...@@ -300,8 +317,11 @@ function formatCurrencyValue(
) )
} }
const digits = const digits = getFractionDigits(
Math.abs(value) >= 1 ? options.digitsLarge : options.digitsSmall value,
options.digitsLarge,
options.digitsSmall
)
const adjustedValue = adjustForMinimum(value, digits, options.minimumNonZero) const adjustedValue = adjustForMinimum(value, digits, options.minimumNonZero)
if (meta.kind === 'currency') { if (meta.kind === 'currency') {
......
...@@ -22,6 +22,7 @@ import { ...@@ -22,6 +22,7 @@ import {
formatCurrencyFromUSD, formatCurrencyFromUSD,
formatQuotaWithCurrency, formatQuotaWithCurrency,
getCurrencyDisplay, getCurrencyDisplay,
getCurrencyFractionDigits,
} from './currency' } from './currency'
// ============================================================================ // ============================================================================
...@@ -104,16 +105,44 @@ export function parseQuotaFromDollars(amount: number): number { ...@@ -104,16 +105,44 @@ export function parseQuotaFromDollars(amount: number): number {
*/ */
export function quotaUnitsToDollars(units: number): number { export function quotaUnitsToDollars(units: number): number {
const { config, meta } = getCurrencyDisplay() const { config, meta } = getCurrencyDisplay()
return quotaUnitsToDisplayAmount(units, config.quotaPerUnit, meta)
}
function quotaUnitsToDisplayAmount(
units: number,
quotaPerUnit: number,
meta: ReturnType<typeof getCurrencyDisplay>['meta']
): number {
if (meta.kind === 'tokens') { if (meta.kind === 'tokens') {
return units return units
} }
const usdAmount = units / config.quotaPerUnit return (units / quotaPerUnit) * meta.exchangeRate
const exchangeRate = }
meta.kind === 'currency' || meta.kind === 'custom' ? meta.exchangeRate : 1
/**
* Convert quota units to a plain number suitable for an editable input.
* Uses the same precision as quota list formatting without symbols or suffixes.
*/
export function quotaUnitsToEditableAmount(units: number): number {
const { config, meta } = getCurrencyDisplay()
const amount = quotaUnitsToDisplayAmount(units, config.quotaPerUnit, meta)
if (meta.kind === 'tokens') {
return Math.round(amount)
}
return Number(amount.toFixed(getCurrencyFractionDigits(amount)))
}
/** Return the input step matching the configured editable quota precision. */
export function getEditableQuotaStep(): number {
const { meta } = getCurrencyDisplay()
if (meta.kind === 'tokens') {
return 1
}
return usdAmount * exchangeRate return 10 ** -getCurrencyFractionDigits(0)
} }
// ============================================================================ // ============================================================================
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment