diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index b7bf691..dfda3c9 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -69,6 +69,30 @@ class ProductController extends SuperController } + public function archiveTable(Request $request) + { + $data = $request->all(); + if (isset($data['select']) && is_null($data['select'])) { + $data['select'] = []; + } + + $query_table = Product::onlyTrashed()->with($this->with); + TableEngine::create($query_table, $data) + ->initSearch($this->search) + ->select($this->select) + ->export($query_table); + + return $query_table; + } + + public function archive($id) + { + $product = Product::onlyTrashed()->findOrFail($id); + $product->restore(); + + return response()->json(true); + } + public function editProducts(Request $request) { if (isset($request->auto_open_at, $request->auto_close_at)) { diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 79a8b5c..afc9e8f 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -90,35 +90,24 @@ class Controller extends BaseController $send_sms = true; $user = User::where('mobile', $to)->first() ?: Customer::where('mobile', $to)->first(); if ($user) { -// -// // Telegram -// if (isset($credentials['telegram']) && $user->telegram) { -// try { -// $res = Http::get("https://api.wwwapp.ir/telegran_send.php", [ -// 'botToken' => $credentials['telegram']['token'], -// 'chatId' => $user->telegram, -// 'message' => "جناب {$user->name} {$msg}", -// ]); -// $data = $res->json(); -// if (isset($data['success']) && $data['success'] === true) { -// $platforms[] = "telegram:{$user->telegram}"; -// $send_sms = false; -// } -// } catch (\Exception $e) { -// } -// } -// - // Bale - if (isset($credentials['bale']) && $user->bale) { + $baleToken = trim((string) ($credentials['bale']['token'] ?? '')); + $baleChatId = trim((string) ($user->bale ?? '')); + if ($baleToken !== '' && $baleChatId !== '') { try { - Http::withoutVerifying()->get("https://tapi.bale.ai/{$credentials['bale']['token']}/sendMessage", [ - 'chat_id' => $user->bale, - 'text' => "جناب {$user->name} \n{$msg}", - ]); - $platforms[] = "bale:{$user->bale}"; - $send_sms = false; - } catch (\Exception $e) { - return $e->getMessage(); + $baleResponse = Http::withoutVerifying()->get( + "https://tapi.bale.ai/bot{$baleToken}/sendMessage", + [ + 'chat_id' => $baleChatId, + 'text' => "جناب {$user->name} \n{$msg}", + ] + ); + $balePayload = $baleResponse->json(); + if ($baleResponse->successful() && ($balePayload['ok'] ?? false) === true) { + $platforms[] = "bale:{$baleChatId}"; + $send_sms = false; + } + } catch (\Throwable $e) { + report($e); } } } @@ -153,7 +142,9 @@ class Controller extends BaseController 'platform' => 'rayegansms', ]); - $platforms[] = "rayegansms:{$to}"; + $platforms[] = $response->successful() + ? "rayegansms:{$to}" + : "rayegansms_failed:{$to}:{$response->status()}"; } diff --git a/app/Http/Controllers/Single/SingleOrderController.php b/app/Http/Controllers/Single/SingleOrderController.php index a88f011..ff860ca 100644 --- a/app/Http/Controllers/Single/SingleOrderController.php +++ b/app/Http/Controllers/Single/SingleOrderController.php @@ -583,13 +583,17 @@ class SingleOrderController extends Controller User::where('send_sms', 1)->get()->each(function ($admin) use ($message, $data, $transactionType, $companyName) { $this->send($admin->mobile, $message); }); - $wallet = Wallet::where('owner', $data['customer_id'])->whereNull('fund_id')->first(); + $wallet = Wallet::where('owner', $data['customer_id']) + ->whereNull('fund_id') + ->whereNull('product_id') + ->first(); $balanceLabel = self::metalTypeLabel(); -// $pricePerGram = $totalPrice / $amount; + $goldBalance = $wallet ? ($wallet->gold ?? 0) : 0; + $cashBalance = $wallet ? ($wallet->cash ?? 0) : 0; $customerMessage = "$companyName\n$transactionType {$subjectTitle}\nگرم: {$amount}\n" . "مبلغ کل: " . number_format($totalPrice) . "\n\n" . - "موجودی {$balanceLabel}: {$wallet->gold}\n" . - "مانده ریالی: " . number_format($wallet->cash) . "\n" . + "موجودی {$balanceLabel}: {$goldBalance}\n" . + "مانده ریالی: " . number_format($cashBalance) . "\n" . $this->Config('app_url'); diff --git a/app/Http/Controllers/Site/RequestsController.php b/app/Http/Controllers/Site/RequestsController.php index 38b7b93..0fa9181 100644 --- a/app/Http/Controllers/Site/RequestsController.php +++ b/app/Http/Controllers/Site/RequestsController.php @@ -181,21 +181,27 @@ class RequestsController extends Controller // $this->send($admin->mobile, $messageExam); // }); - $wallet = Wallet::where('owner', $data['customer_id'])->whereNull('fund_id')->first(); + $wallet = Wallet::where('owner', $data['customer_id']) + ->whereNull('fund_id') + ->whereNull('product_id') + ->first(); if($data['method'] == 'cash') { $pricePerGram = $data['total_price']; }elseif ($data['method'] == 'gold') { $pricePerGram = $data['total_price'] / $data['amount']; } + $goldBalance = $wallet ? ($wallet->gold ?? 0) : 0; + $cashBalance = $wallet ? ($wallet->cash ?? 0) : 0; + if($data['method'] == 'cash') { $customerMessage = "$companyName\nبرداشت ریالی \nمبلغ: {$data['amount']}\n" . - "\nموجودی طلایی: {$wallet->gold}\n" . - "مانده ریالی: " . number_format($wallet->cash) . "\n" . $this->Config('app_url'); + "\nموجودی طلایی: {$goldBalance}\n" . + "مانده ریالی: " . number_format($cashBalance) . "\n" . $this->Config('app_url'); }elseif ($data['method'] == 'gold') { $customerMessage = "$companyName\nبرداشت طلایی \nگرم: {$data['amount']}\nقیمت گرم: " . number_format($pricePerGram) . - "\nمبلغ کل: " . number_format($data['total_price']) . "\nموجودی طلایی: {$wallet->gold}\n" . - "مانده ریالی: " . number_format($wallet->cash) . "\n" . $this->Config('app_url'); + "\nمبلغ کل: " . number_format($data['total_price']) . "\nموجودی طلایی: {$goldBalance}\n" . + "مانده ریالی: " . number_format($cashBalance) . "\n" . $this->Config('app_url'); } diff --git a/app/Services/LivePriceTickService.php b/app/Services/LivePriceTickService.php index c8bdd8b..e8d74fa 100644 --- a/app/Services/LivePriceTickService.php +++ b/app/Services/LivePriceTickService.php @@ -29,8 +29,10 @@ class LivePriceTickService | (قبل از sync محصولات تا آبشدهٔ «قیمت اپ» تازه باشد) */ $request = Request::create('/api/price/last', 'GET', ['force_refresh' => '1']); - $response = app(ReportController::class)->lastPrice($request); - $payload = json_decode($response->getContent(), true); + $result = app(ReportController::class)->lastPrice($request); + $payload = is_array($result) + ? $result + : (json_decode($result->getContent(), true) ?: []); if (! is_array($payload)) { $payload = []; } diff --git a/app/Services/PriceSnapshotService.php b/app/Services/PriceSnapshotService.php index 529786b..5748970 100644 --- a/app/Services/PriceSnapshotService.php +++ b/app/Services/PriceSnapshotService.php @@ -8,13 +8,16 @@ use Carbon\Carbon; class PriceSnapshotService { + /** حداقل قیمت معتبر آبشده (ریال) — زیر این در نمودار نمایش داده نمی‌شود */ + public const MIN_VALID_CHART_PRICE_RIAL = 30_000_000; + /** هر تیک کران — ذخیره در prices برای نمودار (هرگز صفر ثبت نمی‌شود) */ public function recordTick(array $payload): void { $priceValue = (int) ($payload['price'] ?? 0); - if ($priceValue <= 30_000_000) { + if ($priceValue <= self::MIN_VALID_CHART_PRICE_RIAL) { $last = Price::orderByDesc('id')->first(); - if (! $last || (int) $last->price <= 30_000_000) { + if (! $last || (int) $last->price <= self::MIN_VALID_CHART_PRICE_RIAL) { return; } $priceValue = (int) $last->price; @@ -119,11 +122,7 @@ class PriceSnapshotService ->orderBy('created_at') ->get(['price', 'created_at']); - $dailyPoints = $daily->map(function (Price $row) { - $ts = ($row->created_at ?? now())->timestamp * 1000; - - return ['timestamp' => $ts, 'value' => (int) $row->price]; - })->values()->all(); + $dailyPoints = $this->mapValidPriceRows($daily); $weekRows = Price::query() ->where('created_at', '>=', now()->subDays(7)) @@ -153,11 +152,16 @@ class PriceSnapshotService $lastBucket = null; foreach ($rows as $row) { + $price = (int) $row->price; + if ($price <= self::MIN_VALID_CHART_PRICE_RIAL) { + continue; + } + $ts = $row->created_at?->timestamp ?? 0; $bucket = (int) floor($ts / $bucketSeconds); $point = [ 'timestamp' => $ts * 1000, - 'value' => (int) $row->price, + 'value' => $price, ]; if ($bucket !== $lastBucket) { @@ -170,4 +174,72 @@ class PriceSnapshotService return $points; } + + /** @param \Illuminate\Support\Collection $rows */ + private function mapValidPriceRows($rows): array + { + $points = []; + + foreach ($rows as $row) { + $price = (int) $row->price; + if ($price <= self::MIN_VALID_CHART_PRICE_RIAL) { + continue; + } + + $points[] = [ + 'timestamp' => ($row->created_at ?? now())->timestamp * 1000, + 'value' => $price, + ]; + } + + return $points; + } + + /** + * @param array{DAILY?: array, WEEKLY?: array, MONTHLY?: array} $prices + * @return array{DAILY: array, WEEKLY: array, MONTHLY: array} + */ + public function sanitizeChartPrices(array $prices): array + { + $minScaled = intdiv(self::MIN_VALID_CHART_PRICE_RIAL, 1000); + + foreach (['DAILY', 'WEEKLY', 'MONTHLY'] as $period) { + if (! isset($prices[$period]) || ! is_array($prices[$period])) { + $prices[$period] = []; + continue; + } + + $lastValid = null; + $sanitized = []; + + foreach ($prices[$period] as $point) { + $value = (int) ($point['value'] ?? 0); + $timestamp = (int) ($point['timestamp'] ?? 0); + + if ($timestamp <= 0) { + continue; + } + + $isValid = $value > self::MIN_VALID_CHART_PRICE_RIAL + || ($value > $minScaled && $value <= self::MIN_VALID_CHART_PRICE_RIAL); + + if ($isValid) { + $lastValid = $value; + } elseif ($lastValid !== null) { + $value = $lastValid; + } else { + continue; + } + + $sanitized[] = [ + 'timestamp' => $timestamp, + 'value' => $value, + ]; + } + + $prices[$period] = $sanitized; + } + + return $prices; + } } diff --git a/app/Services/ZaarboxPriceService.php b/app/Services/ZaarboxPriceService.php index 8c2f55d..21d6032 100644 --- a/app/Services/ZaarboxPriceService.php +++ b/app/Services/ZaarboxPriceService.php @@ -93,17 +93,17 @@ class ZaarboxPriceService if ($this->config->getBool('chart_use_local_data', false)) { $local = app(PriceSnapshotService::class)->chartFromDatabase(); if (! empty($local['DAILY'])) { - return $local; + return app(PriceSnapshotService::class)->sanitizeChartPrices($local); } } $cached = Cache::get($this->cacheKey()); if (is_array($cached) && isset($cached['prices'])) { - return $cached['prices']; + return app(PriceSnapshotService::class)->sanitizeChartPrices($cached['prices']); } - return $this->refreshCache(); + return app(PriceSnapshotService::class)->sanitizeChartPrices($this->refreshCache()); } /** @@ -566,7 +566,9 @@ class ZaarboxPriceService for ($bucket = $firstBucket; $bucket <= $endMs; $bucket += $intervalMs) { while ($index < $count && $points[$index]['timestamp'] <= $bucket) { - $lastPoint = $points[$index]; + if ($points[$index]['price'] > PriceSnapshotService::MIN_VALID_CHART_PRICE_RIAL) { + $lastPoint = $points[$index]; + } $index++; } diff --git a/database/sql/deploy_settings_const.sql b/database/sql/deploy_settings_const.sql index 15ec117..c0d218a 100644 --- a/database/sql/deploy_settings_const.sql +++ b/database/sql/deploy_settings_const.sql @@ -90,11 +90,11 @@ SELECT 'admin_url', '', 'آدرس پنل ادمین', 1, 1, NULL, 'text' WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'admin_url' LIMIT 1); INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`) -SELECT 'logo_login', '', 'لوگوی صفحه ورود', 1, 1, NULL, 'text' +SELECT 'logo_login', '', 'لوگوی صفحه ورود', 0, 0, NULL, 'text' WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'logo_login' LIMIT 1); INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`) -SELECT 'logo_register', '', 'لوگوی ثبت‌نام', 1, 1, NULL, 'text' +SELECT 'logo_register', '', 'لوگوی ثبت‌نام', 0, 0, NULL, 'text' WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'logo_register' LIMIT 1); INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`) @@ -407,11 +407,11 @@ SELECT 'minimum_sell', '', 'حداقل فروش', 1, 1, NULL, 'number' WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'minimum_sell' LIMIT 1); INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`) -SELECT 'referral_bonus_amount', '', 'مبلغ پاداش معرف', 1, 1, NULL, 'text' +SELECT 'referral_bonus_amount', '', 'مقدار پاداش معرف', 1, 1, NULL, 'text' WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'referral_bonus_amount' LIMIT 1); INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`) -SELECT 'balance_report_exclude_customer_id', '', 'حذف مشتری از تراز', 1, 1, NULL, 'number' +SELECT 'balance_report_exclude_customer_id', '', 'حذف مشتری از تراز', 1, 1, NULL, 'text' WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'balance_report_exclude_customer_id' LIMIT 1); INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`) @@ -450,7 +450,7 @@ UPDATE `settings` SET `title_fa` = 'چت پشتیبانی' WHERE `key` = 'has_ch UPDATE `settings` SET `title_fa` = 'آدرس API سرور' WHERE `key` = 'server_url' AND (`title_fa` IS NULL OR TRIM(`title_fa`) = ''); UPDATE `settings` SET `title_fa` = 'کلید داخلی اپ' WHERE `key` = 'key' AND (`title_fa` IS NULL OR TRIM(`title_fa`) = ''); UPDATE `settings` SET `title_fa` = 'تعداد معرف برای هدیه' WHERE `key` = 'referral_count_for_gift' AND (`title_fa` IS NULL OR TRIM(`title_fa`) = ''); -UPDATE `settings` SET `title_fa` = 'مبلغ پاداش معرف' WHERE `key` = 'referral_bonus_amount' AND (`title_fa` IS NULL OR TRIM(`title_fa`) = ''); +UPDATE `settings` SET `title_fa` = 'مقدار پاداش معرف' WHERE `key` = 'referral_bonus_amount' AND (`title_fa` IS NULL OR TRIM(`title_fa`) = '' OR `title_fa` = 'مبلغ پاداش معرف'); UPDATE `settings` SET `title_fa` = 'حداقل برداشت (ریال)' WHERE `key` = 'min_withdraw' AND (`title_fa` IS NULL OR TRIM(`title_fa`) = ''); UPDATE `settings` SET `title_fa` = 'حداقل اولین خرید (ریال)' WHERE `key` = 'min_first_buy' AND (`title_fa` IS NULL OR TRIM(`title_fa`) = ''); UPDATE `settings` SET `title_fa` = 'حداقل فروش' WHERE `key` = 'minimum_sell' AND (`title_fa` IS NULL OR TRIM(`title_fa`) = ''); @@ -583,13 +583,13 @@ UPDATE `settings` SET `type` = 'switch' WHERE `key` IN ( UPDATE `settings` SET `type` = 'number' WHERE `key` IN ( 'fee_single', 'buy_fee', 'sell_fee', 'fee_cash', 'fee_service_charge_percentage', 'fee_free', 'min_withdraw', 'min_first_buy', 'minimum_sell', - 'montzar_product_id', 'balance_report_exclude_customer_id', 'gift_for_referrer', + 'montzar_product_id', 'gift_for_referrer', 'gift_for_referral', 'up_price', 'down_price', 'payping_fin_customer_id', 'tahesab_port' ); -- text (بقیه — URL، توکن، کلید API، ساعت، ...) UPDATE `settings` SET `type` = 'text' WHERE `key` IN ( - 'name_fa', 'name_en', 'app_url', 'bace_url', 'logo_register', 'admin_url', 'title_pwa', 'title_fav', + 'name_fa', 'name_en', 'app_url', 'bace_url', 'logo_login', 'logo_register', 'admin_url', 'title_pwa', 'title_fav', 'server_url', 'key', 'main_product', 'zaarbox_api_url', 'zaarbox_price_key', 'chart_id', 'etehadie_api_url', 'montzar_mobile', 'montzar_password', 'montzar_base_url', 'montzar_token', 'montzar_diff', 'montzar_live_price', 'montzar_mazaneh', @@ -598,7 +598,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', 'tahesab_bank_account', 'telegram_bot_token', 'bale_bot_token', 'support_messenger_id', 'support_messenger_text', 'referral_limit', - 'referral_bonus_amount', 'metal_type', 'montify_name', 'price_extreme_date', + 'referral_bonus_amount', 'balance_report_exclude_customer_id', 'metal_type', 'montify_name', 'price_extreme_date', 'default_vc_number', 'liman_token' ); @@ -622,11 +622,11 @@ SELECT 'service_charge_percentage', 'کارمزد درصدی (alias)', COALESCE( WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'service_charge_percentage' LIMIT 1); INSERT INTO `settings` (`key`, `title_fa`, `value`, `can_edit`, `is_exist`, `is_active`, `type`) -SELECT 'title_pwa', 'عنوان PWA', '', 1, 0, 1, 'text' +SELECT 'title_pwa', 'عنوان PWA', '', 0, 0, 0, 'text' WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'title_pwa' LIMIT 1); INSERT INTO `settings` (`key`, `title_fa`, `value`, `can_edit`, `is_exist`, `is_active`, `type`) -SELECT 'title_fav', 'عنوان favicon', '', 1, 0, 1, 'text' +SELECT 'title_fav', 'عنوان favicon', '', 0, 0, 0, 'text' WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'title_fav' LIMIT 1); INSERT INTO `settings` (`key`, `title_fa`, `value`, `can_edit`, `is_exist`, `is_active`, `type`) @@ -816,6 +816,14 @@ WHERE `key` = 'send_to_tahesab' AND TRIM(COALESCE(`value`, '')) = ''; -- is_store فقط از دیتابیس/دیپلوی — در پنل قابل ویرایش نیست UPDATE `settings` SET `can_edit` = 0 WHERE `key` = 'is_store'; +-- لوگو / PWA / favicon — فقط از دیپلوی؛ در پنل نمایش داده نمی‌شوند +UPDATE `settings` SET `can_edit` = 0, `is_active` = 0 +WHERE `key` IN ('logo_login', 'logo_register', 'title_pwa', 'title_fav'); + +UPDATE `settings` SET `title_fa` = 'مقدار پاداش معرف' WHERE `key` = 'referral_bonus_amount'; + +UPDATE `settings` SET `type` = 'text' WHERE `key` = 'balance_report_exclude_customer_id'; + -- پاکسازی title_fa / description از نام پروژه یا ارجاع کد UPDATE `settings` SET `title_fa` = 'حالت فروشگاهی' WHERE `key` = 'is_store' AND (`title_fa` LIKE '%قطره%' OR `title_fa` LIKE '%کشکول%'); @@ -823,12 +831,10 @@ WHERE `key` = 'is_store' AND (`title_fa` LIKE '%قطره%' OR `title_fa` LIKE '% UPDATE `settings` SET `title_fa` = 'همگام‌سازی fin' WHERE `key` = 'payping_fin_sync_enabled' AND `title_fa` LIKE '%لیمان%'; -UPDATE `settings` SET `title_fa` = 'نام فروشگاه در درگاه', `description` = NULL +UPDATE `settings` SET `title_fa` = 'نام فروشگاه در درگاه', `description` = 'نام فروشگاه که در درگاه پرداخت و رسید بانکی به کاربر نمایش داده می‌شود.' WHERE `key` = 'montify_name'; -UPDATE `settings` SET `description` = NULL -WHERE `key` IN ('jibit_verification', 'register_jibit_verify', 'up_price', 'node_client_id', 'node_shared_secret') - AND (`description` LIKE '%Controller%' OR `description` LIKE '%.env%'); +-- descriptionهای قدیمی که عمداً NULL می‌شدند — در بلوک پایانی بازنویسی می‌شوند UPDATE `const` SET `title_fa` = 'توکن fin (legacy)' WHERE `key` = 'liman' AND `title_fa` LIKE '%لیمان%'; @@ -837,6 +843,118 @@ UPDATE `const` SET `title_fa` = COALESCE(NULLIF(TRIM(`title_fa`), ''), 'باز UPDATE `const` SET `title_fa` = COALESCE(NULLIF(TRIM(`title_fa`), ''), 'سقف واریز اولیه') WHERE `key` = 'limited_price'; UPDATE `const` SET `title_fa` = COALESCE(NULLIF(TRIM(`title_fa`), ''), 'لاگ آخرین سفارش') WHERE `key` = 'last_order_log'; +-- --------------------------------------------------------------------------- +-- راهنمای کامل تنظیمات (description) — برای نمایش در پنل ادمین +-- فرمت select: «متن راهنما|گزینه۱,گزینه۲» +-- --------------------------------------------------------------------------- +UPDATE `settings` SET `description` = 'نام فارسی برند که در پیامک‌ها، عنوان صفحات و بخش‌های عمومی اپ کاربر نمایش داده می‌شود.' WHERE `key` = 'name_fa'; +UPDATE `settings` SET `description` = 'آدرس کامل وب‌اپ کاربر (با https). در لینک‌های پیامک، بازگشت از درگاه و ارجاعات داخلی استفاده می‌شود.' WHERE `key` = 'app_url'; +UPDATE `settings` SET `description` = 'آدرس کامل پنل مدیریت (با https). برای لینک‌های ادمین و تنظیمات CORS در صورت نیاز.' WHERE `key` = 'admin_url'; +UPDATE `settings` SET `description` = 'آدرس تصویر لوگو در صفحه ورود. معمولاً از طریق دیپلوی یا آپلود فایل تنظیم می‌شود؛ در پنل قابل ویرایش نیست.' WHERE `key` = 'logo_login'; +UPDATE `settings` SET `description` = 'آدرس تصویر لوگو در صفحه ثبت‌نام. معمولاً از طریق دیپلوی تنظیم می‌شود؛ در پنل قابل ویرایش نیست.' WHERE `key` = 'logo_register'; +UPDATE `settings` SET `description` = 'عنوان کوتاه برای نصب PWA روی موبایل. از دیپلوی مدیریت می‌شود.' WHERE `key` = 'title_pwa'; +UPDATE `settings` SET `description` = 'عنوان نمایش‌داده‌شده در تب مرورگر (favicon). از دیپلوی مدیریت می‌شود.' WHERE `key` = 'title_fav'; +UPDATE `settings` SET `description` = 'عنوان فلز اصلی در رابط کاربر (مثلاً «طلای آبشده»). در دارایی، کیف پول و برچسب‌های معامله دیده می‌شود.' WHERE `key` = 'metal_type'; +UPDATE `settings` SET `description` = 'واحد نمایش وزن در دارایی‌ها، کیف پول و جزئیات معاملات|گرم,کیلوگرم' WHERE `key` = 'unit'; +UPDATE `settings` SET `description` = 'روشن: کاربران جدید می‌توانند ثبت‌نام کنند. خاموش: فقط ورود کاربران موجود مجاز است.' WHERE `key` = 'open_register'; +UPDATE `settings` SET `description` = 'روشن: حساب کاربر بلافاصله پس از ثبت‌نام فعال می‌شود. خاموش: نیاز به تأیید دستی ادمین دارد.' WHERE `key` = 'auto_active_customer'; +UPDATE `settings` SET `description` = 'نوع چیدمان اپ: روشن = حالت فروشگاهی (سبد، دسته‌بندی، حواله و …). خاموش = حالت معامله آبشده/محصولات کلاسیک. فقط از دیپلوی تغییر می‌کند.' WHERE `key` = 'is_store'; +UPDATE `settings` SET `description` = 'پیشوند اختیاری برای شماره‌گذاری حواله‌ها در سیستم حسابداری خارجی. خالی = بدون پیشوند.' WHERE `key` = 'default_vc_number'; + +UPDATE `settings` SET `description` = 'روشن: نمودار قیمت از تاریخچه ذخیره‌شده در دیتابیس خودتان. خاموش: از سرویس تابلو آنلاین (با شناسه chart_id) استفاده می‌شود.' WHERE `key` = 'chart_use_local_data'; +UPDATE `settings` SET `description` = 'شناسه یکتا (UUID) نمودار در سرویس تابلو. اگر خالی باشد از شناسه پیش‌فرض سرویس استفاده می‌شود.' WHERE `key` = 'chart_id'; + +UPDATE `settings` SET `description` = 'منبع دریافت قیمت لحظه‌ای برای اپ کاربر و همگام‌سازی محصولات|montzar,zaarbox,main_product' WHERE `key` = 'price_provider'; +UPDATE `settings` SET `description` = 'شناسه عددی یا نام reading_method محصول آبشده مرجع. وقتی منبع قیمت روی main_product باشد برای محاسبه استفاده می‌شود.' WHERE `key` = 'main_product'; +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` = 'روشن: خارج از بازه startDay تا endDay معامله و نمایش قیمت بسته می‌شود. خاموش: محدودیت ساعتی اعمال نمی‌شود.' WHERE `key` = 'close_price'; +UPDATE `settings` SET `description` = 'ساعت شروع بازه معامله روزانه (فرمت 24 ساعته، مثال 09:00). فقط وقتی close_price روشن باشد اثر دارد.' WHERE `key` = 'startDay'; +UPDATE `settings` SET `description` = 'ساعت پایان بازه معامله روزانه (فرمت 24 ساعته، مثال 22:00). فقط وقتی close_price روشن باشد اثر دارد.' WHERE `key` = 'endDay'; +UPDATE `settings` SET `description` = 'اختلاف پایه (ریال) برای محاسبه قیمت خرید و فروش نمایشی روی مظنه. معمولاً نصف فاصله اسپرد کل است.' WHERE `key` = 'fee_single'; +UPDATE `settings` SET `description` = 'مبلغ اضافه (ریال) روی قیمت پایه برای نمایش/محاسبه سمت خرید. خالی = استفاده از fee_single.' WHERE `key` = 'buy_fee'; +UPDATE `settings` SET `description` = 'مبلغ کسر (ریال) از قیمت پایه برای نمایش/محاسبه سمت فروش. خالی = استفاده از fee_single.' WHERE `key` = 'sell_fee'; +UPDATE `settings` SET `description` = 'کارمزد ثابت ریالی هر سفارش (legacy). در صورت تنظیم fee_service_charge_percentage معمولاً اولویت با درصد است.' WHERE `key` = 'fee_cash'; +UPDATE `settings` SET `description` = 'درصد کارمزد هر سفارش از مبلغ معامله (اعشاری، مثال 0.005 یعنی ۰٫۵٪). روی payment_amount اعمال می‌شود.' WHERE `key` = 'fee_service_charge_percentage'; +UPDATE `settings` SET `description` = 'تعداد اولین سفارش‌های هر کاربر که از کارمزد معاف می‌شوند. ۰ = غیرفعال.' WHERE `key` = 'fee_free'; +UPDATE `settings` SET `description` = 'حداقل مبلغ درخواست برداشت وجه (ریال). درخواست‌های کمتر رد می‌شوند.' WHERE `key` = 'min_withdraw'; +UPDATE `settings` SET `description` = 'حداقل مبلغ اولین خرید هر کاربر (ریال). برای فعال‌سازی کیف یا احراز سطح اول استفاده می‌شود.' WHERE `key` = 'min_first_buy'; +UPDATE `settings` SET `description` = 'حداقل مقدار/مبلغ مجاز برای ثبت سفارش فروش. بسته به نوع محصول می‌تواند وزن یا ریال باشد.' WHERE `key` = 'minimum_sell'; +UPDATE `settings` SET `description` = 'بیشترین قیمت ثبت‌شده در روز جاری (ریال). توسط سیستم به‌روز می‌شود؛ دستی تغییر ندهید.' WHERE `key` = 'up_price'; +UPDATE `settings` SET `description` = 'کمترین قیمت ثبت‌شده در روز جاری (ریال). توسط سیستم به‌روز می‌شود؛ دستی تغییر ندهید.' WHERE `key` = 'down_price'; +UPDATE `settings` SET `description` = 'تاریخ آخرین به‌روزرسانی بیشترین/کمترین قیمت روز. توسط سیستم پر می‌شود.' WHERE `key` = 'price_extreme_date'; +UPDATE `settings` SET `description` = 'شناسه عددی مشتری که از گزارش تراز و آمار تجمیعی حذف می‌شود (مثلاً حساب تست). چند id با کاما.' WHERE `key` = 'balance_report_exclude_customer_id'; +UPDATE `settings` SET `description` = 'تنظیم legacy — از price_provider استفاده کنید. روشن بودن این گزینه دیگر توصیه نمی‌شود.' WHERE `key` = 'main_product_price'; +UPDATE `settings` SET `description` = 'تنظیم legacy — از price_provider استفاده کنید. معمولاً خاموش بماند.' WHERE `key` = 'montzar_price'; + +UPDATE `settings` SET `description` = 'متن کامل قوانین و مقررات استفاده از اپ که در صفحه قوانین به کاربر نشان داده می‌شود. HTML ساده مجاز است.' WHERE `key` = 'text_rule'; +UPDATE `settings` SET `description` = 'متن «درباره ما» شامل معرفی مجموعه، راه‌های ارتباطی و توضیح خدمات. در صفحه مربوطه در اپ کاربر نمایش داده می‌شود.' WHERE `key` = 'text_about'; + +UPDATE `settings` SET `description` = 'روشن: وارد کردن کد معرف در ثبت‌نام اجباری است. خاموش: کد معرف اختیاری است.' WHERE `key` = 'referral_id'; +UPDATE `settings` SET `description` = 'مقدار پاداش (معمولاً وزن فلز به گرم) که به کیف معرف پس از ثبت‌نام موفق معرف‌شونده واریز می‌شود.' WHERE `key` = 'gift_for_referrer'; +UPDATE `settings` SET `description` = 'مقدار پاداش (معمولاً وزن فلز) که به کاربر تازه‌ثبت‌نام‌شده (معرف‌شونده) داده می‌شود. ۰ = بدون پاداش.' WHERE `key` = 'gift_for_referral'; +UPDATE `settings` SET `description` = 'روشن: پس از ثبت‌نام کاربر جدید، پیامک اطلاع‌رسانی برای معرف ارسال می‌شود.' WHERE `key` = 'register_referral_sms_to_referrer'; +UPDATE `settings` SET `description` = 'روشن: متن پاداش معرف در پیامک خوش‌آمد ثبت‌نام گنجانده می‌شود.' WHERE `key` = 'register_referral_bonus_message'; +UPDATE `settings` SET `description` = 'حداکثر تعداد دعوت‌های مجاز برای هر کاربر. خالی = بدون محدودیت.' WHERE `key` = 'referral_limit'; +UPDATE `settings` SET `description` = 'مبلغ یا مقدار پاداش معرف (متن آزاد — بسته به پیاده‌سازی پیامک/اعتبار). برای نمایش در پیام‌ها.' WHERE `key` = 'referral_bonus_amount'; +UPDATE `settings` SET `description` = 'روشن: تطبیق شماره کارت بانکی کاربر با سرویس احراز هویت جیبیت هنگام ثبت/ویرایش کارت.' WHERE `key` = 'jibit_verification'; +UPDATE `settings` SET `description` = 'روشن: تطبیق کد ملی و موبایل در فرآیند ثبت‌نام از طریق سرویس جیبیت.' WHERE `key` = 'register_jibit_verify'; +UPDATE `settings` SET `description` = 'روشن: منوی «کسب درآمد / دعوت دوستان» در اپ کاربر نمایش داده می‌شود.' WHERE `key` = 'has_page_earn_money'; + +UPDATE `settings` SET `description` = 'شماره موبایل حساب API سرویس همکار قیمت. برای دریافت توکن و لیست محصولات.' WHERE `key` = 'montzar_mobile'; +UPDATE `settings` SET `description` = 'رمز عبور حساب API سرویس همکار قیمت. محرمانه نگه دارید.' WHERE `key` = 'montzar_password'; +UPDATE `settings` SET `description` = 'شناسه محصول آبشده در API همکار. باید با محصول مرجع در سرویس خارجی یکسان باشد.' WHERE `key` = 'montzar_product_id'; +UPDATE `settings` SET `description` = 'آدرس پایه سرور API همکار (بدون اسلش انتهایی). مثال: https://example.com' WHERE `key` = 'montzar_base_url'; +UPDATE `settings` SET `description` = 'توکن دسترسی API همکار. پس از ورود موفق توسط سیستم پر می‌شود؛ دستی ویرایش نکنید.' WHERE `key` = 'montzar_token'; +UPDATE `settings` SET `description` = 'اختلاف قیمت دریافتی از همکار (ریال). توسط cron/همگام‌سازی پر می‌شود.' WHERE `key` = 'montzar_diff'; +UPDATE `settings` SET `description` = 'آخرین قیمت لحظه‌ای دریافت‌شده از همکار (ریال). فقط خواندنی — توسط سیستم به‌روز می‌شود.' WHERE `key` = 'montzar_live_price'; +UPDATE `settings` SET `description` = 'مظنه میانی دریافت‌شده از همکار. توسط سیستم به‌روز می‌شود.' WHERE `key` = 'montzar_mazaneh'; +UPDATE `settings` SET `description` = 'مظنه واقعی سمت خرید از همکار. توسط سیستم به‌روز می‌شود.' WHERE `key` = 'montzar_truly_buy_mazaneh'; +UPDATE `settings` SET `description` = 'مظنه واقعی سمت فروش از همکار. توسط سیستم به‌روز می‌شود.' WHERE `key` = 'montzar_truly_sell_mazaneh'; +UPDATE `settings` SET `description` = 'روشن: امکان ثبت سفارش خرید از طریق قیمت همکار باز است (طبق وضعیت سرویس خارجی).' WHERE `key` = 'montzar_open_buy'; +UPDATE `settings` SET `description` = 'روشن: امکان ثبت سفارش فروش از طریق قیمت همکار باز است.' WHERE `key` = 'montzar_open_sell'; +UPDATE `settings` SET `description` = 'روشن: سرویس همکار در حال حاضر بسته/غیرفعال گزارش شده است.' WHERE `key` = 'montzar_closed'; +UPDATE `settings` SET `description` = 'پاسخ JSON آخرین سفارش ارسالی به همکار. برای عیب‌یابی؛ توسط سیستم پر می‌شود.' WHERE `key` = 'montzar_order_response'; + +UPDATE `settings` SET `description` = 'شناسه کلاینت برای API سفارش خارجی (هدر X-Client-Id). باید با مقدار تنظیم‌شده در سرویس مقصد یکسان باشد.' WHERE `key` = 'node_client_id'; +UPDATE `settings` SET `description` = 'کلید مشترک امضای HMAC برای احراز درخواست‌های API سفارش خارجی. محرمانه نگه دارید.' WHERE `key` = 'node_shared_secret'; +UPDATE `settings` SET `description` = 'روشن: API دریافت کیف پول و ثبت سفارش از سرویس فروشگاه خارجی فعال است. نیاز به node_client_id و node_shared_secret دارد.' WHERE `key` = 'external_order_api_enabled'; +UPDATE `settings` SET `description` = 'نام فروشگاه که در درگاه پرداخت و رسید بانکی به کاربر نمایش داده می‌شود.' WHERE `key` = 'montify_name'; + +UPDATE `settings` SET `description` = 'روشن: پس از واریز/برداشت، تراکنش به سامانه حسابداری fin همگام‌سازی می‌شود.' WHERE `key` = 'payping_fin_sync_enabled'; +UPDATE `settings` SET `description` = 'آدرس کامل endpoint ثبت تراکنش در سامانه fin (POST).' WHERE `key` = 'payping_fin_url'; +UPDATE `settings` SET `description` = 'توکن احراز هویت برای API سامانه fin.' WHERE `key` = 'payping_fin_token'; +UPDATE `settings` SET `description` = 'شناسه مشتری/حساب در سامانه fin که تراکنش‌ها به نام آن ثبت می‌شوند.' WHERE `key` = 'payping_fin_customer_id'; +UPDATE `settings` SET `description` = 'کد item تکی درگاه (legacy). اگر payping_gateway_items پر باشد اولویت با لیست چرخشی است.' WHERE `key` = 'payping_gateway_item'; +UPDATE `settings` SET `description` = 'آدرس API شروع پرداخت (redirect کاربر به درگاه).' WHERE `key` = 'payping_start_url'; +UPDATE `settings` SET `description` = 'آدرس API تأیید و نهایی‌سازی پرداخت پس از بازگشت از درگاه.' WHERE `key` = 'payping_verify_url'; +UPDATE `settings` SET `description` = 'نام درگاه پرداخت فعال که به سرویس واسط درگاه ارسال می‌شود|sepal,' WHERE `key` = 'payping_gateway_name'; +UPDATE `settings` SET `description` = 'لیست کدهای item درگاه (با کاما) برای چرخش بین چند merchant. خالی = استفاده از payping_gateway_item.' WHERE `key` = 'payping_gateway_items'; +UPDATE `settings` SET `description` = 'روشن: مبلغ پرداخت به نزدیک‌ترین تومان گرد می‌شود. خاموش: مبلغ دقیق ریالی.' WHERE `key` = 'payping_round_toman'; +UPDATE `settings` SET `description` = 'روشن: در صورت وجود برداشت ناموفق قبلی، تسویه جدید با احتیاط بیشتر انجام می‌شود.' WHERE `key` = 'payping_strict_failed_withdraw'; + +UPDATE `settings` SET `description` = 'آدرس IP یا hostname سرور نرم‌افزار ته‌حساب روی شبکه داخلی.' WHERE `key` = 'tahesab_host'; +UPDATE `settings` SET `description` = 'شماره پورت اتصال به API ته‌حساب (معمولاً 8080 یا مقدار مستندات ته‌حساب).' WHERE `key` = 'tahesab_port'; +UPDATE `settings` SET `description` = 'توکن/API Key اتصال به ته‌حساب. از پنل ته‌حساب دریافت می‌شود.' WHERE `key` = 'tahesab_token'; +UPDATE `settings` SET `description` = 'نام دیتابیس یا شناسه پایگاه در ته‌حساب که سند به آن ارسال می‌شود.' WHERE `key` = 'tahesab_db'; +UPDATE `settings` SET `description` = 'نام یا کد حساب بانکی در سند واریز/برداشت ته‌حساب.' WHERE `key` = 'tahesab_bank_account'; +UPDATE `settings` SET `description` = 'روشن: واریز و برداشت مشتریان به‌صورت خودکار به ته‌حساب ارسال می‌شود.' WHERE `key` = 'send_to_tahesab'; + +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` = 'توکن ربات تلگرام برای ارسال اعلان‌ها. از BotFather دریافت می‌شود. محرمانه.' WHERE `key` = 'telegram_bot_token'; +UPDATE `settings` SET `description` = 'توکن ربات بله برای ارسال اعلان‌ها. محرمانه.' WHERE `key` = 'bale_bot_token'; + +UPDATE `settings` SET `description` = 'روشن: پس از شارژ کیف توسط ادمین، پیامک به مشتری ارسال می‌شود.' WHERE `key` = 'charge_notif'; +UPDATE `settings` SET `description` = 'روشن: امکان ارسال پیامک گروهی از پنل ادمین فعال است.' WHERE `key` = 'group_sms'; + +UPDATE `settings` SET `description` = 'شناسه‌های عددی محصول (با کاما) که موجودی آن‌ها از کیف طلای اصلی خوانده می‌شود. خالی = غیرفعال.' WHERE `key` = 'main_wallet_products'; +UPDATE `settings` SET `description` = 'روشن: در حالت فروشگاهی قیمت از API نصیریان خوانده می‌شود (در صورت پیکربندی). فقط برای برخی نصب‌ها.' WHERE `key` = 'price_from_nasiryan'; +UPDATE `settings` SET `description` = 'روشن: API محاسبه سود طلا فعال است. در اکثر نصب‌ها خاموش می‌ماند.' WHERE `key` = 'profit_gold_api_enabled'; + COMMIT; SELECT 'deploy_settings_const.sql finished' AS status; diff --git a/routes/admin.php b/routes/admin.php index 8410fcc..f705797 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -41,6 +41,8 @@ Route::middleware('auth:user')->group(function (){ Route::post('price_triggers/table', [PriceTriggerController::class, 'table']); //-----------product Route::post('/product/table', [ProductController::class, 'tableProduct']); + Route::post('/product/archive/table', [ProductController::class, 'archiveTable']); + Route::get('/product/archive/{id}', [ProductController::class, 'archive']); Route::get('/product/active/{id}',[ProductController::class, 'active']); Route::get('/product/hide/{id}',[ProductController::class, 'hide']); Route::get('/product/credit',[ProductController::class, 'listProductCredit']);