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:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user