fix(trade): keep exact grams when the input shows rounded display

Percent and cash conversions no longer lose precision by rounding the gram field to three decimals.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-08 14:56:14 +03:30
co-authored by Cursor
parent e9abc9840a
commit 5015fb2838
2 changed files with 69 additions and 15 deletions
+38
View File
@@ -65,6 +65,44 @@ export function formatTradeGramInputValue(grams?: number | null): string | undef
return value.toFixed(TRADE_GRAM_INPUT_DECIMALS)
}
/** متن اینپوت گرم — فقط نمایش سه‌رقمی؛ منبع حقیقت همان گرم دقیق است */
export function tradeGramInputDisplay(exactGrams?: number | string | null): string {
if (exactGrams === undefined || exactGrams === null || exactGrams === '') return ''
const n = Number(exactGrams)
if (!Number.isFinite(n) || n <= 0) return ''
return formatTradeGramInputValue(n) ?? ''
}
/** گرم دقیق از درصد کیف — بدون رند نمایشی اینپوت */
export function exactGoldFromWalletPercent(balance: number, percent: number): number {
if (!(Number(percent) > 0) || !(Number(balance) > 0)) return 0
return normalizeOrderGoldAmount((Number(balance) * Number(percent)) / 100)
}
/**
* اگر متن اینپوت همان نمایش سه‌رقمی مقدار دقیق فعلی باشد، دقیق را نگه می‌دارد.
* در غیر این صورت عدد تایپ‌شده منبع حقیقت می‌شود.
*/
export function goldFromGramInputDisplay(
raw: unknown,
exactGrams?: number | string | null
): number | undefined {
if (raw === '' || raw == null) return undefined
const text = String(raw).trim()
if (text === '') return undefined
const typed = Number(text)
if (!Number.isFinite(typed) || typed <= 0) return undefined
const exact = Number(exactGrams)
if (Number.isFinite(exact) && exact > 0) {
const shown = formatTradeGramInputValue(exact)
if (shown != null && text === shown) {
return normalizeOrderGoldAmount(exact)
}
}
return normalizeOrderGoldAmount(typed)
}
function feeRate() {
return Number(serviceFeePercentage.value)
}
+31 -15
View File
@@ -81,10 +81,10 @@
type="price" @onchange="calculateGold" />
</a-form-item>
<a-form-item :rules="[justNumber()]">
<BasicInput v-model:value="form.goldValue" :addon-before="weightUnit" :addon-bg-color="colors.primary"
<BasicInput v-model:value="goldValueInput" :addon-before="weightUnit" :addon-bg-color="colors.primary"
addon-text-color="white"
:place-holder="amountFieldLabel"
size="large" type="text" inputmode="decimal" @onchange="calculateCash" />
size="large" type="text" inputmode="decimal" />
</a-form-item>
<div v-if="cashValidationMessage" class="text-red-700 text-xs font-bold mt-2">
{{ cashValidationMessage }}
@@ -289,7 +289,9 @@ import {
calcTradeTargetCash,
buildTradeConfirmBreakdown,
formatTradeGramDisplay,
formatTradeGramInputValue,
tradeGramInputDisplay,
exactGoldFromWalletPercent,
goldFromGramInputDisplay,
loadServiceFeeFromSettings,
normalizeOrderGoldAmount,
serviceFeePercentage,
@@ -306,7 +308,7 @@ import {
resolveProductTradeIsBuy,
} from '@/utils/productTradeOpen'
import { formatCashWalletBalanceText, getWeightUnit } from '@/utils/productDealMethod'
import { formatProductWalletBalanceText, getCashWalletBalance, getProductInventoryQuantity } from '@/utils/productAssets'
import { formatProductWalletBalanceText, getCashWalletBalance } from '@/utils/productAssets'
import { useWalletAssets } from '@/composables/useWalletAssets'
import { usesQuantityTrade } from '@/utils/productCategories'
import { useSettingsStore } from '@/store/settingsStore'
@@ -367,11 +369,7 @@ const tradeProductLabel = computed(() => {
const amountFieldLabel = computed(() => `مقدار ${tradeProductLabel.value}`)
const productWalletBalance = computed(() =>
getProductInventoryQuantity(
assetsSummary.value?.inventory_cards ?? [],
props.ProductItem?.id,
walletStore?.walletInfo?.gold
)
Number(walletStore?.walletInfo?.gold ?? 0)
)
const productWalletBalanceText = computed(() =>
@@ -443,6 +441,24 @@ const displayTradeGoldAmount = computed(() =>
formatTradeGramDisplay(orderGoldAmount.value)
)
const goldValueInput = computed({
get() {
const display = tradeGramInputDisplay(form.value.goldValue)
return display === '' ? undefined : display
},
set(raw: string | number | null | undefined) {
const next = goldFromGramInputDisplay(raw, form.value.goldValue)
form.value.goldValue = next
if (next == null) {
form.value.goldCash = undefined
form.value.percentWallet = 0
inputLock.value = null
return
}
calculateCash(next)
},
})
const feeFormHint = computed(() =>
buildTradeConfirmBreakdown({
isBuy: isBuy.value,
@@ -634,9 +650,10 @@ function percentChange(val: number) {
form.value.goldCash = unitCashCalculator.value * val
calculateGold(form.value.goldCash)
} else {
form.value.goldValue = formatTradeGramInputValue(parseFloat(String(unitGoldCalculator.value * val)))
if (form.value.goldValue != null) {
calculateCash(Number(form.value.goldValue))
const grams = exactGoldFromWalletPercent(productWalletBalance.value, val)
form.value.goldValue = grams > 0 ? grams : undefined
if (grams > 0) {
calculateCash(grams)
}
}
} else {
@@ -646,8 +663,6 @@ function percentChange(val: number) {
}
}
const unitGoldCalculator = computed(() => productWalletBalance.value / 100)
const unitCashCalculator = computed(() => {
return cashWalletBalance.value / 100
})
@@ -701,7 +716,8 @@ function calculateGold(val: number) {
if (!unit || Number.isNaN(unit)) return
if (val) {
form.value.goldValue = formatTradeGramInputValue(calcGramFromLockedRial(val, unit, isBuy.value))
const grams = calcGramFromLockedRial(val, unit, isBuy.value)
form.value.goldValue = grams > 0 ? grams : undefined
} else if (val.toString() === '') {
form.value.goldValue = undefined
inputLock.value = null