Stop applying special pricing twice on live quotes so sell stays below buy.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-20 23:59:55 +03:30
co-authored by Cursor
parent 3b83eac099
commit b4a8546ce6
5 changed files with 84 additions and 11 deletions
+6
View File
@@ -35,4 +35,10 @@ export interface Price {
/** montzar | zaarbox | main_product | store = مظنه زنده، database_last = آخرین قیمت ذخیره‌شده */
price_source?: string | null
last_historical?: number
special_pricing?: {
package_id?: number
title?: string
profit_buy?: number
profit_sell?: number
} | null
}
+2
View File
@@ -23,6 +23,8 @@ export interface Product {
cash_fee_sell: number | undefined
cash_fee_buy_is_percent?: boolean | null
cash_fee_sell_is_percent?: boolean | null
special_profit_buy?: number | null
special_profit_sell?: number | null
order_limit_enabled?: boolean
min_order_cash?: number | null
max_order_cash?: number | null
+25 -4
View File
@@ -3,6 +3,13 @@ export type ProductCashFeeSource = {
cash_fee_sell?: number | null
cash_fee_buy_is_percent?: boolean | null
cash_fee_sell_is_percent?: boolean | null
special_profit_buy?: number | null
special_profit_sell?: number | null
}
export type ProductCashFeeOptions = {
/** پیش‌فرض true. اگر مظنهٔ last از قبل special_pricing دارد باید false باشد. */
includeSpecial?: boolean
}
/** 0.5 => 0.5% ، 5 => 5% — همیشه عدد درصد */
@@ -42,23 +49,37 @@ export function resolveProductCashFeeRial(
return Math.round(Math.abs(base * resolveCashFeePercentRatio(raw)))
}
export function resolveProductCashFeeBuyRial(product: ProductCashFeeSource, basePriceRial: number) {
export function resolveProductCashFeeBuyRial(
product: ProductCashFeeSource,
basePriceRial: number,
options?: ProductCashFeeOptions,
) {
const baseFee = resolveProductCashFeeRial(
product.cash_fee_buy,
product.cash_fee_buy_is_percent,
basePriceRial,
)
const special = Number((product as any).special_profit_buy ?? 0)
if (options?.includeSpecial === false) {
return baseFee
}
const special = Number(product.special_profit_buy ?? 0)
return baseFee + (Number.isFinite(special) ? Math.round(special) : 0)
}
export function resolveProductCashFeeSellRial(product: ProductCashFeeSource, basePriceRial: number) {
export function resolveProductCashFeeSellRial(
product: ProductCashFeeSource,
basePriceRial: number,
options?: ProductCashFeeOptions,
) {
const baseFee = resolveProductCashFeeRial(
product.cash_fee_sell,
product.cash_fee_sell_is_percent,
basePriceRial,
)
const special = Number((product as any).special_profit_sell ?? 0)
if (options?.includeSpecial === false) {
return baseFee
}
const special = Number(product.special_profit_sell ?? 0)
return baseFee + (Number.isFinite(special) ? Math.round(special) : 0)
}
+44 -7
View File
@@ -10,6 +10,7 @@ import {
usesQuantityTrade,
} from '@/utils/productCategories'
import { isProductTradingHoursOpen } from '@/utils/productTradeOpen'
import { lastPriceIncludesSpecial } from '@/utils/specialPricing'
/**
* قیمت‌گیری محصول — هم‌تراز با khaneghi_user:
@@ -24,6 +25,42 @@ function hasReferenceReadingMethod(product: Product): boolean {
return Boolean(product.reading_method)
}
/** پایه از buy/sell مظنه آمده — special همان‌جا اعمال شده و نباید دوباره جمع شود */
function productUsesLiveBoardQuote(product: Product, side: 'buy' | 'sell'): boolean {
if (isColleaguePriceProduct(product)) {
const stored = side === 'sell'
? Number(product.sellPrice ?? product.price ?? 0)
: Number(product.price ?? 0)
if (stored > 0) {
return false
}
const live = side === 'buy' ? getTradeBuyPrice(lastPrice.value) : getTradeSellPrice(lastPrice.value)
return isMeltedTradeGroup(product.group_type) && live > 0
}
if (isMeltedProductUsingAppPrice(product) || isAppPriceProduct(product)) {
const live = side === 'buy' ? getTradeBuyPrice(lastPrice.value) : getTradeSellPrice(lastPrice.value)
return live > 0
}
return false
}
function cashFeeOptions(product: Product, side: 'buy' | 'sell') {
if (lastPriceIncludesSpecial(lastPrice.value) && productUsesLiveBoardQuote(product, side)) {
return { includeSpecial: false as const }
}
return undefined
}
function buyCashFee(product: Product, base: number) {
return resolveProductCashFeeBuyRial(product, base, cashFeeOptions(product, 'buy'))
}
function sellCashFee(product: Product, base: number) {
return resolveProductCashFeeSellRial(product, base, cashFeeOptions(product, 'sell'))
}
/** قیمت منبع همکار؛ اگر خالی بود آبشده همان مظنهٔ صفحه اصلی را می‌گیرد */
function getColleagueSourcedUnitPrice(product: Product, live: number, apiPrice = Number(product.price ?? 0)): number {
if (apiPrice > 0) {
@@ -137,7 +174,7 @@ export function getProductBuyPrice(product?: Product | null): number {
return 0
}
const fee = resolveProductCashFeeBuyRial(product, base)
const fee = buyCashFee(product, base)
const withFee = base + fee
if (isColleaguePriceProduct(product)) {
@@ -216,28 +253,28 @@ export function getProductSellPrice(product?: Product | null): number {
}
if (isColleaguePriceProduct(product)) {
return base - resolveProductCashFeeSellRial(product, base)
return base - sellCashFee(product, base)
}
if (isAppPriceProduct(product)) {
const withFee = base - resolveProductCashFeeSellRial(product, base)
const withFee = base - sellCashFee(product, base)
return withFee * productPriceAmountFactor(product)
}
if (isMeltedProductUsingAppPrice(product) || isMeltedTradeGroup(product.group_type)) {
return base - resolveProductCashFeeSellRial(product, base)
return base - sellCashFee(product, base)
}
if (usesQuantityTrade(product) && hasProductUnitWeight(product)) {
const withFee = base - resolveProductCashFeeSellRial(product, base)
const withFee = base - sellCashFee(product, base)
return withFee * productPriceAmountFactor(product)
}
if (usesQuantityTrade(product)) {
return base - resolveProductCashFeeSellRial(product, base)
return base - sellCashFee(product, base)
}
return base - resolveProductCashFeeSellRial(product, base)
return base - sellCashFee(product, base)
}
/** کارت فروشگاه: قیمت نمایشی صفر → «بدون قیمت» */
+7
View File
@@ -48,6 +48,13 @@ export function getSpecialPricingState() {
return state
}
/** مظنهٔ last از قبل اختلاف ویژه دارد (سرور یا applyBoardSpecial) */
export function lastPriceIncludesSpecial(
payload?: { special_pricing?: unknown } | null,
): boolean {
return Boolean(payload && typeof payload === 'object' && payload.special_pricing)
}
/** اگر سرور قبلاً special_pricing نزده، اختلاف تابلو را روی last اعمال کن */
export function applyBoardSpecialToLastPayload<T extends Record<string, any>>(payload: T): T {
if (!payload || typeof payload !== 'object') return payload