From fabce6176af13cad3fe818626be0a67b685ad720 Mon Sep 17 00:00:00 2001 From: adonis Date: Mon, 10 Aug 2026 19:21:56 +0330 Subject: [PATCH] Price orders at the provider's real trade rate instead of the mid quote. montzar_live_price is the midpoint between the colleague's buy and sell rates, so orders priced straight from it charged the customer less than we pay the provider and the whole spread was lost on every trade, even though the app already showed the correct side. Order base price now follows the traded side (base + spread on buy, base - spread on sell), which also makes price_original the real cost basis so the dashboard stops counting the provider spread as profit. Conditional order triggers compare against the same displayed price. Co-authored-by: Cursor --- .../Single/SingleOrderController.php | 18 +++++---- app/Services/ConditionalOrderService.php | 16 ++++++-- app/Services/ConfigService.php | 16 ++++++++ app/Services/ProductDisplayPriceService.php | 39 +++++++++++++++++++ 4 files changed, 78 insertions(+), 11 deletions(-) diff --git a/app/Http/Controllers/Single/SingleOrderController.php b/app/Http/Controllers/Single/SingleOrderController.php index 2e8418d..32b5cbb 100644 --- a/app/Http/Controllers/Single/SingleOrderController.php +++ b/app/Http/Controllers/Single/SingleOrderController.php @@ -66,7 +66,11 @@ class SingleOrderController extends Controller $orderProductId = (int) ($data['abshode_id'] ?? $data['product_id'] ?? $config->mainProductId() ?? 0); $orderProduct = $orderProductId > 0 ? Product::find($orderProductId) : null; - $basePrice = app(ProductDisplayPriceService::class)->resolveOrderBasePrice($orderProduct); + $orderDirection = isset($data['direction']) ? (int) $data['direction'] : null; + + // پایه وسطِ نرخ خرید/فروش همکار است — سفارش با همان سمتی حساب شود که با همکار معامله می‌کنیم + $basePrice = app(ProductDisplayPriceService::class) + ->resolveOrderTradePrice($orderProduct, $orderDirection); if ($basePrice <= 0) { if ($config->isOutsideTradingHours()) { @@ -79,7 +83,7 @@ class SingleOrderController extends Controller $data['all_price'] = $latestPrice; $data['price'] = $basePrice; $data['price_id'] = $latestPrice?->id; - $data['difference'] = $config->orderPriceDiffRial(isset($data['direction']) ? (int) $data['direction'] : null); + $data['difference'] = $config->orderPriceDiffRial($orderDirection); $data['service_charge_percentage'] = $config->orderServiceChargePercentage(); $fee_free = Setting::where('key', 'fee_free')->where('is_active', true)->value('value'); @@ -125,7 +129,7 @@ class SingleOrderController extends Controller if (!empty($data['fee_free'])) { $data['service_charge_percentage'] = 0; } - $data['price_original'] = $this->resolveProductUnitBasePrice($data['product']); + $data['price_original'] = $this->resolveProductUnitBasePrice($data['product'], 1); $this->applyCashPriceDiff($config, $data['product'], 1, $data, (float) $data['price_original']); $data['total_fee'] = $data['number'] * $data['fee']; $data['total_price_original'] = $this->resolveProductOrderPricingAmount($data['product'], 1.0) @@ -146,7 +150,7 @@ class SingleOrderController extends Controller if (!empty($data['fee_free'])) { $data['service_charge_percentage'] = 0; } - $data['price_original'] = $this->resolveProductUnitBasePrice($data['product']); + $data['price_original'] = $this->resolveProductUnitBasePrice($data['product'], 2); $this->applyCashPriceDiff($config, $data['product'], 2, $data, (float) $data['price_original']); $data['total_fee'] = $data['number'] * $data['fee']; $data['total_price_original'] = $this->resolveProductOrderPricingAmount($data['product'], 1.0) @@ -1382,7 +1386,7 @@ class SingleOrderController extends Controller $base = (float) ($basePriceRial ?? 0); if ($base <= 0) { - $base = (float) $this->resolveProductUnitBasePrice($product); + $base = (float) $this->resolveProductUnitBasePrice($product, $direction); } if ($base <= 0) { $base = ProductPriceService::productBasePriceRial($product); @@ -1510,13 +1514,13 @@ class SingleOrderController extends Controller * - reading_method (مثل شمش zaarbox) → همان قیمت enrichشدهٔ لیست/UI، نه ستون کهنه/صفر DB * - بقیه → قیمت ذخیره‌شدهٔ محصول */ - private function resolveProductUnitBasePrice(Product $product): int + private function resolveProductUnitBasePrice(Product $product, ?int $direction = null): int { $display = app(ProductDisplayPriceService::class); // قیمت اپ یا فید مرجع — همان منبع نمایش؛ DB خام برای reading_method اغلب ۰/کهنه است if ($display->usesAppPrice($product) || $product->reading_method) { - return (int) round($display->resolveOrderBasePrice($product)); + return (int) round($display->resolveOrderTradePrice($product, $direction)); } return (int) (($product->price ?? 0) + ($product->price_profit ?? 0)); diff --git a/app/Services/ConditionalOrderService.php b/app/Services/ConditionalOrderService.php index a445f6d..543f69c 100644 --- a/app/Services/ConditionalOrderService.php +++ b/app/Services/ConditionalOrderService.php @@ -74,7 +74,7 @@ class ConditionalOrderService } $display = app(ProductDisplayPriceService::class); - $currentPrice = $this->resolveComparePrice($product, $liquidPrice, $display); + $currentPrice = $this->resolveComparePrice($product, $liquidPrice, $display, $config, (int) $order->direction); if ($currentPrice <= 0 || ! $order->conditionMet($currentPrice)) { return; } @@ -89,12 +89,20 @@ class ConditionalOrderService $this->executeOrder($order, $product, $currentPrice); } - private function resolveComparePrice(Product $product, int $liquidPrice, ProductDisplayPriceService $display): int - { + private function resolveComparePrice( + Product $product, + int $liquidPrice, + ProductDisplayPriceService $display, + ConfigService $config, + int $direction + ): int { if ($display->usesAppPrice($product)) { $base = $liquidPrice > 0 ? $liquidPrice : $display->resolveOrderBasePrice($product); - return max(0, (int) $base); + // مقایسه با همان قیمتی که مشتری در اپ می‌بیند و سفارش با آن ثبت می‌شود، نه پایهٔ وسط + $trade = $config->buildTradeDisplayPrices((float) $base, $config->priceProvider()); + + return max(0, (int) ($direction === 2 ? $trade['sell'] : $trade['buy'])); } $price = (int) ($product->price ?? 0); diff --git a/app/Services/ConfigService.php b/app/Services/ConfigService.php index 3329cdd..8c92b71 100644 --- a/app/Services/ConfigService.php +++ b/app/Services/ConfigService.php @@ -132,6 +132,22 @@ class ConfigService return $this->displayBuyPriceDiffRial(); } + /** + * اختلاف دوطرفهٔ نرخ همکار (نصف فاصلهٔ نرخ خرید و فروش همکار). + * + * پایهٔ ذخیره‌شده (montzar_live_price) وسط این دو نرخ است، پس نرخ واقعی معاملهٔ ما با همکار + * «پایه + این مقدار» برای خرید و «پایه − این مقدار» برای فروش است. + * فقط برای provider همکار معنا دارد؛ بقیه نرخ دوطرفه نمی‌دهند. + */ + public function providerTradeSpreadRial(): float + { + if ($this->priceProvider() !== 'montzar') { + return 0.0; + } + + return max(0.0, (float) $this->get('montzar_diff', 0)); + } + /** درصد کارمزد سفارش — fee_service_charge_percentage، service_charge_percentage، config/fee.php */ public function orderServiceChargePercentage(): float { diff --git a/app/Services/ProductDisplayPriceService.php b/app/Services/ProductDisplayPriceService.php index 54b5469..589f327 100644 --- a/app/Services/ProductDisplayPriceService.php +++ b/app/Services/ProductDisplayPriceService.php @@ -471,6 +471,45 @@ class ProductDisplayPriceService return $this->resolveHistoricalBasePrice(); } + /** + * قیمت پایهٔ سفارش برای یک جهت — با اختلاف واقعی نرخ همکار. + * پایه وسطِ نرخ خرید/فروش همکار است؛ سفارش باید با همان سمتی حساب شود که ما با همکار معامله می‌کنیم. + */ + public function resolveOrderTradePrice(?Product $product = null, ?int $direction = null): int + { + $base = (float) $this->resolveOrderBasePrice($product); + + return (int) $this->applyProviderTradeSpread($base, $product, $direction); + } + + /** + * خرید → پایه + اختلاف نرخ همکار، فروش → پایه − اختلاف. + * فقط روی قیمتی که از مظنهٔ اپ آمده؛ محصول با قیمت دستی/فید/فرمول دست نمی‌خورد. + * ceil مثل buildTradeDisplayPrices تا قیمت سفارش با قیمت نمایشی اپ یکی بماند. + */ + public function applyProviderTradeSpread( + float $basePrice, + ?Product $product = null, + ?int $direction = null + ): float { + if ($basePrice <= 0 || $direction === null) { + return $basePrice; + } + + if ($product instanceof Product && ! $this->usesAppPrice($product)) { + return $basePrice; + } + + $spread = app(ConfigService::class)->providerTradeSpreadRial(); + if ($spread <= 0) { + return $basePrice; + } + + return $direction === 2 + ? max(0.0, ceil($basePrice - $spread)) + : ceil($basePrice + $spread); + } + /** @param array $payload */ private function extractBaseFromPayload(array $payload): int {