fix(settings): push trading-hours changes to live user prices immediately
Recompute home price display on save, broadcast settings over SSE, and keep last-price status in sync without a user refresh. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+158
-19
@@ -14,6 +14,17 @@ class ConfigService
|
||||
{
|
||||
public const PRICE_PROVIDERS = ['montzar', 'main_product', 'zaarbox'];
|
||||
|
||||
/** کلیدهای صفحه «ساعت کاری» پنل — هر تغییر باید بلافاصله روی قیمت کاربر پخش شود */
|
||||
public const TRADING_HOURS_CLIENT_KEYS = [
|
||||
'close_price',
|
||||
'startDay',
|
||||
'endDay',
|
||||
'show_last_price',
|
||||
'show_home_price_when_closed',
|
||||
'show_gold_ounce_price',
|
||||
'trading_hours_by_day',
|
||||
];
|
||||
|
||||
/**
|
||||
* حداکثر انحراف مجاز مظنه نسبت به آخرین قیمت معتبر DB.
|
||||
* پرش اشتباه provider (مثل ۱۶۸M ماندهٔ montzar روی مظنهٔ ~۲۲۰M) را رد میکند.
|
||||
@@ -751,40 +762,168 @@ class ConfigService
|
||||
$start = $now->copy()->startOfDay()->setTime($startHour, $startMinute);
|
||||
$end = $now->copy()->startOfDay()->setTime($endHour, $endMinute);
|
||||
|
||||
// بازهٔ شبانه (مثلاً ۲۲:۰۰ تا ۰۶:۰۰)
|
||||
if ($end->lte($start)) {
|
||||
return $now->lt($start) && $now->gt($end);
|
||||
}
|
||||
|
||||
return !$now->between($start, $end);
|
||||
}
|
||||
|
||||
public function shouldShowLastSavedPrice(): bool
|
||||
{
|
||||
return $this->getBool('show_last_price', false);
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $payload */
|
||||
public function payloadHasDisplayablePrice(array $payload): bool
|
||||
{
|
||||
return (int) ($payload['buy'] ?? 0) > 0
|
||||
|| (int) ($payload['sell'] ?? 0) > 0
|
||||
|| (int) ($payload['price_last'] ?? 0) > 0
|
||||
|| (int) ($payload['price'] ?? 0) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* گیت ساعت کاری روی payload کش/SSE — وقتی close_price روشن است.
|
||||
* آخرین قیمت جدول prices را روی payload میگذارد — وقتی مظنه زنده نیست و show_last_price روشن است.
|
||||
*
|
||||
* @param array<string, mixed> $payload
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function fillPayloadFromLastStoredPrice(array $payload): array
|
||||
{
|
||||
if ($this->payloadHasDisplayablePrice($payload)) {
|
||||
return $payload;
|
||||
}
|
||||
|
||||
$stored = Price::query()->orderByDesc('id')->value('price');
|
||||
$priceValue = $stored !== null ? (float) $stored : 0.0;
|
||||
if ($priceValue <= 0) {
|
||||
return $payload;
|
||||
}
|
||||
|
||||
$trade = $this->buildTradeDisplayPrices($priceValue, 'database_last');
|
||||
$payload['sell'] = $trade['sell'];
|
||||
$payload['buy'] = $trade['buy'];
|
||||
$payload['price_last'] = $trade['priceLast'];
|
||||
$payload['price'] = (int) $priceValue;
|
||||
$payload['price_source'] = $payload['price_source'] ?? 'database_last';
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* وضعیت نمایش صفحه اصلی + پرچم معامله:
|
||||
* - close_price خاموش / داخل ساعت: قیمت هست → open ؛ وگرنه با show_last_price آخرین قیمت؛ وگرنه no_quote
|
||||
* - close_price روشن و خارج ساعت: show_home_price_when_closed روشن → آخرین قیمت (open) ؛ خاموش → closed
|
||||
* closed=true یعنی خرید/فروش بسته است؛ status فقط برای نمایش خانه است.
|
||||
*
|
||||
* @param array<string, mixed> $payload
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function applyHomePriceDisplayState(array $payload, ?Carbon $now = null): array
|
||||
{
|
||||
$outside = $this->isOutsideTradingHours($now);
|
||||
$showWhenClosed = $this->shouldShowHomePriceWhenClosed();
|
||||
$showLast = $this->shouldShowLastSavedPrice();
|
||||
|
||||
if ($outside) {
|
||||
$payload['closed'] = true;
|
||||
if ($showWhenClosed) {
|
||||
$payload = $this->fillPayloadFromLastStoredPrice($payload);
|
||||
$payload['status'] = $this->payloadHasDisplayablePrice($payload) ? 'open' : 'no_quote';
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
$payload['status'] = 'closed';
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
$payload['closed'] = false;
|
||||
if ($showLast) {
|
||||
$payload = $this->fillPayloadFromLastStoredPrice($payload);
|
||||
}
|
||||
$payload['status'] = $this->payloadHasDisplayablePrice($payload) ? 'open' : 'no_quote';
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* گیت ساعت کاری روی payload کش/SSE.
|
||||
*
|
||||
* @param array<string, mixed> $payload
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function applyGlobalTradingHoursToPayload(array $payload): array
|
||||
{
|
||||
if (!$this->isClosePriceEnabled() || !$this->isOutsideTradingHours()) {
|
||||
return $payload;
|
||||
return $this->applyHomePriceDisplayState($payload);
|
||||
}
|
||||
|
||||
public function isTradingHoursSettingKey(string $key): bool
|
||||
{
|
||||
return in_array($key, self::TRADING_HOURS_CLIENT_KEYS, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* تنظیمات ساعت کاری برای اپ کاربر (بدون فیلتر show_to_user — secret نیستند).
|
||||
*
|
||||
* @return list<array{key: string, value: mixed, is_active: mixed}>
|
||||
*/
|
||||
public function clientTradingHoursSettings(): array
|
||||
{
|
||||
$rows = Setting::query()
|
||||
->whereIn('key', self::TRADING_HOURS_CLIENT_KEYS)
|
||||
->get()
|
||||
->keyBy('key');
|
||||
|
||||
$out = [];
|
||||
foreach (self::TRADING_HOURS_CLIENT_KEYS as $key) {
|
||||
$row = $rows->get($key);
|
||||
$out[] = [
|
||||
'key' => $key,
|
||||
'value' => $row->value ?? '',
|
||||
'is_active' => $row->is_active ?? 1,
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->shouldShowHomePriceWhenClosed()) {
|
||||
$payload['closed'] = true;
|
||||
$payload['status'] = 'closed';
|
||||
return $out;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function attachTradingHoursSettingsPatch(array $payload): array
|
||||
{
|
||||
$payload['settings_patch'] = $this->clientTradingHoursSettings();
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* بعد از ذخیره ساعت کاری در پنل: کش last، SSE تنظیمات، و قیمت را همان لحظه برای کاربر بفرست.
|
||||
*/
|
||||
public function notifyTradingHoursChanged(): void
|
||||
{
|
||||
$this->syncAppClosedFlag();
|
||||
|
||||
$patch = $this->clientTradingHoursSettings();
|
||||
$cache = app(LivePriceCacheService::class);
|
||||
$payload = $cache->get() ?? $cache->buildFromDatabase();
|
||||
$payload = $this->applyHomePriceDisplayState($payload);
|
||||
$payload = app(ZaarboxPriceService::class)->attachGoldOunceToPayload($payload);
|
||||
$payload['settings_patch'] = $patch;
|
||||
|
||||
$cache->replaceBody($payload);
|
||||
|
||||
$stream = app(PriceStreamService::class);
|
||||
if ($patch !== []) {
|
||||
$stream->publish('settings', $patch);
|
||||
}
|
||||
|
||||
return [
|
||||
'sell' => 0,
|
||||
'buy' => 0,
|
||||
'price_last' => 0,
|
||||
'price' => 0,
|
||||
'main_product' => (int) ($payload['main_product'] ?? 0),
|
||||
'montzar' => (int) ($payload['montzar'] ?? 0),
|
||||
'zaarbox' => (int) ($payload['zaarbox'] ?? 0),
|
||||
'closed' => true,
|
||||
'status' => 'closed',
|
||||
'time' => now(),
|
||||
];
|
||||
app(PriceBroadcastService::class)->broadcastAfterPriceUpdate($payload, true);
|
||||
}
|
||||
|
||||
public function syncAppClosedFlag(?Carbon $now = null): bool
|
||||
|
||||
Reference in New Issue
Block a user