fallback handled

This commit is contained in:
2026-06-17 16:13:18 +03:30
parent 34298d3bc5
commit cc5808e755
7 changed files with 232 additions and 2 deletions
@@ -460,6 +460,12 @@ class ProductController extends SuperController
$data['use_last_price_if_zero'] = !empty($data['use_last_price_if_zero']);
}
if (!Product::hasTradeByQuantityColumn()) {
unset($data['trade_by_quantity']);
} elseif (array_key_exists('trade_by_quantity', $data)) {
$data['trade_by_quantity'] = !empty($data['trade_by_quantity']);
}
if (!Product::hasDirectPriceColumn()) {
unset($data['direct_price']);
}
+10
View File
@@ -451,6 +451,16 @@ class Controller extends BaseController
return (float) $product['amount'] > 0;
}
/** معامله مقداری: فقط فیلد trade_by_quantity */
public static function usesQuantityTrade($product): bool
{
$value = is_array($product)
? ($product['trade_by_quantity'] ?? null)
: (is_object($product) ? ($product->trade_by_quantity ?? null) : null);
return !empty($value);
}
/** ضریب قیمت محصول — بدون وزن قطعه همیشه ۱ */
public static function productPriceMultiplier($product): float
{
+25
View File
@@ -21,6 +21,7 @@ class Product extends Model
private static ?bool $showInAssetsColumnExists = null;
private static ?bool $useLastPriceIfZeroColumnExists = null;
private static ?bool $directPriceColumnExists = null;
private static ?bool $tradeByQuantityColumnExists = null;
protected static function booted(): void
{
@@ -44,6 +45,9 @@ class Product extends Model
if (!static::hasDirectPriceColumn()) {
unset($product->attributes['direct_price']);
}
if (!static::hasTradeByQuantityColumn()) {
unset($product->attributes['trade_by_quantity']);
}
});
}
@@ -153,6 +157,24 @@ class Product extends Model
return static::$directPriceColumnExists;
}
public static function hasTradeByQuantityColumn(): bool
{
if (static::$tradeByQuantityColumnExists !== null) {
return static::$tradeByQuantityColumnExists;
}
try {
static::$tradeByQuantityColumnExists = Schema::hasColumn(
(new static)->getTable(),
'trade_by_quantity'
);
} catch (\Throwable) {
static::$tradeByQuantityColumnExists = false;
}
return static::$tradeByQuantityColumnExists;
}
public function orderLimitsEnabled(): bool
{
if (!static::hasOrderLimitColumns() || !($this->order_limit_enabled ?? false)) {
@@ -204,6 +226,7 @@ class Product extends Model
'for_barayand', // Whether it is for Barayand (boolean).
'show', // Whether to show the record (boolean).
'show_in_assets', // Show balance on customer assets page cards
'trade_by_quantity', // UI معامله مقداری: ارزش کل + مقدار
'use_last_price_if_zero', // Keep last DB price when automatic provider returns zero
'open_sell', // Whether selling is open (boolean).
'auto_open_at', // Whether selling is open (boolean).
@@ -230,6 +253,7 @@ class Product extends Model
protected $attributes = [
'show_in_assets' => false,
'trade_by_quantity' => false,
'use_last_price_if_zero' => false,
];
@@ -238,6 +262,7 @@ class Product extends Model
'active' => 'boolean', // Cast to boolean.
'show' => 'boolean', // Cast to boolean.
'show_in_assets' => 'boolean',
'trade_by_quantity' => 'boolean',
'use_last_price_if_zero' => 'boolean',
'for_barayand' => 'boolean', // Cast to boolean.
'open_sell' => 'boolean', // Cast to boolean.
@@ -0,0 +1,98 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
if (! Schema::hasTable('products')) {
return;
}
if (! Schema::hasColumn('products', 'trade_by_quantity')) {
Schema::table('products', function (Blueprint $table) {
if (Schema::hasColumn('products', 'show_in_assets')) {
$table->boolean('trade_by_quantity')->default(false)->after('show_in_assets');
} else {
$table->boolean('trade_by_quantity')->default(false);
}
});
}
$this->backfillQuantityTradeForLegacyProducts();
}
public function down(): void
{
if (! Schema::hasTable('products') || ! Schema::hasColumn('products', 'trade_by_quantity')) {
return;
}
Schema::table('products', function (Blueprint $table) {
$table->dropColumn('trade_by_quantity');
});
}
private function backfillQuantityTradeForLegacyProducts(): void
{
$mainProductIds = $this->resolveMainProductIds();
DB::table('products')
->where(function ($query) use ($mainProductIds) {
$query->whereIn('group_type', ['آبشده', 'کاتد مس']);
if ($mainProductIds !== []) {
$query->orWhereIn('id', $mainProductIds);
}
})
->where(function ($query) {
$query->where('trade_by_quantity', false)
->orWhereNull('trade_by_quantity');
})
->update(['trade_by_quantity' => true]);
}
/** @return array<int, int> */
private function resolveMainProductIds(): array
{
$ids = [];
if (Schema::hasTable('settings')) {
$value = DB::table('settings')->where('key', 'main_product')->value('value');
$id = $this->parsePositiveInt($value);
if ($id !== null) {
$ids[] = $id;
}
}
if (Schema::hasTable('const')) {
$value = DB::table('const')->where('key', 'main_product')->value('value');
$id = $this->parsePositiveInt($value);
if ($id !== null) {
$ids[] = $id;
}
}
return array_values(array_unique($ids));
}
private function parsePositiveInt(mixed $value): ?int
{
if ($value === null) {
return null;
}
$value = trim((string) $value);
if ($value === '' || ! ctype_digit($value)) {
return null;
}
$id = (int) $value;
return $id > 0 ? $id : null;
}
};
@@ -0,0 +1,40 @@
-- معامله مقداری: UI «ارزش کل» + «مقدار» مستقل از دسته‌بندی
-- اجرا: mysql ... < add_trade_by_quantity_to_products.sql
--
-- بک‌فیل: محصولات قبلی دسته آبشده/کاتد مس + محصول main_product (معمولاً id=13)
SET @has_col := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'products'
AND COLUMN_NAME = 'trade_by_quantity'
);
SET @sql := IF(
@has_col = 0,
'ALTER TABLE `products` ADD COLUMN `trade_by_quantity` TINYINT(1) NOT NULL DEFAULT 0 AFTER `show_in_assets`',
'SELECT ''trade_by_quantity already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- محصولات آبشده/کاتد مس + main_product از settings/const
UPDATE `products` p
LEFT JOIN `settings` s
ON s.`key` = 'main_product'
AND TRIM(s.`value`) REGEXP '^[0-9]+$'
AND CAST(TRIM(s.`value`) AS UNSIGNED) = p.`id`
LEFT JOIN `const` c
ON c.`key` = 'main_product'
AND TRIM(c.`value`) REGEXP '^[0-9]+$'
AND CAST(TRIM(c.`value`) AS UNSIGNED) = p.`id`
SET p.`trade_by_quantity` = 1
WHERE COALESCE(p.`trade_by_quantity`, 0) = 0
AND (
p.`group_type` IN ('آبشده', 'کاتد مس')
OR s.`id` IS NOT NULL
OR c.`id` IS NOT NULL
);
+24 -2
View File
@@ -317,6 +317,21 @@ INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `de
SELECT 'support_messenger_text', '', 'متن پشتیبانی پیام‌رسان', 1, 0, 'متن دکمه یا توضیح پشتیبانی در اپ کاربر'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'support_messenger_text' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'invite_link_text',
'⭐️الان طلا بخر؛ آخر ماه سودشو ببر!
با خرید طلای آبشده در مقدارهای کم ولی مستمر، سود بیشتری ببر!
این دعوت نامه از طرف "٪نام" عزیز برای شما ارسال شده تا شما هم از بازار طلای ما جا نمونید.
روی لینک زیر کلیک کنید و ثبت نام خود را تکمیل کنید.
٪لینک',
'متن دعوتنامه اشتراک‌گذاری',
1, 1,
'متن پیام اشتراک‌گذاری دعوت در اپ کاربر (دکمه «اشتراک با دوستان» در صفحه اصلی، بخش معرف در پروفایل، صفحه کسب درآمد و مودال اشتراک). متغیرهای جایگزین: ٪نام یا %نام = نام کامل کاربر دعوت‌کننده؛ ٪لینک یا %لینک = آدرس کامل صفحه ثبت‌نام همراه با کد معرف (بر اساس تنظیم app_url یا دامنه فعلی مرورگر). فاصله اختیاری بعد از ٪ یا % مجاز است (مثلاً ٪ نام). اگر این فیلد خالی باشد، اپ از متن پیش‌فرض داخلی بر اساس نوع نصب استفاده می‌کند.',
'longtext'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'invite_link_text' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`)
SELECT 'support_messenger', '', 'پیام‌رسان پشتیبانی', 1, 1, 'bale,telegram,whatsapp,rubika,eitaa,soroush'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'support_messenger' LIMIT 1);
@@ -593,7 +608,7 @@ WHERE `key` = 'main_product';
-- =============================================================================
-- longtext
UPDATE `settings` SET `type` = 'longtext' WHERE `key` IN ('text_rule', 'text_about', 'support_messenger_text', 'montzar_order_response');
UPDATE `settings` SET `type` = 'longtext' WHERE `key` IN ('text_rule', 'text_about', 'support_messenger_text', 'invite_link_text', 'montzar_order_response');
-- select (+ گزینه‌ها در description)
UPDATE `settings` SET `type` = 'select', `description` = 'منبع فعال قیمت|montzar,zaarbox,main_product' WHERE `key` = 'price_provider';
@@ -633,7 +648,7 @@ UPDATE `settings` SET `type` = 'text' WHERE `key` IN (
'payping_fin_token', 'payping_gateway_item', 'tahesab_host', 'tahesab_token', 'tahesab_db',
'node_client_id', 'node_shared_secret',
'wss_path', 'wss_link', 'wss_trigger_path',
'tahesab_bank_account', 'telegram_bot_token', 'bale_bot_token', 'support_messenger_id', 'support_messenger_text', 'referral_limit',
'tahesab_bank_account', 'telegram_bot_token', 'bale_bot_token', 'support_messenger_id', 'support_messenger_text', 'invite_link_text', 'referral_limit',
'referral_bonus_amount', 'balance_report_exclude_customer_id', 'metal_type', 'montify_name', 'price_extreme_date',
'default_vc_number', 'liman_token'
);
@@ -809,6 +824,12 @@ UPDATE `settings` SET
`description` = 'متن دکمه یا توضیح پشتیبانی در اپ کاربر'
WHERE `key` = 'support_messenger_text';
UPDATE `settings` SET
`title_fa` = 'متن دعوتنامه اشتراک‌گذاری',
`type` = 'longtext',
`description` = 'متن پیام اشتراک‌گذاری دعوت در اپ کاربر (دکمه «اشتراک با دوستان» در صفحه اصلی، بخش معرف در پروفایل، صفحه کسب درآمد و مودال اشتراک). متغیرهای جایگزین: ٪نام یا %نام = نام کامل کاربر دعوت‌کننده؛ ٪لینک یا %لینک = آدرس کامل صفحه ثبت‌نام همراه با کد معرف (بر اساس تنظیم app_url یا دامنه فعلی مرورگر). فاصله اختیاری بعد از ٪ یا % مجاز است (مثلاً ٪ نام). اگر این فیلد خالی باشد، اپ از متن پیش‌فرض داخلی بر اساس نوع نصب استفاده می‌کند.'
WHERE `key` = 'invite_link_text';
UPDATE `settings` SET
`title_fa` = 'پیام‌رسان پشتیبانی',
`type` = 'select',
@@ -987,6 +1008,7 @@ UPDATE `settings` SET `description` = 'روشن: واریز و برداشت مش
UPDATE `settings` SET `description` = 'نوع پیام‌رسان برای دکمه پشتیبانی در اپ کاربر|bale,telegram,whatsapp,rubika,eitaa,soroush' WHERE `key` = 'support_messenger';
UPDATE `settings` SET `description` = 'شناسه تماس پشتیبانی: یوزرنیم (تلگرام)، شماره (واتساپ/بله) یا آیدی بسته به پیام‌رسان انتخاب‌شده.' WHERE `key` = 'support_messenger_id';
UPDATE `settings` SET `description` = 'متن دکمه یا توضیح کوتاه کنار لینک پشتیبانی در اپ کاربر.' WHERE `key` = 'support_messenger_text';
UPDATE `settings` SET `description` = 'متن پیام اشتراک‌گذاری دعوت در اپ کاربر (دکمه «اشتراک با دوستان» در صفحه اصلی، بخش معرف در پروفایل، صفحه کسب درآمد و مودال اشتراک). متغیرهای جایگزین: ٪نام یا %نام = نام کامل کاربر دعوت‌کننده؛ ٪لینک یا %لینک = آدرس کامل صفحه ثبت‌نام همراه با کد معرف (بر اساس تنظیم app_url یا دامنه فعلی مرورگر). فاصله اختیاری بعد از ٪ یا % مجاز است (مثلاً ٪ نام). اگر این فیلد خالی باشد، اپ از متن پیش‌فرض داخلی بر اساس نوع نصب استفاده می‌کند.' WHERE `key` = 'invite_link_text';
UPDATE `settings` SET `description` = 'شماره موبایل پشتیبانی واریز؛ در صفحه شارژ حساب دکمه پیامک با متن «شماره حساب» نمایش داده می‌شود.' WHERE `key` = 'deposit_sms_support_number';
UPDATE `settings` SET `description` = 'توکن ربات تلگرام برای ارسال اعلان‌ها. از BotFather دریافت می‌شود. محرمانه.' WHERE `key` = 'telegram_bot_token';
+29
View File
@@ -0,0 +1,29 @@
-- invite_link_text: متن دعوتنامه اشتراک‌گذاری (longtext)
-- اجرا روی DB موجود: INSERT اگر نبود + به‌روزرسانی type و description
START TRANSACTION;
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'invite_link_text',
'⭐️الان طلا بخر؛ آخر ماه سودشو ببر!
با خرید طلای آبشده در مقدارهای کم ولی مستمر، سود بیشتری ببر!
این دعوت نامه از طرف "٪نام" عزیز برای شما ارسال شده تا شما هم از بازار طلای ما جا نمونید.
روی لینک زیر کلیک کنید و ثبت نام خود را تکمیل کنید.
٪لینک',
'متن دعوتنامه اشتراک‌گذاری',
1, 1,
'متن پیام اشتراک‌گذاری دعوت در اپ کاربر (دکمه «اشتراک با دوستان» در صفحه اصلی، بخش معرف در پروفایل، صفحه کسب درآمد و مودال اشتراک). متغیرهای جایگزین: ٪نام یا %نام = نام کامل کاربر دعوت‌کننده؛ ٪لینک یا %لینک = آدرس کامل صفحه ثبت‌نام همراه با کد معرف (بر اساس تنظیم app_url یا دامنه فعلی مرورگر). فاصله اختیاری بعد از ٪ یا % مجاز است (مثلاً ٪ نام). اگر این فیلد خالی باشد، اپ از متن پیش‌فرض داخلی بر اساس نوع نصب استفاده می‌کند.',
'longtext'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'invite_link_text' LIMIT 1);
UPDATE `settings` SET
`title_fa` = 'متن دعوتنامه اشتراک‌گذاری',
`type` = 'longtext',
`can_edit` = 1,
`is_active` = 1,
`description` = 'متن پیام اشتراک‌گذاری دعوت در اپ کاربر (دکمه «اشتراک با دوستان» در صفحه اصلی، بخش معرف در پروفایل، صفحه کسب درآمد و مودال اشتراک). متغیرهای جایگزین: ٪نام یا %نام = نام کامل کاربر دعوت‌کننده؛ ٪لینک یا %لینک = آدرس کامل صفحه ثبت‌نام همراه با کد معرف (بر اساس تنظیم app_url یا دامنه فعلی مرورگر). فاصله اختیاری بعد از ٪ یا % مجاز است (مثلاً ٪ نام). اگر این فیلد خالی باشد، اپ از متن پیش‌فرض داخلی بر اساس نوع نصب استفاده می‌کند.'
WHERE `key` = 'invite_link_text';
COMMIT;