diff --git a/app/Http/Controllers/Site/ReportController.php b/app/Http/Controllers/Site/ReportController.php index aff7e47..715a55a 100644 --- a/app/Http/Controllers/Site/ReportController.php +++ b/app/Http/Controllers/Site/ReportController.php @@ -141,7 +141,20 @@ class ReportController extends Controller $payload['last_historical'] = app(ProductDisplayPriceService::class)->resolveHistoricalBasePrice(); app(ProductDisplayPriceService::class)->cacheLivePricePayload($payload); - if ($saveFullCache || request()->boolean('force_refresh')) { + $isFreshPrice = $saveFullCache || request()->boolean('force_refresh'); + + // ارسال قیمت به کانال فقط روی «لحظهٔ تغییر قیمت» = مسیر force_refresh (همان چیزی که سرورِ + // حساس به قیمت صدا می‌زند و مبنای برادکست SSE است) — نه هر pulling عادی که کش برمی‌گرداند. + // بعد از پاسخ اجرا می‌شود تا تاخیری برای فراخوان نباشد. در کنسول صدا نمی‌زنیم چون tick() + // خودش مستقیماً broadcastIfEligible را اجرا می‌کند و afterResponse در دیمن اجرا نمی‌شود. + // خودِ سرویس با price_channel_min_seconds و تشخیص تغییر قیمت گیت می‌کند. + if ($isFreshPrice && (int) ($payload['price'] ?? 0) > 0 && ! app()->runningInConsole()) { + dispatch(function () { + app(\App\Services\PriceChannelBroadcastService::class)->broadcastIfEligible(); + })->afterResponse(); + } + + if ($isFreshPrice) { $cache = app(LivePriceCacheService::class); $cache->markTick(); diff --git a/app/Services/PriceChannelBroadcastService.php b/app/Services/PriceChannelBroadcastService.php index 8353a57..47b7d8b 100644 --- a/app/Services/PriceChannelBroadcastService.php +++ b/app/Services/PriceChannelBroadcastService.php @@ -48,22 +48,26 @@ class PriceChannelBroadcastService return; } + // شرط ۱ (ارزان): از آخرین «ارسال واقعی» حداقل price_channel_min_seconds گذشته باشد. + // چون فقط روی ارسال واقعی markSent می‌کنیم، یک بررسیِ بی‌ارسال تایمر را ریست نمی‌کند + // و ارسالِ بعدی را عقب نمی‌اندازد. + if (! $this->cooldownElapsed()) { + return; + } + $products = $this->eligibleProducts(); if ($products === []) { Log::debug('PriceChannel: محصول واجد شرایطی نیست (active/قیمت/ساعت کاری)'); return; } - // فقط وقتی قیمت واقعاً تغییر کرده باشد + // شرط ۲: قیمت نسبت به آخرین ارسال واقعاً تغییر کرده باشد؛ وگرنه چیزی نفرست + // و تایمر را هم دست نزن (تا سرِ اولین تغییرِ بعدی بلافاصله بفرستد). $signature = $this->priceSignature($products); if ($signature === Cache::get(self::SIGNATURE_CACHE_KEY)) { return; } - if (! $this->cooldownElapsed()) { - return; - } - $message = $this->buildMessage($products); if ($message === '') { return; @@ -74,6 +78,7 @@ class PriceChannelBroadcastService $results[$messenger] = $this->dispatchToMessenger($messenger, $target['token'], $target['chat_ids'], $message); } + // فقط بعد از ارسال واقعی: تایمر cooldown و امضای قیمت به‌روزرسانی می‌شوند $this->markSent(); Cache::put(self::SIGNATURE_CACHE_KEY, $signature, now()->addDay()); @@ -289,6 +294,16 @@ class PriceChannelBroadcastService ->orderBy('id') ->get(); + // قیمت زنده را روی مدل‌ها می‌گذارد (بدون UPDATE دیتابیس) تا پیام و تشخیص تغییر + // مستقل از cron/tick و همیشه تازه باشد. + try { + $display = app(ProductDisplayPriceService::class); + $display->warmLivePriceCache(); + $display->enrichForApi($products); + } catch (\Throwable $e) { + Log::debug('PriceChannel enrich live price failed', ['message' => $e->getMessage()]); + } + $eligible = []; foreach ($products as $product) { if ((int) ($product->price ?? 0) <= 0) {