fix(price): read last quote and daily extremes from the active provider

A stray montzar tick landed in the prices table, which stores no source, so
the database_last fallback served a ~168M quote on a shop whose Zaarbox board
was at ~217M. Last price and today's high/low now come from price_sync_logs
filtered by price_provider, the database fallback no longer counts as today's
price, and daily extremes reset when the provider changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-31 09:45:37 +03:30
co-authored by Cursor
parent 1b73305d6f
commit 142cfd7661
5 changed files with 98 additions and 37 deletions
@@ -82,8 +82,7 @@ class ReportController extends Controller
return ['sell' => 0, 'buy' => 0];
}
$price = Price::orderByDesc('id')->first();
$priceValue = $price ? (float) $price->price : 0.0;
$priceValue = (float) app(ProductDisplayPriceService::class)->resolveHistoricalBasePrice();
if ($priceValue <= 0) {
return ['sell' => 0, 'buy' => 0];
@@ -406,8 +405,9 @@ class ReportController extends Controller
}
if ($priceValue <= 0) {
$lastPrice = Price::latest()->first();
$storedPrice = $lastPrice ? (float) $lastPrice->price : 0.0;
// آخرین قیمت همین provider (نه هر ردیفی از جدول prices — ممکن است
// از یک تیک provider دیگر مانده باشد و مظنه را ده‌ها میلیون پرت کند)
$storedPrice = (float) app(ProductDisplayPriceService::class)->resolveHistoricalBasePrice();
// fallback فقط با show_last_price تصمیم‌گیری می‌شود؛ force_refresh (تیک کران) نباید آن را override کند.
// آستانهٔ ثابت قبلی (۳۰ میلیون) برای طلا بود و محصولات ارزان‌تر مثل کاتد مس/نقره را اشتباهاً «بدون مظنه» می‌کرد.
@@ -448,7 +448,9 @@ class ReportController extends Controller
$buy = $display['buy'];
$priceLast = $display['priceLast'];
if (!$closed && $priceValue > 0) {
// سقف/کف روز فقط از مظنهٔ زنده؛ fallback دیتابیس قیمت دیشب را
// به‌عنوان «قیمت امروز» ثبت می‌کرد و بازهٔ روز را خراب می‌کرد.
if (!$closed && $priceValue > 0 && $priceSource !== 'database_last') {
$config->trackDailyPriceExtreme((float) $priceValue);
}
+76 -23
View File
@@ -5,9 +5,11 @@ namespace App\Services;
use App\Models\Cnt;
use App\Models\Price;
use App\Models\PriceCandle;
use App\Models\PriceSyncLog;
use App\Models\Product;
use App\Models\Setting;
use Carbon\Carbon;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
class ConfigService
@@ -37,13 +39,65 @@ class ConfigService
}
/**
* آخرین قیمت ذخیره‌شده در جدول prices.
* آخرین قیمت مرجع ثبت‌شده از همین provider (price_sync_logs).
*
* جدول prices منبع قیمت را نگه نمی‌دارد؛ یک تیک از provider دیگر
* (مثلاً montzar روی فروشگاهی که تابلوی zaarbox دارد) تا روزها
* «آخرین قیمت» می‌ماند و مظنه پرت نشان داده می‌شود.
*/
public function referenceQuotePrice(): float
public function lastProviderQuotePrice(): int
{
$last = Price::query()->orderByDesc('id')->value('price');
$provider = $this->priceProvider();
return $last !== null ? (float) $last : 0.0;
return (int) Cache::remember(
'price:last_provider_quote:' . $provider,
10,
function () use ($provider) {
try {
return (int) PriceSyncLog::query()
->where('source', $provider)
->where('liquid_gold_price', '>', 0)
->orderByDesc('id')
->value('liquid_gold_price');
} catch (\Throwable $e) {
return 0;
}
}
);
}
/**
* کمینه/بیشینهٔ قیمت مرجع امروز — فقط ردیف‌های همین provider.
*
* @return array{min: int, max: int}
*/
public function providerTodayQuoteExtremes(): array
{
$provider = $this->priceProvider();
$row = Cache::remember(
'price:provider_today_extremes:' . $provider,
10,
function () use ($provider) {
try {
$stats = PriceSyncLog::query()
->where('source', $provider)
->where('liquid_gold_price', '>', 0)
->whereDate('created_at', now()->toDateString())
->selectRaw('MIN(liquid_gold_price) as min_price, MAX(liquid_gold_price) as max_price')
->first();
return [
'min' => (int) ($stats->min_price ?? 0),
'max' => (int) ($stats->max_price ?? 0),
];
} catch (\Throwable $e) {
return ['min' => 0, 'max' => 0];
}
}
);
return is_array($row) ? $row : ['min' => 0, 'max' => 0];
}
/** مظنهٔ مثبت قابل استفاده است — محدودیت انحراف نسبت به DB برداشته شد. */
@@ -542,18 +596,24 @@ class ConfigService
$currentUp = (float) ($upSetting->value ?: 0);
$currentDown = (float) ($downSetting->value ?: 0);
$anchor = $currentUp > 0 ? $currentUp : $this->referenceQuotePrice();
if ($anchor > 0 && ! $this->isPlausibleQuotePrice((float) $priceInt, $anchor)) {
return;
}
if ($extremeDate !== $today) {
// سقف و کف روز باید از یک منبع باشند؛ وگرنه تیک provider دیگر
// بازهٔ جعلی می‌سازد (مثلاً سقف تابلو و کف montzar).
$provider = $this->priceProvider();
$extremeSource = trim((string) $this->getString('price_extreme_source', ''));
if ($extremeDate !== $today || ($extremeSource !== '' && $extremeSource !== $provider)) {
$this->UpdateConfig('up_price', $priceInt);
$this->UpdateConfig('down_price', $priceInt);
$this->UpdateConfig('price_extreme_date', $today);
$this->UpdateConfig('price_extreme_source', $provider);
return;
}
if ($extremeSource === '') {
$this->UpdateConfig('price_extreme_source', $provider);
}
if ($currentUp <= 0 || $priceInt > $currentUp) {
$this->UpdateConfig('up_price', $priceInt);
}
@@ -569,6 +629,12 @@ class ConfigService
*/
public function resolvedDailyBaseExtremes(): array
{
// تاریخچهٔ برچسب‌دار همین provider — سقف/کف بدون قاطی شدن با تیک منبع دیگر
$fromLog = $this->providerTodayQuoteExtremes();
if ($fromLog['max'] > 0 && $fromLog['min'] > 0) {
return ['up' => $fromLog['max'], 'down' => $fromLog['min']];
}
$up = $this->getInt('up_price', 0);
$down = $this->getInt('down_price', 0);
@@ -604,21 +670,8 @@ class ConfigService
[$up, $down] = [$down, $up];
}
// کف روز آلوده به مظنهٔ اشتباه provider (مثلاً ۱۶۸M) را دور بریز
if ($up > 0 && $down > 0 && ! $this->isPlausibleQuotePrice((float) $down, (float) $up)) {
$fromTable = $this->filteredTodayMinMax();
if ($fromTable['min'] > 0 && $this->isPlausibleQuotePrice((float) $fromTable['min'], (float) $up)) {
$down = $fromTable['min'];
} else {
$down = $up;
}
if ($this->getInt('down_price', 0) !== $down) {
$this->UpdateConfig('down_price', $down);
}
}
$storedDown = $this->getInt('down_price', 0);
if ($down > 0 && $down < $storedDown && $this->isPlausibleQuotePrice((float) $down, (float) max($up, 1))) {
if ($down > 0 && $down < $storedDown) {
$this->UpdateConfig('down_price', $down);
}
+1 -3
View File
@@ -2,7 +2,6 @@
namespace App\Services;
use App\Models\Price;
use Illuminate\Support\Facades\Cache;
class LivePriceCacheService
@@ -114,8 +113,7 @@ class LivePriceCacheService
{
$config = app(ConfigService::class);
$display = app(ProductDisplayPriceService::class);
$last = Price::orderByDesc('id')->first();
$base = $last ? (float) $last->price : 0.0;
$base = (float) $display->resolveHistoricalBasePrice();
$provider = $config->priceProvider();
$montzarSource = 0;
+12 -4
View File
@@ -281,8 +281,19 @@ class ProductDisplayPriceService
return $historical;
}
/**
* آخرین قیمت پایهٔ ذخیره‌شده.
*
* اول تاریخچهٔ برچسب‌دار همین provider (price_sync_logs)؛ جدول prices منبع را
* ذخیره نمی‌کند و یک تیک اشتباه از provider دیگر «آخرین قیمت» را پرت می‌کند.
*/
public function resolveHistoricalBasePrice(): int
{
$fromProviderLog = app(ConfigService::class)->lastProviderQuotePrice();
if ($fromProviderLog > 0) {
return $fromProviderLog;
}
$last = Price::orderByDesc('id')->first();
if (!$last || (float) $last->price <= 0) {
return 0;
@@ -562,10 +573,7 @@ class ProductDisplayPriceService
}
if ($price <= 0 && $config->getBool('show_last_price', false)) {
$last = Price::orderByDesc('id')->first();
if ($last && (float) $last->price > 0) {
$price = (int) $last->price;
}
$price = $this->resolveHistoricalBasePrice();
}
$diffBuy = (int) $config->displayBuyPriceDiffRial();
+2 -2
View File
@@ -2334,7 +2334,7 @@ UPDATE `settings` SET `can_edit` = 1 WHERE `key` IN (
-- تنظیمات پیشرفته (can_edit=0) — فقط در پنل پیشرفته؛ ادمین دیپلوی ویرایش می‌کند
UPDATE `settings` SET `can_edit` = 0 WHERE `key` IN (
'up_price', 'down_price', 'price_extreme_date',
'up_price', 'down_price', 'price_extreme_date', 'price_extreme_source',
'main_product', 'price_provider', 'zaarbox_api_url',
'montzar_base_url', 'montzar_diff', 'montzar_closed', 'montzar_order_response',
'montzar_token', 'montzar_live_price', 'montzar_last_product_log',
@@ -2513,7 +2513,7 @@ UPDATE `settings` SET `can_edit` = 1 WHERE `key` IN (
'do_send_liman'
);
UPDATE `settings` SET `can_edit` = 0 WHERE `key` IN (
'up_price', 'down_price', 'price_extreme_date',
'up_price', 'down_price', 'price_extreme_date', 'price_extreme_source',
'main_product', 'price_provider', 'zaarbox_api_url',
'montzar_base_url', 'montzar_diff', 'montzar_closed', 'montzar_order_response',
'montzar_token', 'montzar_live_price', 'montzar_last_product_log',