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:
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Setting;
|
||||
use App\Services\ConfigService;
|
||||
use App\Services\PriceChannelBroadcastService;
|
||||
use App\Services\SuperAdminProgramSettingsOverlay;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -167,6 +168,14 @@ class SettingsController extends Controller
|
||||
})->afterResponse();
|
||||
}
|
||||
|
||||
if (app(ConfigService::class)->isTradingHoursSettingKey($key)) {
|
||||
try {
|
||||
app(ConfigService::class)->notifyTradingHoursChanged();
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
|
||||
if ($isNew) {
|
||||
$projectName = Setting::where('key', 'name_fa')->value('value') ?: 'پروژه';
|
||||
$settingTitle = trim($request->title_fa ?? '') ?: $request->key;
|
||||
|
||||
@@ -61,6 +61,11 @@ class PriceStreamController extends Controller
|
||||
$send('last', app(ZaarboxPriceService::class)->attachGoldOunceToPayload($last));
|
||||
}
|
||||
|
||||
$hoursSettings = app(\App\Services\ConfigService::class)->clientTradingHoursSettings();
|
||||
if ($hoursSettings !== []) {
|
||||
$send('settings', $hoursSettings);
|
||||
}
|
||||
|
||||
$list = app(PriceBroadcastService::class)->buildListPayload(false);
|
||||
if (is_array($list) && $list !== []) {
|
||||
$send('list', $list);
|
||||
|
||||
@@ -98,15 +98,8 @@ class ReportController extends Controller
|
||||
protected function appendUpPrices(array $payload, bool $saveFullCache = false): array
|
||||
{
|
||||
// status: open | closed (خارج از ساعت کاری) | no_quote (مظنه در دسترس نیست)
|
||||
if (!isset($payload['status'])) {
|
||||
if (!empty($payload['closed'])) {
|
||||
$payload['status'] = 'closed';
|
||||
} elseif ((int) ($payload['price'] ?? 0) <= 0 && (int) ($payload['buy'] ?? 0) <= 0) {
|
||||
$payload['status'] = 'no_quote';
|
||||
} else {
|
||||
$payload['status'] = 'open';
|
||||
}
|
||||
}
|
||||
$payload = app(ConfigService::class)->applyHomePriceDisplayState($payload);
|
||||
$payload = app(ConfigService::class)->attachTradingHoursSettingsPatch($payload);
|
||||
|
||||
$payload['up'] = $this->buildUpDownPrices();
|
||||
$payload['last_historical'] = app(ProductDisplayPriceService::class)->resolveHistoricalBasePrice();
|
||||
|
||||
+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
|
||||
|
||||
@@ -45,12 +45,7 @@ class LivePriceCacheService
|
||||
return $cachedBody;
|
||||
}
|
||||
|
||||
Cache::put(self::FULL_RESPONSE_KEY, [
|
||||
'body' => $body,
|
||||
'stored_at' => now(),
|
||||
], now()->addSeconds(self::TTL_SECONDS + 5));
|
||||
|
||||
app(ProductDisplayPriceService::class)->cacheLivePricePayload($body);
|
||||
$this->storeBody($body);
|
||||
|
||||
app(PriceBroadcastService::class)->broadcastLastIfChanged($body);
|
||||
|
||||
@@ -59,6 +54,32 @@ class LivePriceCacheService
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* بازنویسی کش last بدون گیت هویت — برای اعمال فوری ساعت کاری.
|
||||
*
|
||||
* @param array<string, mixed> $body
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function replaceBody(array $body): array
|
||||
{
|
||||
$this->storeBody($body);
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $body
|
||||
*/
|
||||
private function storeBody(array $body): void
|
||||
{
|
||||
Cache::put(self::FULL_RESPONSE_KEY, [
|
||||
'body' => $body,
|
||||
'stored_at' => now(),
|
||||
], now()->addSeconds(self::TTL_SECONDS + 5));
|
||||
|
||||
app(ProductDisplayPriceService::class)->cacheLivePricePayload($body);
|
||||
}
|
||||
|
||||
public function markTick(): void
|
||||
{
|
||||
Cache::put(self::LAST_TICK_KEY, now(), now()->addMinutes(10));
|
||||
|
||||
@@ -34,6 +34,7 @@ class PriceBroadcastService
|
||||
}
|
||||
|
||||
if (is_array($lastPayload) && $lastPayload !== []) {
|
||||
$lastPayload = app(ConfigService::class)->applyHomePriceDisplayState($lastPayload);
|
||||
$lastPayload = app(ZaarboxPriceService::class)->attachGoldOunceToPayload($lastPayload);
|
||||
}
|
||||
|
||||
@@ -63,7 +64,7 @@ class PriceBroadcastService
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
* @return array<string, int|bool>
|
||||
* @return array<string, int|bool|string>
|
||||
*/
|
||||
public static function lastPriceIdentity(array $payload): array
|
||||
{
|
||||
@@ -75,6 +76,7 @@ class PriceBroadcastService
|
||||
'price_last' => (int) ($payload['price_last'] ?? 0),
|
||||
'price' => (int) ($payload['price'] ?? 0),
|
||||
'closed' => (bool) ($payload['closed'] ?? false),
|
||||
'status' => (string) ($payload['status'] ?? ''),
|
||||
'main_product' => (int) ($payload['main_product'] ?? 0),
|
||||
'montzar' => (int) ($payload['montzar'] ?? 0),
|
||||
'zaarbox' => (int) ($payload['zaarbox'] ?? 0),
|
||||
@@ -93,6 +95,7 @@ class PriceBroadcastService
|
||||
return;
|
||||
}
|
||||
|
||||
$lastPayload = app(ConfigService::class)->applyHomePriceDisplayState($lastPayload);
|
||||
$lastPayload = app(ZaarboxPriceService::class)->attachGoldOunceToPayload($lastPayload);
|
||||
|
||||
if (! $this->rememberHashIfChanged(self::LAST_HASH_KEY, self::lastPriceIdentity($lastPayload))) {
|
||||
|
||||
@@ -1653,16 +1653,16 @@ UPDATE `settings` SET `description` = 'روشن: نمودار قیمت از تا
|
||||
-- [super-admin can_edit=0]
|
||||
-- UPDATE `settings` SET `description` = 'آدرس سرور(های) فید قیمت (/api/free). تا ۵ آدرس (JSON آرایه) پشتیبانی میشود؛ اگر آدرس اول پاسخ ندهد بهترتیب سراغ بعدی میرود. مقدار تکی قبلی حفظ میشود.' WHERE `key` = 'zaarbox_api_url';
|
||||
UPDATE `settings` SET `description` = 'کلید یا UUID ردیف قیمت در پاسخ API تابلو. باید با محصول/ردیف موردنظر در سرویس خارجی یکسان باشد.' WHERE `key` = 'zaarbox_price_key';
|
||||
UPDATE `settings` SET `description` = 'روشن: اگر API قیمت در دسترس نباشد، آخرین قیمت ذخیرهشده در دیتابیس به کاربر نشان داده میشود. خاموش: در قطعی API ممکن است قیمت صفر نمایش داده شود.' WHERE `key` = 'show_last_price';
|
||||
UPDATE `settings` SET `description` = 'وقتی مظنه زنده نیست (و فروشگاه از نظر ساعت بسته حساب نمیشود): روشن = آخرین قیمت ذخیرهشده نشان داده شود. خاموش = پیام «مظنه در دسترس نیست».' WHERE `key` = 'show_last_price';
|
||||
UPDATE `settings` SET `description` = 'روشن: اگر آدرس API تابلو (zaarbox_api_url) تنظیم شده باشد، قیمت انس طلا در اپ مشتری نمایش داده میشود. خاموش: نمایش داده نمیشود.' WHERE `key` = 'show_gold_ounce_price';
|
||||
UPDATE `settings` SET `description` = 'روشن: وقتی فروشگاه خارج از ساعت کاری است، قیمت صفحه اصلی اپ همچنان نمایش داده میشود و پیام «فروشگاه بسته است» نشان داده نمیشود. خرید/فروش همچنان بسته میماند. خاموش: مثل قبل، خارج از ساعت پیام بسته بودن و قیمت صفر نشان داده میشود.' WHERE `key` = 'show_home_price_when_closed';
|
||||
UPDATE `settings` SET `description` = 'فقط وقتی «بستن خودکار خارج از ساعت کاری» روشن است و الان خارج از بازه هستید. روشن: آخرین قیمت روی صفحه اصلی نشان داده میشود (معامله بسته میماند). خاموش: پیام «فروشگاه بسته است».' WHERE `key` = 'show_home_price_when_closed';
|
||||
UPDATE `settings` SET `description` = 'روشن: بعد از انتخاب محصول در صفحه خرید/فروش، متن توضیح محصول نمایش داده میشود. خاموش: توضیح نشان داده نمیشود.' WHERE `key` = 'show_product_details';
|
||||
UPDATE `settings` SET `description` = 'روشن: در صفحه خرید/فروش (بالای تب دستهها) یک دکمه تمامعرض نشان داده میشود که با کلیک، لینک تنظیمشده را در تب جدید مرورگر باز میکند. خاموش: دکمه مخفی است.' WHERE `key` = 'products_site_link_enabled';
|
||||
UPDATE `settings` SET `description` = 'آدرس کامل سایت یا صفحه خارجی (مثلاً https://example.com). با کلیک روی دکمه در اپ کاربر، همین آدرس در تب جدید مرورگر باز میشود. اگر خالی باشد، با وجود روشن بودن سوییچ، دکمه کاری انجام نمیدهد.' WHERE `key` = 'products_site_link_url';
|
||||
UPDATE `settings` SET `description` = 'متنی که روی دکمه لینک خارجی در صفحه خرید/فروش نمایش داده میشود. اگر خالی باشد، پیشفرض «ورود به سایت» همراه آیکون کره زمین نشان داده میشود.' WHERE `key` = 'products_site_link_text';
|
||||
UPDATE `settings` SET `description` = 'روشن: روی کارت محصول برچسب خرید باز/بسته و فروش باز/بسته نمایش داده میشود. خاموش: مخفی است.' WHERE `key` = 'show_product_trade_status';
|
||||
UPDATE `settings` SET `description` = 'روشن: روی کارت محصول دستهبندی، وضعیت تحویل فیزیکی و مقدار/وزن نمایش داده میشود. خاموش: مخفی است.' WHERE `key` = 'show_product_card_meta';
|
||||
UPDATE `settings` SET `description` = 'روشن: خارج از بازه «ساعت شروع» تا «ساعت پایان» معامله و نمایش قیمت بسته میشود. خاموش: محدودیت ساعتی سراسری اعمال نمیشود.' WHERE `key` = 'close_price';
|
||||
UPDATE `settings` SET `description` = 'سوییچ اصلی ساعت کاری. خاموش: محدودیت ساعت نیست — مظنه زنده یا با «نمایش آخرین قیمت ذخیرهشده» آخرین قیمت، وگرنه «مظنه در دسترس نیست». روشن: داخل بازه همان منطق مظنه؛ خارج از بازه با «نمایش قیمت صفحه اصلی خارج از ساعت کاری» یا پیام «فروشگاه بسته است».' WHERE `key` = 'close_price';
|
||||
UPDATE `settings` SET `description` = 'ساعت شروع فعالیت روزانه. فقط وقتی «بستن خودکار خارج از ساعت کاری» روشن باشد اعمال میشود (مثلاً ۰۹:۰۰).' WHERE `key` = 'startDay';
|
||||
UPDATE `settings` SET `description` = 'ساعت پایان فعالیت روزانه. فقط وقتی «بستن خودکار خارج از ساعت کاری» روشن باشد اعمال میشود (مثلاً ۲۲:۰۰).' WHERE `key` = 'endDay';
|
||||
UPDATE `settings` SET `description` = 'مبلغ اضافه (ریال) روی قیمت پایه برای سمت خرید. خالی یا صفر = بدون اختلاف.' WHERE `key` = 'buy_fee';
|
||||
|
||||
@@ -1359,8 +1359,8 @@ UPDATE `settings` SET `description` = 'روشن: نمودار قیمت از تا
|
||||
-- [super-admin can_edit=0]
|
||||
-- UPDATE `settings` SET `description` = 'آدرس پایه API سرویس تابلو آنلاین (بدون مسیر انتهایی). مثال: https://example.com/api' WHERE `key` = 'zaarbox_api_url';
|
||||
UPDATE `settings` SET `description` = 'کلید یا UUID ردیف قیمت در پاسخ API تابلو. باید با محصول/ردیف موردنظر در سرویس خارجی یکسان باشد.' WHERE `key` = 'zaarbox_price_key';
|
||||
UPDATE `settings` SET `description` = 'روشن: اگر API قیمت در دسترس نباشد، آخرین قیمت ذخیرهشده در دیتابیس به کاربر نشان داده میشود. خاموش: در قطعی API ممکن است قیمت صفر نمایش داده شود.' WHERE `key` = 'show_last_price';
|
||||
UPDATE `settings` SET `description` = 'روشن: وقتی فروشگاه خارج از ساعت کاری است، قیمت صفحه اصلی اپ همچنان نمایش داده میشود و پیام «فروشگاه بسته است» نشان داده نمیشود. خرید/فروش همچنان بسته میماند. خاموش: مثل قبل، خارج از ساعت پیام بسته بودن و قیمت صفر نشان داده میشود.' WHERE `key` = 'show_home_price_when_closed';
|
||||
UPDATE `settings` SET `description` = 'وقتی مظنه زنده نیست (و فروشگاه از نظر ساعت بسته حساب نمیشود): روشن = آخرین قیمت ذخیرهشده نشان داده شود. خاموش = پیام «مظنه در دسترس نیست».' WHERE `key` = 'show_last_price';
|
||||
UPDATE `settings` SET `description` = 'فقط وقتی «بستن خودکار خارج از ساعت کاری» روشن است و الان خارج از بازه هستید. روشن: آخرین قیمت روی صفحه اصلی نشان داده میشود (معامله بسته میماند). خاموش: پیام «فروشگاه بسته است».' WHERE `key` = 'show_home_price_when_closed';
|
||||
UPDATE `settings` SET `description` = 'روشن: بعد از انتخاب محصول در صفحه خرید/فروش، متن توضیح محصول نمایش داده میشود. خاموش: توضیح نشان داده نمیشود.' WHERE `key` = 'show_product_details';
|
||||
UPDATE `settings` SET `description` = 'روشن: در صفحه خرید/فروش (بالای تب دستهها) یک دکمه تمامعرض نشان داده میشود که با کلیک، لینک تنظیمشده را در تب جدید مرورگر باز میکند. خاموش: دکمه مخفی است.' WHERE `key` = 'products_site_link_enabled';
|
||||
UPDATE `settings` SET `description` = 'آدرس کامل سایت یا صفحه خارجی (مثلاً https://example.com). با کلیک روی دکمه در اپ کاربر، همین آدرس در تب جدید مرورگر باز میشود. اگر خالی باشد، با وجود روشن بودن سوییچ، دکمه کاری انجام نمیدهد.' WHERE `key` = 'products_site_link_url';
|
||||
@@ -1376,7 +1376,7 @@ UPDATE `settings` SET `type` = 'switch' WHERE `key` = 'show_product_trade_status
|
||||
UPDATE `settings` SET `description` = 'روشن: روی کارت محصول دستهبندی، وضعیت تحویل فیزیکی و مقدار/وزن نمایش داده میشود. خاموش: مخفی است.' WHERE `key` = 'show_product_card_meta';
|
||||
UPDATE `settings` SET `title_fa` = 'نمایش دسته و تحویل و مقدار روی کارت محصول' WHERE `key` = 'show_product_card_meta';
|
||||
UPDATE `settings` SET `type` = 'switch' WHERE `key` = 'show_product_card_meta';
|
||||
UPDATE `settings` SET `description` = 'روشن: خارج از بازه «ساعت شروع» تا «ساعت پایان» معامله و نمایش قیمت بسته میشود. خاموش: محدودیت ساعتی سراسری اعمال نمیشود.' WHERE `key` = 'close_price';
|
||||
UPDATE `settings` SET `description` = 'سوییچ اصلی ساعت کاری. خاموش: محدودیت ساعت نیست — مظنه زنده یا با «نمایش آخرین قیمت ذخیرهشده» آخرین قیمت، وگرنه «مظنه در دسترس نیست». روشن: داخل بازه همان منطق مظنه؛ خارج از بازه با «نمایش قیمت صفحه اصلی خارج از ساعت کاری» یا پیام «فروشگاه بسته است».' WHERE `key` = 'close_price';
|
||||
UPDATE `settings` SET `description` = 'ساعت شروع فعالیت روزانه. فقط وقتی «بستن خودکار خارج از ساعت کاری» روشن باشد اعمال میشود (مثلاً ۰۹:۰۰).' WHERE `key` = 'startDay';
|
||||
UPDATE `settings` SET `description` = 'ساعت پایان فعالیت روزانه. فقط وقتی «بستن خودکار خارج از ساعت کاری» روشن باشد اعمال میشود (مثلاً ۲۲:۰۰).' WHERE `key` = 'endDay';
|
||||
UPDATE `settings` SET `description` = 'مبلغ اضافه (ریال) روی قیمت پایه برای سمت خرید. خالی یا صفر = بدون اختلاف.' WHERE `key` = 'buy_fee';
|
||||
|
||||
@@ -123,6 +123,7 @@ UPDATE `settings` SET `show_to_user` = 1 WHERE `key` IN (
|
||||
'referral_show',
|
||||
'show_gold_ounce_price',
|
||||
'show_home_price_when_closed',
|
||||
'show_last_price',
|
||||
'show_product_card_meta',
|
||||
'show_product_details',
|
||||
'show_product_trade_status',
|
||||
@@ -137,6 +138,7 @@ UPDATE `settings` SET `show_to_user` = 1 WHERE `key` IN (
|
||||
'text_rule',
|
||||
'tracking_systems',
|
||||
'tracking_systems_enabled',
|
||||
'trading_hours_by_day',
|
||||
'unit',
|
||||
'wss_enabled',
|
||||
'wss_path',
|
||||
|
||||
@@ -88,7 +88,7 @@ class ConfigServiceTradingHoursTest extends TestCase
|
||||
$this->assertTrue($cfg->isOutsideTradingHours($fridayNoon));
|
||||
}
|
||||
|
||||
public function test_apply_global_trading_hours_zeros_payload_when_closed(): void
|
||||
public function test_apply_global_trading_hours_marks_closed_without_hiding_status_when_home_price_off(): void
|
||||
{
|
||||
$weekly = json_encode([
|
||||
'mode' => 'per_day',
|
||||
@@ -100,6 +100,7 @@ class ConfigServiceTradingHoursTest extends TestCase
|
||||
$cfg = $this->cfg([
|
||||
'close_price' => 'true',
|
||||
'show_home_price_when_closed' => 'false',
|
||||
'show_last_price' => 'false',
|
||||
'startDay' => '08:30',
|
||||
'endDay' => '21:30',
|
||||
'trading_hours_by_day' => $weekly,
|
||||
@@ -108,7 +109,7 @@ class ConfigServiceTradingHoursTest extends TestCase
|
||||
$now = Carbon::create(2026, 8, 24, 21, 15, 0);
|
||||
Carbon::setTestNow($now);
|
||||
|
||||
$result = $cfg->applyGlobalTradingHoursToPayload([
|
||||
$result = $cfg->applyHomePriceDisplayState([
|
||||
'sell' => 100,
|
||||
'buy' => 110,
|
||||
'price' => 105,
|
||||
@@ -118,8 +119,91 @@ class ConfigServiceTradingHoursTest extends TestCase
|
||||
|
||||
$this->assertTrue($result['closed']);
|
||||
$this->assertSame('closed', $result['status']);
|
||||
$this->assertSame(0, $result['price']);
|
||||
|
||||
Carbon::setTestNow();
|
||||
}
|
||||
|
||||
public function test_outside_hours_shows_last_price_when_home_price_when_closed_is_on(): void
|
||||
{
|
||||
$cfg = $this->cfg([
|
||||
'close_price' => 'true',
|
||||
'show_home_price_when_closed' => 'true',
|
||||
'show_last_price' => 'false',
|
||||
'startDay' => '11:00',
|
||||
'endDay' => '20:30',
|
||||
]);
|
||||
|
||||
$now = Carbon::create(2026, 8, 25, 2, 3, 0);
|
||||
Carbon::setTestNow($now);
|
||||
|
||||
$result = $cfg->applyHomePriceDisplayState([
|
||||
'sell' => 168000000,
|
||||
'buy' => 168798112,
|
||||
'price' => 168798112,
|
||||
'closed' => false,
|
||||
'status' => 'open',
|
||||
]);
|
||||
|
||||
$this->assertTrue($result['closed']);
|
||||
$this->assertSame('open', $result['status']);
|
||||
$this->assertSame(168798112, $result['buy']);
|
||||
|
||||
Carbon::setTestNow();
|
||||
}
|
||||
|
||||
public function test_close_price_off_with_no_quote_is_no_quote(): void
|
||||
{
|
||||
$cfg = $this->cfg([
|
||||
'close_price' => 'false',
|
||||
'show_last_price' => 'false',
|
||||
'show_home_price_when_closed' => 'false',
|
||||
'startDay' => '11:00',
|
||||
'endDay' => '20:30',
|
||||
]);
|
||||
|
||||
$result = $cfg->applyHomePriceDisplayState([
|
||||
'sell' => 0,
|
||||
'buy' => 0,
|
||||
'price' => 0,
|
||||
'closed' => false,
|
||||
]);
|
||||
|
||||
$this->assertFalse($result['closed']);
|
||||
$this->assertSame('no_quote', $result['status']);
|
||||
}
|
||||
|
||||
public function test_inside_hours_with_quote_is_open(): void
|
||||
{
|
||||
$cfg = $this->cfg([
|
||||
'close_price' => 'true',
|
||||
'show_last_price' => 'false',
|
||||
'show_home_price_when_closed' => 'false',
|
||||
'startDay' => '11:00',
|
||||
'endDay' => '20:30',
|
||||
]);
|
||||
|
||||
$now = Carbon::create(2026, 8, 25, 12, 0, 0);
|
||||
Carbon::setTestNow($now);
|
||||
|
||||
$result = $cfg->applyHomePriceDisplayState([
|
||||
'sell' => 100,
|
||||
'buy' => 110,
|
||||
'price' => 105,
|
||||
]);
|
||||
|
||||
$this->assertFalse($result['closed']);
|
||||
$this->assertSame('open', $result['status']);
|
||||
|
||||
Carbon::setTestNow();
|
||||
}
|
||||
|
||||
public function test_trading_hours_setting_keys(): void
|
||||
{
|
||||
$cfg = new ConfigService();
|
||||
|
||||
$this->assertTrue($cfg->isTradingHoursSettingKey('close_price'));
|
||||
$this->assertTrue($cfg->isTradingHoursSettingKey('trading_hours_by_day'));
|
||||
$this->assertTrue($cfg->isTradingHoursSettingKey('show_home_price_when_closed'));
|
||||
$this->assertFalse($cfg->isTradingHoursSettingKey('name_fa'));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user