added some keys into setting

This commit is contained in:
2026-07-07 14:34:00 +03:30
parent 20a884a874
commit d2843d79a0
5 changed files with 488 additions and 3 deletions
@@ -262,6 +262,7 @@ class ProductController extends SuperController
dispatch(function () {
app(PriceBroadcastService::class)->broadcastAfterPriceUpdate();
app(\App\Services\PriceChannelBroadcastService::class)->broadcastIfEligible();
})->afterResponse();
return response()->json(true);
+6 -1
View File
@@ -86,7 +86,12 @@ class LivePriceTickService
app(LivePriceCacheService::class)->markTick();
// last از put داخل lastPrice؛ list بعد از sync محصولات
app(PriceBroadcastService::class)->broadcastListIfChanged();
$listChanged = app(PriceBroadcastService::class)->broadcastListIfChanged();
// بعد از هر تغییر قیمت، قیمت‌ها را به کانال‌های پیام‌رسان بفرست (با cooldown داخلی)
if ($listChanged) {
app(PriceChannelBroadcastService::class)->broadcastIfEligible();
}
} finally {
$lock->release();
}
@@ -0,0 +1,323 @@
<?php
namespace App\Services;
use App\Http\Controllers\Controller;
use App\Models\Product;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
/**
* ارسال قیمت لحظه‌ای به کانال‌های پیام‌رسان (بله، تلگرام، روبیکا).
*
* اصول (طبق rule ساعت کاری):
* • هیچ تنظیمی را بازنویسی نمی‌کند — فقط می‌خواند.
* • فقط پیام‌رسان‌هایی که ادمین انتخاب کرده و توکن + مقصد دارند ارسال می‌شود.
* • قیمت هر محصول از قیمت پایه ذخیره‌شده + کارمزد نمایش همان اپ ساخته می‌شود.
*/
class PriceChannelBroadcastService
{
private const COOLDOWN_CACHE_KEY = 'price_channel:last_sent_at';
private const SUPPORTED_MESSENGERS = ['bale', 'telegram', 'rubika'];
/** مثقال بازار آبشده (گرم) — وقتی تنظیم change_mesghal خالی باشد */
private const DEFAULT_CHANGE_MESGHAL = 4.3318;
public function __construct(private readonly ConfigService $config)
{
}
/**
* نقطه‌ی ورود — بعد از تغییر قیمت صدا زده می‌شود.
* فقط وقتی قابلیت روشن است، پیام‌رسانی انتخاب شده و فاصله‌ی مجاز گذشته باشد ارسال می‌کند.
*/
public function broadcastIfEligible(): void
{
try {
if (! $this->config->getBool('price_channel_enabled', false)) {
return;
}
$targets = $this->resolveActiveTargets();
if ($targets === []) {
return;
}
if (! $this->cooldownElapsed()) {
return;
}
$message = $this->buildMessage();
if ($message === '') {
return;
}
foreach ($targets as $messenger => $target) {
$this->dispatchToMessenger($messenger, $target['token'], $target['chat_ids'], $message);
}
$this->markSent();
} catch (\Throwable $e) {
Log::warning('PriceChannelBroadcast failed', ['message' => $e->getMessage()]);
}
}
/**
* پیام‌رسان‌های قابل ارسال: انتخاب‌شده + توکن معتبر + حداقل یک مقصد.
*
* @return array<string, array{token: string, chat_ids: array<int, string>}>
*/
private function resolveActiveTargets(): array
{
$selected = $this->selectedMessengers();
if ($selected === []) {
return [];
}
$targets = [];
foreach ($selected as $messenger) {
$token = trim((string) $this->config->getString($this->tokenKey($messenger), ''));
$chatIds = $this->parseList($this->config->getString($this->chatIdsKey($messenger), ''));
if ($token === '' || $chatIds === []) {
continue;
}
$targets[$messenger] = ['token' => $token, 'chat_ids' => $chatIds];
}
return $targets;
}
/** @return array<int, string> */
private function selectedMessengers(): array
{
$raw = $this->parseList($this->config->getString('price_channel_messengers', ''));
return array_values(array_filter(
array_map(static fn ($m) => strtolower(trim($m)), $raw),
static fn ($m) => in_array($m, self::SUPPORTED_MESSENGERS, true)
));
}
private function tokenKey(string $messenger): string
{
return match ($messenger) {
'bale' => 'bale_bot_token',
'telegram' => 'telegram_bot_token',
'rubika' => 'rubika_bot_token',
default => '',
};
}
private function chatIdsKey(string $messenger): string
{
return "price_channel_{$messenger}_to";
}
/**
* ساخت متن پیام قیمت — مرتب و بر اساس داده‌ی هر محصول.
*/
public function buildMessage(): string
{
$products = $this->eligibleProducts();
if ($products === []) {
return '';
}
$channelDiff = (float) $this->config->getInt('price_channel_diff', 0);
$diffBuy = (float) $this->config->displayBuyPriceDiffRial();
$diffSell = (float) $this->config->displaySellPriceDiffRial();
$mesghal = $this->changeMesghal();
$lines = [];
$lines[] = $this->header();
$lines[] = '';
foreach ($products as $product) {
$base = (float) ($product->price ?? 0);
// 🔴 خرید (مشتری می‌فروشد، پایین‌تر) — 🟢 فروش (مشتری می‌خرد، بالاتر)
$low = $base - $diffSell - $channelDiff;
$high = $base + $diffBuy + $channelDiff;
$lines[] = '🛒 ' . trim((string) $product->title);
$lines[] = '🔴 خرید ' . $this->formatPrice($low) . ' 🟢 فروش ' . $this->formatPrice($high);
if ($this->isGramUnit($product) && $mesghal > 0) {
$lines[] = '💰 هر گرم: ' . $this->formatPrice($low / $mesghal)
. ' / ' . $this->formatPrice($high / $mesghal);
}
$lines[] = '';
}
$footer = $this->footerLines();
if ($footer !== []) {
$lines[] = '➖➖➖➖➖➖';
foreach ($footer as $line) {
$lines[] = $line;
}
}
return trim(implode("\n", $lines));
}
private function header(): string
{
$name = trim((string) $this->config->getString('name_fa', ''));
$time = now()->format('H:i');
$title = $name !== '' ? "📊 قیمت لحظه‌ای {$name}" : '📊 قیمت لحظه‌ای';
return $title . "\n🕐 ساعت " . $time;
}
/**
* @return array<int, Product>
*/
private function eligibleProducts(): array
{
$products = Product::query()
->where('active', 1)
->orderBy('id')
->get();
$eligible = [];
foreach ($products as $product) {
if ((int) ($product->price ?? 0) <= 0) {
continue;
}
if (! ($product->open_buy || $product->open_sell)) {
continue;
}
if (! $this->config->isProductWithinTradingHours($product)) {
continue;
}
$eligible[] = $product;
}
return $eligible;
}
private function isGramUnit(Product $product): bool
{
return trim((string) ($product->unit ?? '')) === 'گرم';
}
/** قیمت را به واحد نمایش (تقسیم بر ۱۰٬۰۰۰) با جداکننده‌ی هزارگان می‌برد */
private function formatPrice(float $value): string
{
return number_format(max(0, $value) / 10000, 0);
}
private function changeMesghal(): float
{
$fromConfig = $this->config->get('change_mesghal');
$value = (float) ($fromConfig ?: config('change_mesghal', self::DEFAULT_CHANGE_MESGHAL));
return $value > 0 ? $value : self::DEFAULT_CHANGE_MESGHAL;
}
/**
* خطوط پایانی — فقط مقادیر پرشده نمایش داده می‌شوند (آیدی چنل، شماره، لینک).
*
* @return array<int, string>
*/
private function footerLines(): array
{
$lines = [];
foreach (['price_channel_footer_id', 'price_channel_footer_phone', 'price_channel_footer_link'] as $key) {
$value = trim((string) $this->config->getString($key, ''));
if ($value !== '') {
$lines[] = $value;
}
}
return $lines;
}
private function dispatchToMessenger(string $messenger, string $token, array $chatIds, string $message): void
{
foreach ($chatIds as $chatId) {
try {
match ($messenger) {
'bale' => $this->sendBale($token, $chatId, $message),
'telegram' => $this->sendTelegram($token, $chatId, $message),
'rubika' => $this->sendRubika($token, $chatId, $message),
default => null,
};
} catch (\Throwable $e) {
Log::warning("PriceChannel {$messenger} send failed", [
'chat_id' => $chatId,
'message' => $e->getMessage(),
]);
}
}
}
private function sendBale(string $token, string $chatId, string $message): void
{
Http::withOptions(Controller::httpClientOptions())
->withoutVerifying()
->timeout(6)
->post("https://tapi.bale.ai/bot{$token}/sendMessage", [
'chat_id' => $chatId,
'text' => $message,
]);
}
private function sendTelegram(string $token, string $chatId, string $message): void
{
Http::withOptions(Controller::httpClientOptions())
->timeout(6)
->post("https://api.telegram.org/bot{$token}/sendMessage", [
'chat_id' => $chatId,
'text' => $message,
]);
}
private function sendRubika(string $token, string $chatId, string $message): void
{
Http::withOptions(Controller::httpClientOptions())
->withoutVerifying()
->timeout(6)
->post("https://botapi.rubika.ir/v3/{$token}/sendMessage", [
'chat_id' => $chatId,
'text' => $message,
]);
}
/** حداقل فاصله‌ی مجاز بین دو ارسال — جلوگیری از سیل پیام در هر تیک ۱۰ثانیه‌ای */
private function cooldownElapsed(): bool
{
$minSeconds = max(0, $this->config->getInt('price_channel_min_seconds', 120));
if ($minSeconds === 0) {
return true;
}
$lastSent = Cache::get(self::COOLDOWN_CACHE_KEY);
if ($lastSent === null) {
return true;
}
return now()->timestamp - (int) $lastSent >= $minSeconds;
}
private function markSent(): void
{
Cache::put(self::COOLDOWN_CACHE_KEY, now()->timestamp, now()->addDay());
}
/** @return array<int, string> */
private function parseList(string $raw): array
{
$parts = preg_split('/[,\n]+/', trim($raw)) ?: [];
return array_values(array_filter(array_map('trim', $parts), static fn ($p) => $p !== ''));
}
}
+71 -2
View File
@@ -343,6 +343,71 @@ INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `de
SELECT 'bale_bot_token', '', 'توکن بات بله', 0, 1, NULL
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'bale_bot_token' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`)
SELECT 'rubika_bot_token', '', 'توکن بات روبیکا', 0, 1, NULL
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'rubika_bot_token' LIMIT 1);
-- ===========================================================================
-- ارسال قیمت به کانال‌های پیام‌رسان (بله / تلگرام / روبیکا)
-- • محتوا (can_edit=1): روشن/خاموش، انتخاب پیام‌رسان، اختلاف، خطوط پایانی، فاصله
-- • سیستمی (can_edit=0): مقصد ارسال هر پیام‌رسان (آیدی چنل/گروه)
-- • توکن‌ها همان bale_bot_token / telegram_bot_token / rubika_bot_token (can_edit=0)
-- ===========================================================================
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_enabled', 'false', 'ارسال قیمت به کانال', 1, 1,
'روشن: بعد از هر تغییر قیمت، مظنه به کانال‌های پیام‌رسان انتخاب‌شده ارسال می‌شود.', 'switch'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_enabled' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_messengers', '', 'پیام‌رسان‌های ارسال قیمت', 1, 1,
'کدام پیام‌رسان‌ها قیمت بگیرند؟ با ویرگول جدا کنید. گزینه‌ها: bale,telegram,rubika', 'text'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_messengers' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_diff', '0', 'اختلاف قیمت کانال (ریال)', 1, 1,
'مبلغی که به قیمت خرید کانال اضافه و از قیمت فروش کم می‌شود (اسپرد اضافه فقط برای کانال).', 'price'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_diff' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_min_seconds', '120', 'حداقل فاصله ارسال (ثانیه)', 1, 1,
'حداقل فاصله بین دو ارسال به کانال؛ جلوی سیل پیام را می‌گیرد. صفر = هر تغییر قیمت.', 'number'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_min_seconds' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_footer_id', '', 'آیدی کانال در پیام', 1, 1,
'اگر پر باشد در انتهای پیام نمایش داده می‌شود (مثلاً یوزرنیم کانال). خالی = نمایش داده نمی‌شود.', 'text'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_footer_id' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_footer_phone', '', 'شماره تماس در پیام', 1, 1,
'اگر پر باشد در انتهای پیام نمایش داده می‌شود. خالی = نمایش داده نمی‌شود.', 'text'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_footer_phone' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_footer_link', '', 'لینک در پیام', 1, 1,
'اگر پر باشد در انتهای پیام نمایش داده می‌شود. خالی = نمایش داده نمی‌شود.', 'text'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_footer_link' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_bale_to', '', 'مقصد ارسال بله', 0, 1,
'آیدی چنل/گروه بله برای ارسال قیمت (چند مقصد با ویرگول).', 'text'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_bale_to' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_telegram_to', '', 'مقصد ارسال تلگرام', 0, 1,
'آیدی چنل/گروه تلگرام برای ارسال قیمت (چند مقصد با ویرگول).', 'text'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_telegram_to' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_rubika_to', '', 'مقصد ارسال روبیکا', 0, 1,
'آیدی چنل/گروه روبیکا برای ارسال قیمت (چند مقصد با ویرگول).', 'text'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_rubika_to' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'change_mesghal', '4.3318', 'وزن مثقال (گرم)', 1, 1,
'ضریب تبدیل قیمت مثقال به گرم برای نمایش خط «هر گرم» در پیام کانال.', 'number'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'change_mesghal' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`)
SELECT 'support_messenger_id', '', 'شناسه پشتیبانی پیام‌رسان', 1, 0, 'یوزرنیم یا شماره تماس بسته به پیام‌رسان انتخاب‌شده'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'support_messenger_id' LIMIT 1);
@@ -1191,7 +1256,8 @@ UPDATE `settings` SET `can_edit` = 0 WHERE `key` IN (
'send_to_tahesab', 'service_charge_percentage',
'sse_path',
'tahesab_bank_account', 'tahesab_db', 'tahesab_host', 'tahesab_port', 'tahesab_token',
'telegram_bot_token',
'telegram_bot_token', 'rubika_bot_token',
'price_channel_bale_to', 'price_channel_telegram_to', 'price_channel_rubika_to',
'unit',
'up_price', 'down_price',
'wss_enabled',
@@ -1217,7 +1283,10 @@ UPDATE `settings` SET `can_edit` = 1 WHERE `key` IN (
'register_jibit_verify', 'register_referral_bonus_message', 'register_referral_sms_to_referrer',
'sell_fee', 'show_last_price', 'startDay',
'support_messenger', 'support_messenger_id', 'support_messenger_text',
'text_about', 'text_rule'
'text_about', 'text_rule',
'price_channel_enabled', 'price_channel_messengers', 'price_channel_diff',
'price_channel_min_seconds', 'price_channel_footer_id', 'price_channel_footer_phone',
'price_channel_footer_link', 'change_mesghal'
);
-- تفکیک نهایی type (بعد از همه batchها — override روی دیتابیس موجود)
+87
View File
@@ -0,0 +1,87 @@
-- =============================================================================
-- price_channel_settings.sql — کلیدهای «ارسال قیمت به کانال» برای نصب‌های موجود
--
-- این کلیدها در deploy_settings_const.sql هم هستند (روی هر deploy اجرا می‌شوند).
-- این فایل فقط برای افزودن سریع به یک DB موجود، بدون اجرای کل deploy، است.
--
-- قبل از اجرا: USE `نام_دیتابیس`; + بکاپ
-- idempotent — چند بار قابل اجرا (value ردیف موجود دست نمی‌خورد).
-- =============================================================================
START TRANSACTION;
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
-- توکن روبیکا (توکن بله/تلگرام از قبل موجودند) — سیستمی (can_edit=0)
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`)
SELECT 'rubika_bot_token', '', 'توکن بات روبیکا', 0, 1, NULL
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'rubika_bot_token' LIMIT 1);
-- محتوا — قابل ویرایش (can_edit=1)
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_enabled', 'false', 'ارسال قیمت به کانال', 1, 1,
'روشن: بعد از هر تغییر قیمت، مظنه به کانال‌های پیام‌رسان انتخاب‌شده ارسال می‌شود.', 'switch'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_enabled' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_messengers', '', 'پیام‌رسان‌های ارسال قیمت', 1, 1,
'کدام پیام‌رسان‌ها قیمت بگیرند؟ با ویرگول جدا کنید. گزینه‌ها: bale,telegram,rubika', 'text'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_messengers' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_diff', '0', 'اختلاف قیمت کانال (ریال)', 1, 1,
'مبلغی که به قیمت خرید کانال اضافه و از قیمت فروش کم می‌شود (اسپرد اضافه فقط برای کانال).', 'price'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_diff' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_min_seconds', '120', 'حداقل فاصله ارسال (ثانیه)', 1, 1,
'حداقل فاصله بین دو ارسال به کانال؛ جلوی سیل پیام را می‌گیرد. صفر = هر تغییر قیمت.', 'number'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_min_seconds' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_footer_id', '', 'آیدی کانال در پیام', 1, 1,
'اگر پر باشد در انتهای پیام نمایش داده می‌شود (مثلاً یوزرنیم کانال). خالی = نمایش داده نمی‌شود.', 'text'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_footer_id' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_footer_phone', '', 'شماره تماس در پیام', 1, 1,
'اگر پر باشد در انتهای پیام نمایش داده می‌شود. خالی = نمایش داده نمی‌شود.', 'text'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_footer_phone' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_footer_link', '', 'لینک در پیام', 1, 1,
'اگر پر باشد در انتهای پیام نمایش داده می‌شود. خالی = نمایش داده نمی‌شود.', 'text'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_footer_link' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'change_mesghal', '4.3318', 'وزن مثقال (گرم)', 1, 1,
'ضریب تبدیل قیمت مثقال به گرم برای نمایش خط «هر گرم» در پیام کانال.', 'number'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'change_mesghal' LIMIT 1);
-- مقصد ارسال هر پیام‌رسان (آیدی چنل/گروه) — سیستمی (can_edit=0)
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_bale_to', '', 'مقصد ارسال بله', 0, 1,
'آیدی چنل/گروه بله برای ارسال قیمت (چند مقصد با ویرگول).', 'text'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_bale_to' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_telegram_to', '', 'مقصد ارسال تلگرام', 0, 1,
'آیدی چنل/گروه تلگرام برای ارسال قیمت (چند مقصد با ویرگول).', 'text'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_telegram_to' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'price_channel_rubika_to', '', 'مقصد ارسال روبیکا', 0, 1,
'آیدی چنل/گروه روبیکا برای ارسال قیمت (چند مقصد با ویرگول).', 'text'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_rubika_to' LIMIT 1);
-- استانداردسازی can_edit
UPDATE `settings` SET `can_edit` = 0 WHERE `key` IN (
'rubika_bot_token',
'price_channel_bale_to', 'price_channel_telegram_to', 'price_channel_rubika_to'
);
UPDATE `settings` SET `can_edit` = 1 WHERE `key` IN (
'price_channel_enabled', 'price_channel_messengers', 'price_channel_diff',
'price_channel_min_seconds', 'price_channel_footer_id', 'price_channel_footer_phone',
'price_channel_footer_link', 'change_mesghal'
);
COMMIT;