2026-06-11 16:41:28 +03:30
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
|
|
|
|
|
use App\Models\Price;
|
|
|
|
|
use App\Models\Product;
|
|
|
|
|
use Illuminate\Support\Collection;
|
|
|
|
|
use Illuminate\Support\Facades\Cache;
|
2026-06-11 19:23:16 +03:30
|
|
|
use Illuminate\Support\Facades\Schema;
|
2026-06-11 16:41:28 +03:30
|
|
|
|
|
|
|
|
/**
|
2026-06-27 11:29:09 +03:30
|
|
|
* قیمت نمایشی محصولات برای API.
|
2026-06-11 19:23:16 +03:30
|
|
|
*
|
|
|
|
|
* • آبشده با «قیمت اپ» → montzar_live_price / کش /api/price/last
|
2026-06-27 11:29:09 +03:30
|
|
|
* • محصول با reading_method → فید zaarbox (کش ۱۵ث + fetch روی cache miss)
|
2026-06-11 16:41:28 +03:30
|
|
|
*/
|
|
|
|
|
class ProductDisplayPriceService
|
|
|
|
|
{
|
|
|
|
|
public const LIVE_PRICE_CACHE_KEY = 'live_price_api_payload';
|
|
|
|
|
|
2026-06-13 17:35:40 +03:30
|
|
|
public const LIVE_PRICE_CACHE_SECONDS = 15;
|
2026-06-11 16:41:28 +03:30
|
|
|
|
|
|
|
|
public function usesAppPrice(Product $product): bool
|
|
|
|
|
{
|
|
|
|
|
if ($product->reading_method) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((int) ($product->direct_price ?? 0) === 1) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 15:55:12 +03:30
|
|
|
return $this->isMeltedTradeProduct($product)
|
|
|
|
|
|| $this->usesQuantityAppPrice($product);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** معامله مقداری با قیمت اپ — نه قیمت دستی قدیمی با price پر در DB */
|
|
|
|
|
private function usesQuantityAppPrice(Product $product): bool
|
|
|
|
|
{
|
|
|
|
|
if (! ($product->trade_by_quantity ?? false)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($product->reading_method || (int) ($product->direct_price ?? 0) === 1) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (int) ($product->price ?? 0) <= 0;
|
2026-06-11 16:41:28 +03:30
|
|
|
}
|
|
|
|
|
|
2026-06-11 19:23:16 +03:30
|
|
|
/**
|
|
|
|
|
* پر کردن price روی مدلها برای پاسخ JSON — بدون UPDATE دیتابیس
|
|
|
|
|
*/
|
2026-06-27 11:29:09 +03:30
|
|
|
/** پر کردن کش مظنه و فید zaarbox اگر خالی باشند (قبل از enrich لیست مشتری) */
|
2026-06-12 19:49:53 +03:30
|
|
|
public function warmLivePriceCache(): void
|
|
|
|
|
{
|
|
|
|
|
if (! is_array(Cache::get(self::LIVE_PRICE_CACHE_KEY))) {
|
|
|
|
|
$this->resolveFallbackPayload();
|
|
|
|
|
}
|
2026-06-27 11:29:09 +03:30
|
|
|
|
|
|
|
|
if (! app(ConfigService::class)->isStore()) {
|
|
|
|
|
app(ZaarboxPriceService::class)->cachedPriceList(false);
|
|
|
|
|
}
|
2026-06-12 19:49:53 +03:30
|
|
|
}
|
|
|
|
|
|
2026-06-11 16:41:28 +03:30
|
|
|
public function enrichForApi(Collection|iterable $products): void
|
|
|
|
|
{
|
2026-06-12 19:49:53 +03:30
|
|
|
if (app(ConfigService::class)->isStore()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-11 19:23:16 +03:30
|
|
|
$collection = collect($products);
|
|
|
|
|
$zaarbox = app(ZaarboxPriceService::class);
|
2026-06-27 11:06:32 +03:30
|
|
|
// مثل ادمین — روی cache miss یک بار fetch؛ نه فقط getCachedPriceListOnly
|
|
|
|
|
$zaarboxFeed = $zaarbox->cachedPriceList(false);
|
2026-06-11 16:41:28 +03:30
|
|
|
|
2026-06-11 19:23:16 +03:30
|
|
|
foreach ($collection as $product) {
|
|
|
|
|
if (!$product instanceof Product) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($this->usesAppPrice($product)) {
|
2026-06-11 21:48:18 +03:30
|
|
|
$base = $this->productUsesLastPriceIfZero($product)
|
|
|
|
|
? $this->resolveAppBasePrice()
|
|
|
|
|
: $this->resolveStrictLiveAppBasePrice();
|
|
|
|
|
$this->applyAppPrice($product, $base);
|
2026-06-11 19:23:16 +03:30
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($product->reading_method && is_array($zaarboxFeed)) {
|
|
|
|
|
$this->applyReferenceFeedPrice($product, $zaarboxFeed, $zaarbox);
|
2026-06-12 19:23:09 +03:30
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-22 19:35:20 +03:30
|
|
|
if ($product->reading_method && ! $this->productUsesLastPriceIfZero($product)) {
|
|
|
|
|
$product->setAttribute('reference_provider_active', false);
|
|
|
|
|
$this->zeroProductPrice($product);
|
|
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($product->reading_method) {
|
|
|
|
|
$product->setAttribute('reference_provider_active', true);
|
|
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-12 19:23:09 +03:30
|
|
|
if ($this->isWeightBasedStoreProduct($product)) {
|
|
|
|
|
$this->enrichWeightBasedPrice($product);
|
2026-06-11 16:41:28 +03:30
|
|
|
}
|
|
|
|
|
}
|
2026-06-11 19:23:16 +03:30
|
|
|
}
|
2026-06-11 16:41:28 +03:30
|
|
|
|
2026-06-12 19:23:09 +03:30
|
|
|
/** شمش / سکه و … — قیمت واحد (هر گرم) از مظنه زنده یا آخرین قیمت */
|
|
|
|
|
private function isWeightBasedStoreProduct(Product $product): bool
|
|
|
|
|
{
|
|
|
|
|
if ($this->usesAppPrice($product) || $product->reading_method) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((int) ($product->direct_price ?? 0) === 1) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (float) ($product->amount ?? 0) > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function enrichWeightBasedPrice(Product $product): void
|
|
|
|
|
{
|
|
|
|
|
$useLast = $this->productUsesLastPriceIfZero($product);
|
|
|
|
|
$unitBuy = $useLast
|
|
|
|
|
? $this->resolveLiveBuyPriceWithFallback()
|
|
|
|
|
: $this->resolveStrictLiveBuyPrice();
|
|
|
|
|
|
2026-06-22 19:35:20 +03:30
|
|
|
if ($unitBuy <= 0 && app(ConfigService::class)->getBool('show_last_price', false)) {
|
2026-06-12 19:23:09 +03:30
|
|
|
$unitBuy = $this->resolveHistoricalBasePrice();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-12 19:49:53 +03:30
|
|
|
$offset = ProductPriceService::offsetPercent($product);
|
|
|
|
|
if ($unitBuy > 0 && $offset != 0.0) {
|
|
|
|
|
$unitBuy = ProductPriceService::adjustedTotal($unitBuy, 0, $offset);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-12 19:23:09 +03:30
|
|
|
$this->applyPriceOrFallback($product, $unitBuy, $useLast);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function resolveStrictLiveBuyPrice(): int
|
|
|
|
|
{
|
|
|
|
|
$cached = Cache::get(self::LIVE_PRICE_CACHE_KEY);
|
|
|
|
|
if (! is_array($cached)) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$buy = (int) ($cached['buy'] ?? 0);
|
|
|
|
|
if ($buy > 0) {
|
|
|
|
|
return $buy;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$priceLast = (int) ($cached['price_last'] ?? 0);
|
|
|
|
|
if ($priceLast > 0) {
|
|
|
|
|
return $priceLast;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->extractBaseFromPayload($cached);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function resolveLiveBuyPriceWithFallback(): int
|
|
|
|
|
{
|
|
|
|
|
$strict = $this->resolveStrictLiveBuyPrice();
|
|
|
|
|
if ($strict > 0) {
|
|
|
|
|
return $strict;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$payload = $this->resolveFallbackPayload();
|
|
|
|
|
|
|
|
|
|
return (int) ($payload['buy'] ?? 0) ?: (int) ($payload['price_last'] ?? 0);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-11 19:23:16 +03:30
|
|
|
private function applyAppPrice(Product $product, int $basePrice): void
|
|
|
|
|
{
|
2026-06-11 21:37:17 +03:30
|
|
|
$useLast = $this->productUsesLastPriceIfZero($product);
|
2026-06-11 19:23:16 +03:30
|
|
|
$offset = Product::hasPriceOffsetColumn()
|
|
|
|
|
? (float) ($product->price_offset_percent ?? 0)
|
|
|
|
|
: 0.0;
|
|
|
|
|
|
2026-06-11 21:37:17 +03:30
|
|
|
if ($basePrice <= 0) {
|
|
|
|
|
$this->applyPriceOrFallback($product, 0, $useLast);
|
2026-06-11 21:17:40 +03:30
|
|
|
|
2026-06-11 21:37:17 +03:30
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$computed = ProductPriceService::adjustedTotal($basePrice, 0, $offset);
|
|
|
|
|
$this->applyPriceOrFallback($product, $computed, $useLast);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function applyPriceOrFallback(Product $product, int $computed, bool $useLast): void
|
|
|
|
|
{
|
|
|
|
|
if ($computed > 0) {
|
|
|
|
|
$product->setAttribute('price', $computed);
|
2026-06-11 21:17:40 +03:30
|
|
|
if (Schema::hasColumn($product->getTable(), 'price_profit')) {
|
|
|
|
|
$product->setAttribute('price_profit', 0);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-11 19:23:16 +03:30
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-11 21:37:17 +03:30
|
|
|
if ($useLast) {
|
|
|
|
|
$fallback = $this->resolveLastPriceFallback($product);
|
|
|
|
|
if ($fallback > 0) {
|
|
|
|
|
$product->setAttribute('price', $fallback);
|
|
|
|
|
if (Schema::hasColumn($product->getTable(), 'price_profit')) {
|
|
|
|
|
$product->setAttribute('price_profit', 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$product->setAttribute('price', 0);
|
2026-06-11 19:23:16 +03:30
|
|
|
if (Schema::hasColumn($product->getTable(), 'price_profit')) {
|
|
|
|
|
$product->setAttribute('price_profit', 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-11 21:37:17 +03:30
|
|
|
private function productUsesLastPriceIfZero(Product $product): bool
|
|
|
|
|
{
|
|
|
|
|
return Product::hasUseLastPriceIfZeroColumn()
|
|
|
|
|
&& ($product->use_last_price_if_zero ?? false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* آخرین قیمت ذخیرهشده: اول products.price، بعد جدول prices (تاریخچه مظنه).
|
|
|
|
|
*/
|
|
|
|
|
public function resolveLastPriceFallback(Product $product): int
|
|
|
|
|
{
|
|
|
|
|
$stored = (int) ($product->price ?? 0);
|
|
|
|
|
if ($stored > 0) {
|
|
|
|
|
return $stored;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$storedProfit = (int) ($product->price_profit ?? 0);
|
|
|
|
|
if ($storedProfit > 0) {
|
|
|
|
|
return $stored + $storedProfit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$historical = $this->resolveHistoricalBasePrice();
|
|
|
|
|
if ($historical <= 0) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($this->usesAppPrice($product) || $product->reading_method) {
|
|
|
|
|
$offset = Product::hasPriceOffsetColumn()
|
|
|
|
|
? (float) ($product->price_offset_percent ?? 0)
|
|
|
|
|
: 0.0;
|
|
|
|
|
|
|
|
|
|
return ProductPriceService::adjustedTotal($historical, 0, $offset);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $historical;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function resolveHistoricalBasePrice(): int
|
|
|
|
|
{
|
|
|
|
|
$last = Price::orderByDesc('id')->first();
|
|
|
|
|
if (!$last || (float) $last->price <= 0) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (int) $last->price;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-11 21:48:18 +03:30
|
|
|
private function zeroProductPrice(Product $product): void
|
|
|
|
|
{
|
|
|
|
|
$product->setAttribute('price', 0);
|
|
|
|
|
if (Schema::hasColumn($product->getTable(), 'price_profit')) {
|
|
|
|
|
$product->setAttribute('price_profit', 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* فقط قیمت زنده (کش /api/price/last) — بدون montzar_live_price قدیمی settings.
|
|
|
|
|
* وقتی use_last_price_if_zero خاموش است.
|
|
|
|
|
*/
|
|
|
|
|
public function resolveStrictLiveAppBasePrice(): int
|
|
|
|
|
{
|
|
|
|
|
$cached = Cache::get(self::LIVE_PRICE_CACHE_KEY);
|
|
|
|
|
if (!is_array($cached)) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->extractBaseFromPayload($cached);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-11 19:23:16 +03:30
|
|
|
/** @param array<int, array<string, mixed>> $feed */
|
|
|
|
|
private function applyReferenceFeedPrice(Product $product, array $feed, ZaarboxPriceService $zaarbox): void
|
|
|
|
|
{
|
2026-06-22 19:14:24 +03:30
|
|
|
$useLast = $this->productUsesLastPriceIfZero($product);
|
2026-06-11 19:23:16 +03:30
|
|
|
$row = $zaarbox->findFeedRow($feed, (string) $product->reading_method);
|
|
|
|
|
if ($row === null) {
|
2026-06-22 19:35:20 +03:30
|
|
|
$product->setAttribute('reference_provider_active', $useLast);
|
2026-06-12 19:49:53 +03:30
|
|
|
$offset = ProductPriceService::offsetPercent($product);
|
2026-06-22 19:14:24 +03:30
|
|
|
$historical = $useLast ? $this->resolveHistoricalBasePrice() : 0;
|
2026-06-12 19:49:53 +03:30
|
|
|
$computed = $historical > 0
|
|
|
|
|
? ProductPriceService::adjustedTotal($historical, 0, $offset)
|
|
|
|
|
: 0;
|
|
|
|
|
$this->applyPriceOrFallback($product, $computed, $useLast);
|
|
|
|
|
|
2026-06-11 19:23:16 +03:30
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-22 19:50:45 +03:30
|
|
|
$providerRaw = $zaarbox->resolveReferenceProviderTotal($row);
|
2026-06-22 19:35:20 +03:30
|
|
|
$providerActive = $useLast || $providerRaw > 0;
|
|
|
|
|
$product->setAttribute('reference_provider_active', $providerActive);
|
|
|
|
|
|
|
|
|
|
if (! $useLast && $providerRaw <= 0) {
|
|
|
|
|
$this->zeroProductPrice($product);
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-11 19:23:16 +03:30
|
|
|
$computed = $zaarbox->computeAdjustedProductPrice($product, $row);
|
|
|
|
|
if ($computed <= 0) {
|
2026-06-22 19:14:24 +03:30
|
|
|
if ($useLast) {
|
2026-06-11 21:37:17 +03:30
|
|
|
$fallback = $this->resolveLastPriceFallback($product);
|
|
|
|
|
if ($fallback > 0) {
|
|
|
|
|
$product->setAttribute('price', $fallback);
|
|
|
|
|
if (Schema::hasColumn($product->getTable(), 'price_profit')) {
|
|
|
|
|
$product->setAttribute('price_profit', 0);
|
|
|
|
|
}
|
2026-06-11 21:48:18 +03:30
|
|
|
} else {
|
|
|
|
|
$this->zeroProductPrice($product);
|
2026-06-11 21:37:17 +03:30
|
|
|
}
|
2026-06-11 21:48:18 +03:30
|
|
|
} else {
|
|
|
|
|
$this->zeroProductPrice($product);
|
2026-06-11 16:41:28 +03:30
|
|
|
}
|
|
|
|
|
|
2026-06-11 19:23:16 +03:30
|
|
|
return;
|
|
|
|
|
}
|
2026-06-11 16:41:28 +03:30
|
|
|
|
2026-06-11 19:23:16 +03:30
|
|
|
$product->setAttribute('price', $computed);
|
|
|
|
|
if (Schema::hasColumn($product->getTable(), 'price_profit')) {
|
|
|
|
|
$product->setAttribute('price_profit', 0);
|
2026-06-11 16:41:28 +03:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @param array<string, mixed> $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
|
|
|
|
|
{
|
2026-06-11 19:23:16 +03:30
|
|
|
return $this->extractBaseFromPayload($this->livePricePayload());
|
|
|
|
|
}
|
2026-06-11 16:41:28 +03:30
|
|
|
|
2026-06-28 10:46:09 +03:30
|
|
|
/** قیمت پایه سفارش — همان منبع نمایش اپ (کش / montzar_live_price / prices) */
|
|
|
|
|
public function resolveOrderBasePrice(?Product $product = null): int
|
|
|
|
|
{
|
|
|
|
|
$this->warmLivePriceCache();
|
|
|
|
|
|
|
|
|
|
if ($product instanceof Product) {
|
|
|
|
|
if ($this->usesAppPrice($product)) {
|
2026-06-28 10:51:56 +03:30
|
|
|
$base = $this->resolveAppBasePrice();
|
|
|
|
|
if ($base <= 0) {
|
|
|
|
|
$base = $this->resolveHistoricalBasePrice();
|
|
|
|
|
}
|
|
|
|
|
if ($base <= 0 && $this->productUsesLastPriceIfZero($product)) {
|
|
|
|
|
$base = $this->resolveLastPriceFallback($product);
|
2026-06-28 10:46:09 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($base <= 0) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$offset = Product::hasPriceOffsetColumn()
|
|
|
|
|
? (float) ($product->price_offset_percent ?? 0)
|
|
|
|
|
: 0.0;
|
|
|
|
|
|
|
|
|
|
return ProductPriceService::adjustedTotal($base, 0, $offset);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($product->reading_method) {
|
|
|
|
|
$this->enrichForApi(collect([$product]));
|
|
|
|
|
$unit = (int) (($product->price ?? 0) + ($product->price_profit ?? 0));
|
|
|
|
|
if ($unit > 0) {
|
|
|
|
|
return $unit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((int) ($product->direct_price ?? 0) === 1) {
|
|
|
|
|
return (int) (($product->price ?? 0) + ($product->price_profit ?? 0));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$base = $this->resolveAppBasePrice();
|
|
|
|
|
if ($base > 0) {
|
|
|
|
|
return $base;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->resolveHistoricalBasePrice();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-11 19:23:16 +03:30
|
|
|
/** @param array<string, mixed> $payload */
|
|
|
|
|
private function extractBaseFromPayload(array $payload): int
|
|
|
|
|
{
|
2026-06-11 16:41:28 +03:30
|
|
|
$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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-11 19:23:16 +03:30
|
|
|
$priceLast = (int) ($payload['price_last'] ?? 0);
|
|
|
|
|
if ($priceLast > 0) {
|
|
|
|
|
return $priceLast;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
2026-06-11 16:41:28 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @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') {
|
2026-06-11 19:23:16 +03:30
|
|
|
$price = $this->resolveZaarboxBoardPriceFromCache($config);
|
2026-06-11 16:41:28 +03:30
|
|
|
} elseif ($provider === 'main_product') {
|
|
|
|
|
$main = $config->mainProduct();
|
|
|
|
|
if ($main) {
|
|
|
|
|
$price = (int) (($main->price ?? 0) + ($main->price_profit ?? 0));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-11 19:23:16 +03:30
|
|
|
if ($price <= 0 && $config->getBool('show_last_price', false)) {
|
2026-06-11 16:41:28 +03:30
|
|
|
$last = Price::orderByDesc('id')->first();
|
|
|
|
|
if ($last && (float) $last->price > 0) {
|
|
|
|
|
$price = (int) $last->price;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-02 01:26:26 +03:30
|
|
|
$diffBuy = (int) $config->displayBuyPriceDiffRial();
|
|
|
|
|
$diffSell = (int) $config->displaySellPriceDiffRial();
|
2026-06-11 16:41:28 +03:30
|
|
|
|
2026-06-11 19:23:16 +03:30
|
|
|
$payload = [
|
2026-06-11 16:41:28 +03:30
|
|
|
'price' => $price,
|
|
|
|
|
'buy' => $price > 0 ? $price + $diffBuy : 0,
|
|
|
|
|
'sell' => $price > 0 ? max(0, $price - $diffSell) : 0,
|
|
|
|
|
'price_last' => $price > 0 ? $price + $diffBuy : 0,
|
|
|
|
|
];
|
2026-06-11 19:23:16 +03:30
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-27 11:29:09 +03:30
|
|
|
$feed = app(ZaarboxPriceService::class)->cachedPriceList(false);
|
2026-06-11 19:23:16 +03:30
|
|
|
if (!is_array($feed)) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$row = app(ZaarboxPriceService::class)->findFeedRow($feed, $identifier);
|
|
|
|
|
if ($row === null) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$row = app(ZaarboxPriceService::class)->normalizeFeedRow($row);
|
|
|
|
|
|
2026-06-22 19:50:45 +03:30
|
|
|
return app(ZaarboxPriceService::class)->resolveReferenceProviderTotal($row);
|
2026-06-11 16:41:28 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function isMeltedTradeProduct(Product $product): bool
|
|
|
|
|
{
|
|
|
|
|
$group = trim((string) ($product->group_type ?? ''));
|
|
|
|
|
|
|
|
|
|
return in_array($group, ['آبشده', 'کاتد مس'], true);
|
|
|
|
|
}
|
2026-06-11 21:17:40 +03:30
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* آیا بعد از 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;
|
|
|
|
|
}
|
2026-06-12 19:23:09 +03:30
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* نمایش در اپ کاربر:
|
2026-06-22 19:35:20 +03:30
|
|
|
* • خاموش (show=false در DB) → false
|
2026-06-12 19:23:09 +03:30
|
|
|
* • قیمت نمایشی بعد از enrich → true
|
2026-06-22 19:35:20 +03:30
|
|
|
* • قیمت صفر و show_last_price خاموش → false
|
|
|
|
|
* • قیمت صفر و show_last_price روشن و fallback موجود → true
|
2026-06-12 19:23:09 +03:30
|
|
|
*/
|
|
|
|
|
public function resolveCustomerShow(Product $product): bool
|
|
|
|
|
{
|
2026-06-14 22:00:21 +03:30
|
|
|
if (! (bool) ($product->active ?? true)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (! app(ConfigService::class)->isProductWithinTradingHours($product)) {
|
2026-06-12 19:23:09 +03:30
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($this->hasCustomerDisplayPrice($product)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-22 19:35:20 +03:30
|
|
|
if (! app(ConfigService::class)->getBool('show_last_price', false)) {
|
2026-06-12 19:23:09 +03:30
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-22 19:35:20 +03:30
|
|
|
return $this->resolveLastPriceFallback($product) > 0
|
|
|
|
|
|| $this->resolveHistoricalBasePrice() > 0;
|
2026-06-12 19:23:09 +03:30
|
|
|
}
|
2026-06-11 16:41:28 +03:30
|
|
|
}
|