From b4a8546ce64ccd72200716cb013aec1907839fb2 Mon Sep 17 00:00:00 2001 From: mohammad sedighi Date: Sun, 20 Sep 2026 23:59:55 +0330 Subject: [PATCH] Stop applying special pricing twice on live quotes so sell stays below buy. Co-authored-by: Cursor --- src/models/home.ts | 6 +++++ src/models/products.ts | 2 ++ src/utils/productCashFee.ts | 29 ++++++++++++++++++--- src/utils/productPrice.ts | 51 ++++++++++++++++++++++++++++++++----- src/utils/specialPricing.ts | 7 +++++ 5 files changed, 84 insertions(+), 11 deletions(-) diff --git a/src/models/home.ts b/src/models/home.ts index c9f55c2..cac130f 100644 --- a/src/models/home.ts +++ b/src/models/home.ts @@ -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 } diff --git a/src/models/products.ts b/src/models/products.ts index 5d26584..4682868 100644 --- a/src/models/products.ts +++ b/src/models/products.ts @@ -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 diff --git a/src/utils/productCashFee.ts b/src/utils/productCashFee.ts index a76e1e7..ed109b0 100644 --- a/src/utils/productCashFee.ts +++ b/src/utils/productCashFee.ts @@ -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) } diff --git a/src/utils/productPrice.ts b/src/utils/productPrice.ts index 9db490f..e558bb3 100644 --- a/src/utils/productPrice.ts +++ b/src/utils/productPrice.ts @@ -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) } /** کارت فروشگاه: قیمت نمایشی صفر → «بدون قیمت» */ diff --git a/src/utils/specialPricing.ts b/src/utils/specialPricing.ts index b742ca3..b307df0 100644 --- a/src/utils/specialPricing.ts +++ b/src/utils/specialPricing.ts @@ -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>(payload: T): T { if (!payload || typeof payload !== 'object') return payload