feat(settings): enable_automation gate; reject stale montzar quote jumps
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,6 +6,7 @@ use App\Models\Automation;
|
||||
use App\Models\AutomationDelayedJob;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Product;
|
||||
use App\Services\ConfigService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@@ -19,12 +20,18 @@ class AutomationEngine
|
||||
|
||||
public function runDue(): void
|
||||
{
|
||||
if (! app(ConfigService::class)->isAutomationEnabled()) {
|
||||
return;
|
||||
}
|
||||
$this->runSchedule();
|
||||
$this->processDelayedJobs();
|
||||
}
|
||||
|
||||
public function onCustomerEvent(string $event, int $customerId): void
|
||||
{
|
||||
if (! app(ConfigService::class)->isAutomationEnabled()) {
|
||||
return;
|
||||
}
|
||||
$customer = Customer::query()->find($customerId);
|
||||
if (! $customer || (int) $customer->type !== 2 || $customer->is_archive) {
|
||||
return;
|
||||
@@ -52,6 +59,9 @@ class AutomationEngine
|
||||
|
||||
public function evaluatePriceMove(array $payload = []): void
|
||||
{
|
||||
if (! app(ConfigService::class)->isAutomationEnabled()) {
|
||||
return;
|
||||
}
|
||||
$rows = Automation::query()
|
||||
->where('is_active', true)
|
||||
->where('trigger_type', AutomationCatalog::TRIGGER_PRICE)
|
||||
|
||||
@@ -14,11 +14,44 @@ class ConfigService
|
||||
{
|
||||
public const PRICE_PROVIDERS = ['montzar', 'main_product', 'zaarbox'];
|
||||
|
||||
/**
|
||||
* حداکثر انحراف مجاز مظنه نسبت به آخرین قیمت معتبر DB.
|
||||
* پرش اشتباه provider (مثل ۱۶۸M ماندهٔ montzar روی مظنهٔ ~۲۲۰M) را رد میکند.
|
||||
*/
|
||||
public const MAX_QUOTE_DEVIATION_RATIO = 0.15;
|
||||
|
||||
public function Config($name)
|
||||
{
|
||||
return $this->get($name, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* آخرین قیمت ذخیرهشده در جدول prices — مرجع سلامت مظنهٔ زنده.
|
||||
*/
|
||||
public function referenceQuotePrice(): float
|
||||
{
|
||||
$last = Price::query()->orderByDesc('id')->value('price');
|
||||
|
||||
return $last !== null ? (float) $last : 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* مظنهٔ زنده وقتی خیلی از آخرین قیمت DB فاصله دارد قابل اتکا نیست (ماندهٔ provider اشتباه).
|
||||
*/
|
||||
public function isPlausibleQuotePrice(float $candidate, ?float $reference = null): bool
|
||||
{
|
||||
if ($candidate <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$reference = $reference ?? $this->referenceQuotePrice();
|
||||
if ($reference <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return abs($candidate - $reference) / $reference <= self::MAX_QUOTE_DEVIATION_RATIO;
|
||||
}
|
||||
|
||||
/**
|
||||
* مقدار setting؛ اول سوپرادمین، بعد DB لوکال، بعد price_providers.php.
|
||||
* برای کلیدهای can_edit=0 همیشه از این مسیر (یا ConfigService) بخوانید — نه Setting::where مستقیم.
|
||||
@@ -281,7 +314,8 @@ class ConfigService
|
||||
* اولویت:
|
||||
* 1) price_provider (یا price_primary_source قدیمی) — فقط مقادیر معتبر
|
||||
* 2) main_product_price=true → main_product (legacy)
|
||||
* 3) پیشفرض فایل: montzar
|
||||
* 3) اگر zaarbox_price_key تنظیم شده → zaarbox (نه پیشفرض montzar)
|
||||
* 4) پیشفرض فایل
|
||||
*/
|
||||
public function priceProvider(): string
|
||||
{
|
||||
@@ -296,6 +330,10 @@ class ConfigService
|
||||
return 'main_product';
|
||||
}
|
||||
|
||||
if ($this->getString('zaarbox_price_key', '') !== '') {
|
||||
return 'zaarbox';
|
||||
}
|
||||
|
||||
$default = strtolower((string) config('price_providers.price_provider', 'montzar'));
|
||||
|
||||
return in_array($default, self::PRICE_PROVIDERS, true) ? $default : 'montzar';
|
||||
@@ -473,6 +511,13 @@ class ConfigService
|
||||
$today = now()->toDateString();
|
||||
$extremeDate = $this->Config('price_extreme_date');
|
||||
|
||||
$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) {
|
||||
$this->UpdateConfig('up_price', $priceInt);
|
||||
$this->UpdateConfig('down_price', $priceInt);
|
||||
@@ -480,9 +525,6 @@ class ConfigService
|
||||
return;
|
||||
}
|
||||
|
||||
$currentUp = (float) ($upSetting->value ?: 0);
|
||||
$currentDown = (float) ($downSetting->value ?: 0);
|
||||
|
||||
if ($currentUp <= 0 || $priceInt > $currentUp) {
|
||||
$this->UpdateConfig('up_price', $priceInt);
|
||||
}
|
||||
@@ -533,8 +575,21 @@ 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) {
|
||||
if ($down > 0 && $down < $storedDown && $this->isPlausibleQuotePrice((float) $down, (float) max($up, 1))) {
|
||||
$this->UpdateConfig('down_price', $down);
|
||||
}
|
||||
|
||||
@@ -629,8 +684,9 @@ class ConfigService
|
||||
public function applyMontzarSyncFailure(): void
|
||||
{
|
||||
$staleLive = $this->getInt('montzar_live_price', 0);
|
||||
$usable = $staleLive > 0 && $this->isPlausibleQuotePrice((float) $staleLive);
|
||||
|
||||
$this->UpdateConfig('montzar_closed', $staleLive > 0 ? 'false' : 'true');
|
||||
$this->UpdateConfig('montzar_closed', $usable ? 'false' : 'true');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -817,6 +873,12 @@ class ConfigService
|
||||
return $this->getBool('enable_conditional_order', false);
|
||||
}
|
||||
|
||||
/** سوییچ سوپرادمین اتوماسیون ادمین (پیشفرض خاموش). */
|
||||
public function isAutomationEnabled(): bool
|
||||
{
|
||||
return $this->getBool('enable_automation', false);
|
||||
}
|
||||
|
||||
/** سوییچ دیپلوی قلک per-product (can_edit=0، پیشفرض خاموش). */
|
||||
public function isProductFundsEnabled(): bool
|
||||
{
|
||||
|
||||
@@ -104,11 +104,14 @@ class LivePriceCacheService
|
||||
|
||||
if ($provider === 'montzar') {
|
||||
$liveMontzar = (float) $config->get('montzar_live_price', 0);
|
||||
if ($liveMontzar > 0) {
|
||||
if ($liveMontzar > 0 && $config->isPlausibleQuotePrice($liveMontzar)) {
|
||||
$base = $liveMontzar;
|
||||
$montzarSource = (int) $liveMontzar;
|
||||
$priceSource = 'montzar';
|
||||
} else {
|
||||
$montzarSource = 0;
|
||||
$priceSource = 'database_last';
|
||||
}
|
||||
$montzarSource = (int) ($liveMontzar > 0 ? $liveMontzar : $base);
|
||||
$priceSource = 'montzar';
|
||||
} elseif ($provider === 'zaarbox') {
|
||||
$zaarboxSource = (int) $base;
|
||||
} elseif ($provider === 'main_product') {
|
||||
|
||||
@@ -23,12 +23,14 @@ class SuperAdminProgramSettingsOverlay
|
||||
private const LAST_GOOD_CACHE_MINUTES = 1440;
|
||||
|
||||
/**
|
||||
* تنها کلیدهایی که مالکشان سوپرادمین است — گروه «دسترسی مشتری به قابلیتها».
|
||||
* هر کلید دیگری مالکش جدول settings همین نصب است و در overlay دور ریخته میشود.
|
||||
* کلیدهایی که فقط سوپرادمین ویرایش میکند (پنل خانگی 403).
|
||||
* این لیست فیلتر overlay نیست — برای load از blacklist زیر استفاده میشود
|
||||
* تا مثلاً price_provider از سوپرادمین/کش last-good حذف نشود.
|
||||
*/
|
||||
public const SUPERADMIN_OWNED_KEYS = [
|
||||
'customer_direct_sms',
|
||||
'customer_special_pricing',
|
||||
'enable_automation',
|
||||
'enable_conditional_order',
|
||||
'enable_customer_level_profit',
|
||||
'enable_product_funds',
|
||||
@@ -42,7 +44,7 @@ class SuperAdminProgramSettingsOverlay
|
||||
'show_to_users_chat',
|
||||
];
|
||||
|
||||
/** کلیدهایی که هنوز صفحهٔ اختصاصی ادمین خانگی مالکشان است */
|
||||
/** کلیدهایی که هنوز صفحهٔ اختصاصی ادمین خانگی مالکشان است — از overlay دور انداخته میشوند */
|
||||
public const LOCAL_OWNED_KEYS = [
|
||||
'tracking_systems',
|
||||
];
|
||||
@@ -134,7 +136,9 @@ class SuperAdminProgramSettingsOverlay
|
||||
continue;
|
||||
}
|
||||
$key = trim((string) ($row['key'] ?? ''));
|
||||
if ($key === '' || ! self::isSuperAdminOwned($key)) {
|
||||
// blacklist: فقط کلیدهای لوکال/runtime را دور بریز — نه whitelist قابلیتها
|
||||
// (whitelist قبلی price_provider را حذف میکرد و با قطعی SA به montzar لوکال میپرید)
|
||||
if ($key === '' || self::isLocalOwned($key) || self::isRuntime($key)) {
|
||||
continue;
|
||||
}
|
||||
$map[$key] = $row;
|
||||
|
||||
Reference in New Issue
Block a user