reading_method) { return false; } if ((int) ($product->direct_price ?? 0) === 1) { return false; } return $this->isMeltedTradeProduct($product); } /** * پر کردن price روی مدل‌ها برای پاسخ JSON — بدون UPDATE دیتابیس */ public function enrichForApi(Collection|iterable $products): void { $collection = collect($products); $zaarbox = app(ZaarboxPriceService::class); $zaarboxFeed = $zaarbox->getCachedPriceListOnly(); $baseAppPrice = 0; if ($collection->contains(fn ($p) => $p instanceof Product && $this->usesAppPrice($p))) { $baseAppPrice = $this->resolveAppBasePrice(); } foreach ($collection as $product) { if (!$product instanceof Product) { continue; } if ($this->usesAppPrice($product)) { $this->applyAppPrice($product, $baseAppPrice); continue; } if ($product->reading_method && is_array($zaarboxFeed)) { $this->applyReferenceFeedPrice($product, $zaarboxFeed, $zaarbox); } } } private function applyAppPrice(Product $product, int $basePrice): void { $storedPrice = (int) ($product->price ?? 0); $useLast = Product::hasUseLastPriceIfZeroColumn() && ($product->use_last_price_if_zero ?? false); if ($basePrice <= 0) { if ($useLast && $storedPrice > 0) { return; } $product->setAttribute('price', 0); if (Schema::hasColumn($product->getTable(), 'price_profit')) { $product->setAttribute('price_profit', 0); } return; } $offset = Product::hasPriceOffsetColumn() ? (float) ($product->price_offset_percent ?? 0) : 0.0; $computed = ProductPriceService::adjustedTotal($basePrice, 0, $offset); if ($computed <= 0) { if ($useLast && $storedPrice > 0) { return; } $product->setAttribute('price', 0); if (Schema::hasColumn($product->getTable(), 'price_profit')) { $product->setAttribute('price_profit', 0); } return; } $product->setAttribute('price', $computed); if (Schema::hasColumn($product->getTable(), 'price_profit')) { $product->setAttribute('price_profit', 0); } } /** @param array> $feed */ private function applyReferenceFeedPrice(Product $product, array $feed, ZaarboxPriceService $zaarbox): void { $row = $zaarbox->findFeedRow($feed, (string) $product->reading_method); if ($row === null) { return; } $computed = $zaarbox->computeAdjustedProductPrice($product, $row); if ($computed <= 0) { if ( Product::hasUseLastPriceIfZeroColumn() && ($product->use_last_price_if_zero ?? false) && (int) ($product->price ?? 0) > 0 ) { return; } return; } $product->setAttribute('price', $computed); if (Schema::hasColumn($product->getTable(), 'price_profit')) { $product->setAttribute('price_profit', 0); } } /** @param array $payload */ public function cacheLivePricePayload(array $payload): void { Cache::put(self::LIVE_PRICE_CACHE_KEY, [ 'price' => (int) ($payload['price'] ?? 0), 'buy' => (int) ($payload['buy'] ?? 0), 'sell' => (int) ($payload['sell'] ?? 0), 'price_last' => (int) ($payload['price_last'] ?? 0), ], now()->addSeconds(self::LIVE_PRICE_CACHE_SECONDS)); } public function resolveAppBasePrice(): int { return $this->extractBaseFromPayload($this->livePricePayload()); } /** @param array $payload */ private function extractBaseFromPayload(array $payload): int { $price = (int) ($payload['price'] ?? 0); if ($price > 0) { return $price; } $buy = (int) ($payload['buy'] ?? 0); $sell = (int) ($payload['sell'] ?? 0); if ($buy > 0 && $sell > 0) { return (int) round(($buy + $sell) / 2); } $priceLast = (int) ($payload['price_last'] ?? 0); if ($priceLast > 0) { return $priceLast; } return 0; } /** @return array{price: int, buy: int, sell: int, price_last: int} */ private function livePricePayload(): array { $cached = Cache::get(self::LIVE_PRICE_CACHE_KEY); if (is_array($cached)) { return $cached; } return $this->resolveFallbackPayload(); } /** @return array{price: int, buy: int, sell: int, price_last: int} */ private function resolveFallbackPayload(): array { $config = app(ConfigService::class); $provider = $config->priceProvider(); $price = 0; if ($provider === 'montzar') { $price = $config->getInt('montzar_live_price', 0); } elseif ($provider === 'zaarbox') { $price = $this->resolveZaarboxBoardPriceFromCache($config); } elseif ($provider === 'main_product') { $main = $config->mainProduct(); if ($main) { $price = (int) (($main->price ?? 0) + ($main->price_profit ?? 0)); } } elseif ($provider === 'taban_gohar') { $price = $config->getInt('taban_gohar_live_price', 0); } if ($price <= 0 && $config->getBool('show_last_price', false)) { $last = Price::orderByDesc('id')->first(); if ($last && (float) $last->price > 0) { $price = (int) $last->price; } } $diffBuy = (int) $config->displayBuySpreadRial(); $diffSell = (int) $config->displaySellSpreadRial(); $payload = [ 'price' => $price, 'buy' => $price > 0 ? $price + $diffBuy : 0, 'sell' => $price > 0 ? max(0, $price - $diffSell) : 0, 'price_last' => $price > 0 ? $price + $diffBuy : 0, ]; if ($this->extractBaseFromPayload($payload) > 0) { $this->cacheLivePricePayload($payload); } return $payload; } private function resolveZaarboxBoardPriceFromCache(ConfigService $config): int { $identifier = trim($config->getString('zaarbox_price_key', '')); if ($identifier === '') { return 0; } $feed = app(ZaarboxPriceService::class)->getCachedPriceListOnly(); if (!is_array($feed)) { return 0; } $row = app(ZaarboxPriceService::class)->findFeedRow($feed, $identifier); if ($row === null) { return 0; } $row = app(ZaarboxPriceService::class)->normalizeFeedRow($row); return (int) round((float) ($row['price'] ?? 0) + (float) ($row['profit'] ?? 0)); } private function isMeltedTradeProduct(Product $product): bool { $group = trim((string) ($product->group_type ?? '')); return in_array($group, ['آبشده', 'کاتد مس'], true); } /** * آیا بعد از enrich قیمت پایه برای نمایش در اپ کاربر وجود دارد؟ * کارمزد (cash_fee) به‌تنهایی قیمت محسوب نمی‌شود. */ public function hasCustomerDisplayPrice(Product $product): bool { if ((int) ($product->price ?? 0) > 0) { return true; } if (Schema::hasColumn($product->getTable(), 'price_profit')) { if ((int) ($product->price_profit ?? 0) > 0) { return true; } } return false; } }