From 71d6f6a53476fb4e50313ef45b4e080e837bad95 Mon Sep 17 00:00:00 2001 From: adonis Date: Wed, 19 Aug 2026 18:40:07 +0330 Subject: [PATCH] fix(settings): restrict superadmin template to can_edit=0 keys only Superadmin was seeding all deploy_settings_const INSERT keys including can_edit=1 entries with wrong defaults (e.g. montzar_product_id=25). Template/catalog now follow final can_edit=0 from SQL; editable keys are purged from setting_templates and tenant settings on boot/seed. Co-authored-by: Cursor --- package.json | 3 +- prisma/setting-templates-from-deploy.json | 2184 ++++++----------- scripts/sync-superadmin-setting-template.mjs | 124 + .../deploy-setting-catalog-keys.ts | 148 +- .../program-settings.service.ts | 76 +- .../setting-templates.controller.ts | 12 +- 6 files changed, 958 insertions(+), 1589 deletions(-) create mode 100644 scripts/sync-superadmin-setting-template.mjs diff --git a/package.json b/package.json index e18e24a..0f3d70b 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,8 @@ "replicas:sync": "bash deploy/sync-replica-schema.sh && bash deploy/sync-replica-data.sh --repair-if-needed", "replicas:check": "bash deploy/sync-replica-schema.sh --check && bash deploy/sync-replica-data.sh --check", "replicas:repair-data": "bash deploy/sync-replica-data.sh --repair", - "settings:seed-templates": "echo 'Templates seed on first GET /api/setting-templates or POST /api/setting-templates/seed'" + "settings:sync-template": "node scripts/sync-superadmin-setting-template.mjs", + "settings:seed-templates": "echo 'Templates seed on server boot or POST /api/setting-templates/seed'" }, "prisma": { "seed": "ts-node --transpile-only prisma/seed.ts" diff --git a/prisma/setting-templates-from-deploy.json b/prisma/setting-templates-from-deploy.json index 3c2be05..511e463 100644 --- a/prisma/setting-templates-from-deploy.json +++ b/prisma/setting-templates-from-deploy.json @@ -1,1080 +1,65 @@ [ - { - "key": "name_fa", - "value": "", - "title_fa": "نام فارسی اپ", - "can_edit": 1, - "is_active": 1, - "description": null, - "type": "text" - }, - { - "key": "app_url", - "value": "", - "title_fa": "آدرس اپ (کاربر)", - "can_edit": 1, - "is_active": 1, - "description": null, - "type": "text" - }, - { - "key": "admin_url", - "value": "", - "title_fa": "آدرس پنل ادمین", - "can_edit": 1, - "is_active": 1, - "description": null, - "type": "text" - }, - { - "key": "logo_login", - "value": "", - "title_fa": "لوگوی صفحه ورود", - "can_edit": 0, - "is_active": 0, - "description": null, - "type": "text" - }, - { - "key": "logo_register", - "value": "", - "title_fa": "لوگوی ثبت‌نام", - "can_edit": 0, - "is_active": 0, - "description": null, - "type": "text" - }, - { - "key": "has_page_earn_money", - "value": "false", - "title_fa": "صفحه کسب درآمد", - "can_edit": 1, - "is_active": 1, - "description": null, - "type": "switch" - }, - { - "key": "montzar_order_response", - "value": "[]", - "title_fa": "پاسخ سفارش همکار", - "can_edit": 0, - "is_active": 1, - "description": null, - "type": "longtext" - }, - { - "key": "montzar_last_product_log", - "value": "{}", - "title_fa": "لاگ محصول همکار", - "can_edit": 0, - "is_active": 1, - "description": null, - "type": "longtext" - }, - { - "key": "chart_use_local_data", - "value": "false", - "title_fa": "نمودار از داده خود سایت", - "can_edit": 1, - "is_active": 1, - "description": "خاموش = Zaarbox (chart_id) | روشن = تاریخچه ذخیره‌شده prices", - "type": "switch" - }, - { - "key": "chart_id", - "value": "", - "title_fa": "UUID نمودار Zaarbox", - "can_edit": 1, - "is_active": 1, - "description": "خالی = UUID پیش‌فرض سرویس", - "type": "text" - }, - { - "key": "show_last_price", - "value": "true", - "title_fa": "نمایش آخرین قیمت DB اگر API قطع شد", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "show_gold_ounce_price", - "value": "false", - "title_fa": "نمایش قیمت انس طلا", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "show_home_price_when_closed", - "value": "false", - "title_fa": "نمایش قیمت صفحه اصلی خارج از ساعت کاری", - "can_edit": 1, - "is_active": 1, - "description": "روشن: وقتی فروشگاه خارج از ساعت کاری است، قیمت صفحه اصلی اپ همچنان نمایش داده می‌شود و پیام «فروشگاه بسته است» نشان داده نمی‌شود. خرید/فروش همچنان بسته می‌ماند. خاموش: مثل قبل، خارج از ساعت پیام بسته بودن و قیمت صفر نشان داده می‌شود.", - "type": "switch" - }, - { - "key": "show_product_details", - "value": "true", - "title_fa": "نمایش جزئیات اطلاعات محصول", - "can_edit": 1, - "is_active": 1, - "description": "روشن: بعد از انتخاب محصول در صفحه خرید/فروش، متن توضیح محصول نمایش داده می‌شود. خاموش: توضیح نشان داده نمی‌شود.", - "type": "switch" - }, - { - "key": "products_site_link_enabled", - "value": "false", - "title_fa": "دکمه لینک خارجی در خرید/فروش", - "can_edit": 1, - "is_active": 1, - "description": "روشن: در صفحه خرید/فروش (بالای تب دسته‌ها) یک دکمه تمام‌عرض نشان داده می‌شود که با کلیک، لینک تنظیم‌شده را در تب جدید مرورگر باز می‌کند. خاموش: دکمه مخفی است.", - "type": "switch" - }, - { - "key": "products_site_link_url", - "value": "", - "title_fa": "آدرس لینک دکمه خرید/فروش", - "can_edit": 1, - "is_active": 1, - "description": "آدرس کامل سایت یا صفحه خارجی (مثلاً https://example.com). با کلیک روی دکمه در اپ کاربر، همین آدرس در تب جدید مرورگر باز می‌شود. اگر خالی باشد، با وجود روشن بودن سوییچ، دکمه کاری انجام نمی‌دهد.", - "type": "text" - }, - { - "key": "products_site_link_text", - "value": "", - "title_fa": "متن دکمه لینک خرید/فروش", - "can_edit": 1, - "is_active": 1, - "description": "متنی که روی دکمه لینک خارجی در صفحه خرید/فروش نمایش داده می‌شود. اگر خالی باشد، پیش‌فرض «ورود به سایت» همراه آیکون کره زمین نشان داده می‌شود.", - "type": "text" - }, - { - "key": "show_product_trade_status", - "value": "false", - "title_fa": "نمایش خرید/فروش باز روی کارت محصول", - "can_edit": 1, - "is_active": 1, - "description": "روشن: روی کارت محصول برچسب خرید باز/بسته و فروش باز/بسته نمایش داده می‌شود. خاموش: مخفی است.", - "type": "switch" - }, - { - "key": "show_product_card_meta", - "value": "true", - "title_fa": "نمایش دسته و تحویل و مقدار روی کارت محصول", - "can_edit": 1, - "is_active": 1, - "description": "روشن: روی کارت محصول دسته‌بندی، وضعیت تحویل فیزیکی و مقدار/وزن نمایش داده می‌شود. خاموش: مخفی است.", - "type": "switch" - }, - { - "key": "main_product_price", - "value": "false", - "title_fa": "قیمت از محصول (legacy)", - "can_edit": 1, - "is_active": 1, - "description": "از price_provider استفاده کنید" - }, - { - "key": "montzar_mobile", - "value": "", - "title_fa": "موبایل API همکار", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "montzar_password", - "value": "", - "title_fa": "رمز API همکار", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "montzar_product_id", - "value": "25", - "title_fa": "کد محصول همکار", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "montzar_base_url", - "value": "https://limangold.ir", - "title_fa": "آدرس سرور همکار", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "montzar_token", - "value": "", - "title_fa": "توکن همکار (خودکار)", - "can_edit": 0, - "is_active": 1, - "description": null - }, - { - "key": "montzar_open_buy", - "value": "true", - "title_fa": "باز بودن خرید همکار", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "montzar_open_sell", - "value": "true", - "title_fa": "باز بودن فروش همکار", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "montzar_diff", - "value": "0", - "title_fa": "اختلاف قیمت همکار", - "can_edit": 0, - "is_active": 1, - "description": "سیستم پر می‌کند" - }, - { - "key": "montzar_live_price", - "value": "0", - "title_fa": "قیمت لحظه‌ای همکار", - "can_edit": 0, - "is_active": 1, - "description": null - }, - { - "key": "montzar_mazaneh", - "value": "0", - "title_fa": "مظنه همکار", - "can_edit": 0, - "is_active": 1, - "description": null - }, - { - "key": "montzar_truly_buy_mazaneh", - "value": "0", - "title_fa": "مظنه خرید همکار", - "can_edit": 0, - "is_active": 1, - "description": null - }, - { - "key": "montzar_truly_sell_mazaneh", - "value": "0", - "title_fa": "مظنه فروش همکار", - "can_edit": 0, - "is_active": 1, - "description": null - }, - { - "key": "node_client_id", - "value": "", - "title_fa": "شناسه کلاینت API خارجی (X-Client-Id)", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "node_shared_secret", - "value": "", - "title_fa": "کلید امضای HMAC API خارجی", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "external_order_api_enabled", - "value": "true", - "title_fa": "فعال‌سازی API سفارش خارجی (get_wallet / order)", - "can_edit": 1, - "is_active": 1, - "description": "true + node_client_id + node_shared_secret" - }, - { - "key": "wss_enabled", - "value": "true", - "title_fa": "قیمت لحظه‌ای (SSE)", - "can_edit": 0, - "is_active": 1, - "description": "روشن: اپ به sse_path وصل می‌شود؛ خاموش: polling /api/price/last و /customer/product/list", - "type": "switch" - }, - { - "key": "sse_path", - "value": "/api/price/stream", - "title_fa": "مسیر SSE قیمت", - "can_edit": 0, - "is_active": 1, - "description": "GET لاراول — استریم event:last و event:list با payload کامل", - "type": "text" - }, - { - "key": "tracking_systems", - "value": "[]", - "title_fa": "سایت‌های ری‌گیری", - "can_edit": 1, - "is_active": 1, - "description": "JSON لیست سایت‌های ری‌گیری — هر مورد: id، name، url. از منوی «سایت‌های ری‌گیری» ویرایش می‌شود.", - "type": "longtext" - }, - { - "key": "tracking_systems_enabled", - "value": "false", - "title_fa": "نمایش سایت‌های ری‌گیری", - "can_edit": 1, - "is_active": 1, - "description": "روشن: منوی «سایت‌های ری‌گیری» در ادمین و اپ مشتری نمایش داده می‌شود. خاموش: مخفی است.", - "type": "switch" - }, - { - "key": "montify_name", - "value": "", - "title_fa": "نام فروشگاه در درگاه", - "can_edit": 0, - "is_active": 1, - "description": null, - "type": "text" - }, - { - "key": "is_store", - "value": "false", - "title_fa": "حالت فروشگاهی", - "can_edit": 0, - "is_active": 1, - "description": "فعال = سبد خرید، دسته‌بندی، حواله، تخفیف", - "type": "switch" - }, - { - "key": "jibit_verification", - "value": "true", - "title_fa": "تأیید کارت بانکی با جیبیت", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "register_jibit_verify", - "value": "true", - "title_fa": "تأیید کدملی/موبایل در ثبت‌نام", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "close_price", - "value": "false", - "title_fa": "بستن قیمت در بازه زمانی", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "startDay", - "value": "09:00", - "title_fa": "شروع بازه معامله", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "endDay", - "value": "22:00", - "title_fa": "پایان بازه معامله", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "auto_active_customer", - "value": "false", - "title_fa": "فعال‌سازی خودکار کاربر", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "fee_free", - "value": "0", - "title_fa": "تعداد سفارش اول بدون کارمزد", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "charge_notif", - "value": "true", - "title_fa": "پیامک شارژ ادمین", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "customer_direct_sms", - "value": "false", - "title_fa": "پیامک تکی به مشتری از لیست", - "can_edit": 0, - "is_active": 1, - "description": "روشن: در لیست مشتریان دکمه ارسال پیامک تکی نمایش داده می‌شود. خاموش: مخفی است. فقط از صفحه دیپلوی قابل تغییر است.", - "type": "switch" - }, - { - "key": "enable_conditional_order", - "value": "false", - "title_fa": "سفارش شرطی", - "can_edit": 0, - "is_active": 1, - "description": "روشن: قابلیت سفارش شرطی سراسری فعال است. خاموش: منو و ارزیابی اجرا نمی‌شود. فقط از دیپلوی قابل تغییر است.", - "type": "switch" - }, - { - "key": "enable_product_funds", - "value": "false", - "title_fa": "قلک محصول", - "can_edit": 0, - "is_active": 1, - "description": "روشن: ساخت و مدیریت قلک per-product در اپ مشتری فعال است (یک محصول در هر قلک؛ انتقال بدون سند حسابداری). خاموش: منو و API ساخت قلک جدید در دسترس نیست. فقط از دیپلوی قابل تغییر است.", - "type": "switch" - }, - { - "key": "has_page_gift_card", - "value": "false", - "title_fa": "کارت هدیه", - "can_edit": 0, - "is_active": 1, - "description": "روشن: منوی «ثبت کارت هدیه» و صفحه ثبت کد در اپ کاربر فعال است. خاموش: منو و صفحه مخفی است و API استفاده از کد هدیه در دسترس نیست. فقط از دیپلوی قابل تغییر است.", - "type": "switch" - }, - { - "key": "enable_customer_level_profit", - "value": "false", - "title_fa": "سود چندسطحی معرف", - "can_edit": 0, - "is_active": 1, - "description": "روشن: در لیست مشتریان آیکون تنظیم درصد سود هر سطح زیرمجموعه فعال است و پس از سفارش، سهم کارمزد طبق درصدهای هر معرف به کیف طلا واریز می‌شود. خاموش: آیکون و واریز سود چندسطحی غیرفعال است. فقط از دیپلوی قابل تغییر است.", - "type": "switch" - }, - { - "key": "has_page_price_trigger", - "value": "true", - "title_fa": "هشدار قیمت", - "can_edit": 0, - "is_active": 1, - "description": "روشن: منوی هشدار قیمت در اپ کاربر نمایش داده می‌شود. خاموش: منو و صفحه مخفی است. فقط از دیپلوی قابل تغییر است.", - "type": "switch" - }, - { - "key": "has_page_notification", - "value": "true", - "title_fa": "اعلان داخل اپ", - "can_edit": 0, - "is_active": 1, - "description": "روشن: زنگوله اعلان در اپ کاربر و منوی ثبت اعلان در ادمین فعال است. خاموش: مخفی است. فقط از دیپلوی قابل تغییر است.", - "type": "switch" - }, - { - "key": "jibit_enabled", - "value": "true", - "title_fa": "احراز هویت جیبیت", - "can_edit": 0, - "is_active": 1, - "description": "روشن: سرویس احراز هویت جیبیت (تطبیق کد ملی/موبایل و کارت بانکی) فعال است. خاموش: هیچ فراخوانی به جیبیت انجام نمی‌شود و احراز هویت خودکار کار نمی‌کند. فقط از صفحه دیپلوی قابل تغییر است.", - "type": "switch" - }, - { - "key": "fee_conditional_order", - "value": "0", - "title_fa": "کارمزد سفارش شرطی", - "can_edit": 1, - "is_active": 1, - "description": "مبلغ ریالی که با هر بار ثبت تریگر از کیف پول ریالی مشتری کم می‌شود. صفر = رایگان.", - "type": "number" - }, - { - "key": "sms_notif_order_admin", - "value": "true", - "title_fa": "پیامک خرید/فروش به ادمین", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "sms_notif_order_customer", - "value": "true", - "title_fa": "پیامک خرید/فروش به مشتری", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "sms_notif_withdraw_admin", - "value": "true", - "title_fa": "پیامک درخواست برداشت به ادمین", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "sms_notif_withdraw_otp", - "value": "true", - "title_fa": "پیامک رمز برداشت", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "sms_notif_login_otp", - "value": "true", - "title_fa": "پیامک رمز ورود", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "sms_notif_gateway_charge_admin", - "value": "true", - "title_fa": "پیامک شارژ درگاه به ادمین", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "sms_notif_register_customer", - "value": "true", - "title_fa": "پیامک خوش‌آمد ثبت‌نام", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "sms_notif_register_admin", - "value": "true", - "title_fa": "پیامک ثبت‌نام به ادمین", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "sms_notif_jibit_admin", - "value": "true", - "title_fa": "پیامک احراز هویت به ادمین", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "sms_notif_bank_card_admin", - "value": "true", - "title_fa": "پیامک کارت بانکی به ادمین", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "sms_notif_bank_card_customer", - "value": "true", - "title_fa": "پیامک تایید کارت به مشتری", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "sms_notif_fund_withdraw_admin", - "value": "true", - "title_fa": "پیامک برداشت قلک به ادمین", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "sms_notif_fund_withdraw_customer", - "value": "true", - "title_fa": "پیامک برداشت قلک به مشتری", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "sms_notif_price_trigger", - "value": "true", - "title_fa": "پیامک هشدار قیمت", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "sms_notif_wallet_alert", - "value": "true", - "title_fa": "پیامک هشدار اعتبار پنل پیامک", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "sms_notif_support_admin", - "value": "true", - "title_fa": "پیامک پیام پشتیبانی به ادمین", - "can_edit": 1, - "is_active": 1, - "description": "روشن: وقتی مشتری اولین پیام خوانده‌نشده را در چت پشتیبانی می‌فرستد و ادمین همان اتاق را باز و visible ندارد، پیامک به ادمین‌های دارای «ارسال پیامک» می‌رود. خاموش: اعلان پیامک چت نمی‌رود." - }, - { - "key": "chat_webhook_secret", - "value": "", - "title_fa": "رمز وب‌هوک چت", - "can_edit": 0, - "is_active": 1, - "description": "رمز مشترک بین لاراول و سرور چت برای اعلان پیام پشتیبانی. فقط سرور؛ در صورت خالی بودن به‌صورت خودکار ساخته می‌شود.", - "type": "text" - }, - { - "key": "chat_webhook_base_url", - "value": "", - "title_fa": "آدرس پایه وب‌هوک چت", - "can_edit": 0, - "is_active": 1, - "description": "اگر APP_URL فرانت است (مثلاً https://niazitala.ir) اینجا آدرس لاراول را بگذار (مثلاً https://niazitala.ir/server). خالی = همان APP_URL.", - "type": "text" - }, - { - "key": "gift_for_referrer", - "value": "0.005", - "title_fa": "پاداش معرف", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "gift_for_referral", - "value": "0", - "title_fa": "پاداش معرف‌شونده", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "register_referral_sms_to_referrer", - "value": "false", - "title_fa": "SMS به معرف هنگام ثبت‌نام", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "register_referral_bonus_message", - "value": "false", - "title_fa": "پیام پاداش در SMS ثبت‌نام", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "referral_gift_on_first_order", - "value": "false", - "title_fa": "پاداش معرف پس از اولین سفارش", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "referral_gift_buy_from_montzar", - "value": "true", - "title_fa": "خرید پاداش معرفی از همکار", - "can_edit": 1, - "is_active": 1, - "description": "روشن: اگر منبع قیمت montzar باشد، قبل از واریز پاداش معرف/معرف‌شونده همان وزن از همکار خرید می‌شود؛ شکست خرید = واریز نمی‌شود. خاموش: مثل قبل فقط به کیف واریز می‌شود بدون خرید از همکار.", - "type": "switch" - }, - { - "key": "referral_limit", - "value": "", - "title_fa": "محدودیت دعوت", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "group_sms", - "value": "false", - "title_fa": "پیامک گروهی", - "can_edit": 0, - "is_active": 1, - "description": "روشن: دکمه ارسال پیامک گروهی در پنل ادمین فعال است (ارسال ۱۰تایی). خاموش: ارسال پیامک گروهی غیرفعال است. فقط از تنظیمات پیشرفته.", - "type": "switch" - }, - { - "key": "customer_special_pricing", - "value": "false", - "title_fa": "قیمت ویژه مشتری", - "can_edit": 0, - "is_active": 1, - "description": "روشن: زیرمنوی قیمت ویژه در مشتریان و اعمال سود خرید/فروش پکیج روی مظنه و سفارش برای کاربران انتخاب‌شده. خاموش: غیرفعال. فقط از دیپلوی قابل تغییر است.", - "type": "switch" - }, - { - "key": "sms_panel", - "value": "true", - "title_fa": "پنل پیامک (کیف پول)", - "can_edit": 0, - "is_active": 1, - "description": "روشن: پنل پیامک و کیف پول billing فعال است. خاموش: پیامک‌ها مثل قبل ارسال می‌شوند بدون billing.", - "type": "switch" - }, - { - "key": "tahesab_host", - "value": "", - "title_fa": "آدرس IP سرور ته‌حساب", - "can_edit": 0, - "is_active": 1, - "description": null - }, - { - "key": "tahesab_port", - "value": "", - "title_fa": "پورت سرور ته‌حساب", - "can_edit": 0, - "is_active": 1, - "description": null - }, - { - "key": "tahesab_token", - "value": "", - "title_fa": "توکن ته‌حساب", - "can_edit": 0, - "is_active": 1, - "description": null - }, - { - "key": "tahesab_db", - "value": "", - "title_fa": "دیتابیس ته‌حساب", - "can_edit": 0, - "is_active": 1, - "description": null - }, - { - "key": "tahesab_bank_account", - "value": "", - "title_fa": "حساب بانکی ته‌حساب", - "can_edit": 0, - "is_active": 1, - "description": null - }, - { - "key": "send_to_tahesab", - "value": "false", - "title_fa": "ارسال به ته‌حساب (قدیمی)", - "can_edit": 0, - "is_active": 0, - "description": null - }, { "key": "accounting_provider", "value": "none", "title_fa": "سیستم حسابداری", "can_edit": 0, "is_active": 1, - "description": "none,pargar,tahesab,zarin" + "description": "none,pargar,tahesab,zarin", + "type": "" }, { - "key": "zarin_api", + "key": "admin_url", "value": "", - "title_fa": "آدرس API زرین", + "title_fa": "آدرس پنل ادمین", "can_edit": 0, "is_active": 1, - "description": "آدرس پایه وب‌سرویس زرین (بدون /ZarrinService/accounting).", + "description": "", "type": "text" }, { - "key": "zarin_token", + "key": "apk_download_link", "value": "", - "title_fa": "توکن زرین", + "title_fa": "لینک دانلود اپ اندروید", "can_edit": 0, "is_active": 1, - "description": "توکن مکمل API زرین که در نرم‌افزار زرین تعریف می‌شود.", + "description": "آدرس کامل فایل APK اندروید (https). اگر خالی باشد لینک دانلود در اپ نمایش داده نمی‌شود.", "type": "text" }, { - "key": "zarin_noe_kar", - "value": "16", - "title_fa": "نوع کار پیش‌فرض زرین", + "key": "app_url", + "value": "", + "title_fa": "آدرس اپ (کاربر)", "can_edit": 0, "is_active": 1, - "description": "کد noeKar هنگام ساخت کار ساخته در زرین. مقدار ۱۶ معمولاً گرمی است و برای sabtSanadTedadi مناسب نیست.", - "type": "number" - }, - { - "key": "zarin_noe_kar_tedadi", - "value": "17", - "title_fa": "نوع کار تعدادی زرین", - "can_edit": 0, - "is_active": 1, - "description": "noeKar برای سند تعدادی (سکه/شمش/گوی/مکعب). روی زرین معمولاً ۱۷=سکه-تعدادی یا ۱۸=اجرت تعدادی. اگر خالی/نامعتبر باشد کد از ۱۷ استفاده می‌کند.", - "type": "number" - }, - { - "key": "zarin_gorohe_kar", - "value": "52", - "title_fa": "گروه کار پیش‌فرض زرین", - "can_edit": 0, - "is_active": 1, - "description": "کد gorooheKar هنگام ساخت/جستجوی کار در زرین. برای جداسازی چند پنل روی یک زرین، برای هر پنل مقدار متفاوت بگذارید.", - "type": "number" - }, - { - "key": "zarin_name_g", - "value": "مشتری نقدی (خریدار)", - "title_fa": "نام گروه حساب مشتریان زرین", - "can_edit": 0, - "is_active": 1, - "description": "پارامتر nameG در addhesab — گروه حساب زرین. برای اپ خانگی: مشتری نقدی (خریدار) = گروه ۶. گروه همکار استفاده نشود (مظنه متفاوت است).", + "description": "", "type": "text" }, - { - "key": "zarin_uid_prefix", - "value": "", - "title_fa": "پیشوند عددی uid زرین", - "can_edit": 0, - "is_active": 1, - "description": "فقط عدد. وقتی چند پنل یک زرین دارند باید متفاوت باشد تا uid تکراری نشود.", - "type": "text" - }, - { - "key": "send_to_pargar", - "value": "false", - "title_fa": "ارسال به پرگار (قدیمی)", - "can_edit": 0, - "is_active": 0, - "description": null - }, - { - "key": "pargar_username", - "value": "", - "title_fa": "نام کاربری API پرگار", - "can_edit": 0, - "is_active": 1, - "description": null - }, - { - "key": "pargar_password", - "value": "", - "title_fa": "رمز عبور API پرگار", - "can_edit": 0, - "is_active": 1, - "description": null - }, - { - "key": "pargar_api_token", - "value": "", - "title_fa": "توکن ثابت API پرگار", - "can_edit": 0, - "is_active": 1, - "description": null - }, - { - "key": "pargar_item_id_rial", - "value": "0", - "title_fa": "کد کالای ریال پرگار", - "can_edit": 0, - "is_active": 1, - "description": "کد کالا برای اسناد واریز/برداشت ریالی در پرگار. صفر = استفاده از کد طلا با وزن صفر.", - "type": "number" - }, - { - "key": "pargar_item_id_gold", - "value": "11", - "title_fa": "کد کالای طلا پرگار", - "can_edit": 0, - "is_active": 1, - "description": "کد کالای طلا (کیف اصلی / آبشده) در پرگار برای اسناد وزن‌دار.", - "type": "number" - }, - { - "key": "telegram_bot_token", - "value": "", - "title_fa": "توکن بات تلگرام", - "can_edit": 0, - "is_active": 1, - "description": null - }, { "key": "bale_bot_token", "value": "", "title_fa": "توکن بات بله", "can_edit": 0, "is_active": 1, - "description": null + "description": "", + "type": "text" }, { - "key": "rubika_bot_token", + "key": "chart_id", "value": "", - "title_fa": "توکن بات روبیکا", + "title_fa": "UUID نمودار Zaarbox", "can_edit": 0, "is_active": 1, - "description": null + "description": "خالی = UUID پیش‌فرض سرویس", + "type": "text" }, { - "key": "price_channel_access", + "key": "chart_use_local_data", "value": "false", - "title_fa": "دسترسی ارسال قیمت به کانال", + "title_fa": "نمودار از داده خود سایت", "can_edit": 0, "is_active": 1, - "description": "روشن: گروه «ارسال قیمت به کانال» در تنظیمات عادی پنل دیده می‌شود. خاموش: مخفی است (فقط از تنظیمات پیشرفته).", - "type": "switch" - }, - { - "key": "price_channel_enabled", - "value": "false", - "title_fa": "ارسال قیمت به کانال", - "can_edit": 1, - "is_active": 1, - "description": "روشن: بعد از هر تغییر قیمت، مظنه به کانال‌های پیام‌رسان انتخاب‌شده ارسال می‌شود.", - "type": "switch" - }, - { - "key": "price_channel_messengers", - "value": "", - "title_fa": "پیام‌رسان‌های ارسال قیمت", - "can_edit": 1, - "is_active": 1, - "description": "کدام پیام‌رسان‌ها قیمت بگیرند؟ می‌توانید چند مورد را انتخاب کنید. گزینه‌ها: بله، تلگرام، روبیکا.", - "type": "multiselect" - }, - { - "key": "price_channel_diff", - "value": "0", - "title_fa": "اختلاف قیمت کانال (ریال)", - "can_edit": 1, - "is_active": 1, - "description": "مبلغی که به قیمت خرید کانال اضافه و از قیمت فروش کم می‌شود (اسپرد اضافه فقط برای کانال).", - "type": "price" - }, - { - "key": "price_channel_min_seconds", - "value": "120", - "title_fa": "حداقل فاصله ارسال (ثانیه)", - "can_edit": 1, - "is_active": 1, - "description": "حداقل فاصله بین دو ارسال به کانال؛ جلوی سیل پیام را می‌گیرد. صفر = هر تغییر قیمت.", - "type": "number" - }, - { - "key": "price_channel_message_text", - "value": "@price_channel_default_text", - "title_fa": "قالب متن پیام کانال", - "can_edit": 1, - "is_active": 1, - "description": "@price_channel_message_desc", - "type": "longtext" - }, - { - "key": "price_channel_footer_id", - "value": "", - "title_fa": "آیدی کانال در پیام", - "can_edit": 1, - "is_active": 1, - "description": "آیدی/یوزرنیم کانال برای متغیر %آیدی در قالب پیام. خالی = خط خالی.", - "type": "text" - }, - { - "key": "price_channel_footer_phone", - "value": "", - "title_fa": "شماره تماس در پیام", - "can_edit": 1, - "is_active": 1, - "description": "شماره تماس برای متغیر %شماره در قالب پیام. خالی = خط خالی.", - "type": "text" - }, - { - "key": "price_channel_footer_link", - "value": "", - "title_fa": "لینک در پیام", - "can_edit": 1, - "is_active": 1, - "description": "لینک برای متغیر %لینک در قالب پیام. خالی = خط خالی.", - "type": "text" - }, - { - "key": "price_channel_bale_to", - "value": "", - "title_fa": "مقصد ارسال بله", - "can_edit": 0, - "is_active": 1, - "description": "آیدی چنل/گروه بله برای ارسال قیمت (چند مقصد با ویرگول).", - "type": "text" - }, - { - "key": "price_channel_telegram_to", - "value": "", - "title_fa": "مقصد ارسال تلگرام", - "can_edit": 0, - "is_active": 1, - "description": "آیدی چنل/گروه تلگرام برای ارسال قیمت (چند مقصد با ویرگول).", - "type": "text" - }, - { - "key": "price_channel_rubika_to", - "value": "", - "title_fa": "مقصد ارسال روبیکا", - "can_edit": 0, - "is_active": 1, - "description": "آیدی چنل/گروه روبیکا برای ارسال قیمت (چند مقصد با ویرگول).", - "type": "text" - }, - { - "key": "change_mesghal", - "value": "4.3318", - "title_fa": "وزن مثقال (گرم)", - "can_edit": 1, - "is_active": 1, - "description": "ضریب تبدیل قیمت مثقال به گرم برای نمایش خط «هر گرم» در پیام کانال.", - "type": "number" - }, - { - "key": "support_messenger_id", - "value": "", - "title_fa": "شناسه پشتیبانی پیام‌رسان", - "can_edit": 1, - "is_active": 0, - "description": "یوزرنیم یا شماره تماس بسته به پیام‌رسان انتخاب‌شده" - }, - { - "key": "support_messenger_text", - "value": "", - "title_fa": "متن پشتیبانی پیام‌رسان", - "can_edit": 1, - "is_active": 0, - "description": "متن دکمه یا توضیح پشتیبانی در اپ کاربر" - }, - { - "key": "invite_link_text", - "value": "@invite_link_default_text", - "title_fa": "متن دعوتنامه اشتراک‌گذاری", - "can_edit": 1, - "is_active": 1, - "description": "@invite_link_desc", - "type": "longtext" - }, - { - "key": "support_messenger", - "value": "", - "title_fa": "پیام‌رسان پشتیبانی", - "can_edit": 1, - "is_active": 1, - "description": "bale,telegram,whatsapp,rubika,eitaa,soroush" - }, - { - "key": "show_to_users_chat", - "value": "deactivate", - "title_fa": "نمایش چت به کاربران", - "can_edit": 0, - "is_active": 1, - "description": "روشن: قابلیت چت پشتیبانی برای این دیپلوی فعال است (منوی ادمین و پیش‌نیاز نمایش به کاربر). خاموش: چت در ادمین و اپ کاربر مخفی است و سوییچ «چت پشتیبانی» (has_chat) از تنظیمات عادی هم برداشته می‌شود.", + "description": "خاموش = Zaarbox (chart_id) | روشن = تاریخچه ذخیره‌شده prices", "type": "switch" }, { @@ -1095,15 +80,6 @@ "description": "کلید API سرور چت جدا.", "type": "text" }, - { - "key": "chat_upload_url", - "value": "", - "title_fa": "آدرس آپلود فایل چت", - "can_edit": 0, - "is_active": 1, - "description": "URL کامل آپلود مدیا برای چت پشتیبانی.", - "type": "text" - }, { "key": "chat_download_url", "value": "", @@ -1114,111 +90,49 @@ "type": "text" }, { - "key": "has_chat", - "value": "true", - "title_fa": "چت پشتیبانی", - "can_edit": 1, - "is_active": 1, - "description": "روشن: چت پشتیبانی در اپ مشتری نمایش داده می‌شود (فقط وقتی show_to_users_chat هم روشن باشد). خاموش: دکمه و صفحه چت برای کاربر مخفی است.", - "type": "switch" - }, - { - "key": "deposit_sms_support_number", + "key": "chat_upload_url", "value": "", - "title_fa": "شماره تماس پشتیبانی", - "can_edit": 1, - "is_active": 1, - "description": "شماره تماس پشتیبانی که در صفحه واریز، درباره ما و قوانین به مشتری نشان داده می‌شود. با کلیک، تماس تلفنی برقرار می‌شود.", - "type": "number" - }, - { - "key": "deposit_account_holder", - "value": "", - "title_fa": "نام صاحب حساب واریز", - "can_edit": 1, - "is_active": 1, - "description": "نام صاحب حساب واریز — در متن راهنما و پیام پشتیبانی با ٪نام یا %نام جایگزین می‌شود.", - "type": "text" - }, - { - "key": "deposit_card_number", - "value": "", - "title_fa": "شماره کارت واریز", - "can_edit": 1, - "is_active": 1, - "description": "شماره کارت واریز — در متن‌ها با ٪کارت یا %کارت جایگزین می‌شود.", - "type": "text" - }, - { - "key": "deposit_account_number", - "value": "", - "title_fa": "شماره حساب واریز", - "can_edit": 1, - "is_active": 1, - "description": "شماره حساب بانکی واریز — در مودال راهنمای شارژ کنار کارت و شبا نمایش داده می‌شود. در متن‌ها با ٪حساب یا %حساب جایگزین می‌شود.", - "type": "text" - }, - { - "key": "deposit_sheba", - "value": "", - "title_fa": "شبا واریز", - "can_edit": 1, - "is_active": 1, - "description": "شماره شبا واریز — در متن‌ها با ٪شبا یا %شبا جایگزین می‌شود.", - "type": "text" - }, - { - "key": "deposit_method_card_to_card", - "value": "true", - "title_fa": "فعال‌سازی کارت به کارت", - "can_edit": 1, - "is_active": 1, - "description": "روشن (پیش‌فرض): روش کارت به کارت در صفحه واریز اپ نمایش داده می‌شود. خاموش: این روش از لیست روش‌های واریز حذف می‌شود.", - "type": "switch" - }, - { - "key": "deposit_method_gateway", - "value": "false", - "title_fa": "فعال‌سازی درگاه پرداخت", - "can_edit": 1, - "is_active": 1, - "description": "روشن: روش درگاه پرداخت در صفحه واریز نمایش داده می‌شود. خاموش: مخفی است. اتصال و کدهای درگاه در تنظیمات پیشرفته → روش‌های واریز تنظیم می‌شوند.", - "type": "switch" - }, - { - "key": "deposit_method_identified", - "value": "false", - "title_fa": "فعال‌سازی شناسه واریز", - "can_edit": 1, - "is_active": 1, - "description": "روشن: روش شناسه واریز در صفحه واریز نمایش داده می‌شود. خاموش: مخفی است.", - "type": "switch" - }, - { - "key": "deposit_method_shetab", - "value": "false", - "title_fa": "فعال‌سازی شبکه شتابی", - "can_edit": 1, - "is_active": 1, - "description": "روشن: روش شبکه شتابی در صفحه واریز نمایش داده می‌شود. خاموش: مخفی است.", - "type": "switch" - }, - { - "key": "deposit_support_message_text", - "value": "شماره حساب", - "title_fa": "متن پیام به پشتیبانی (واریز)", - "can_edit": 1, - "is_active": 1, - "description": "@deposit_support_msg_desc", - "type": "longtext" - }, - { - "key": "up_price", - "value": "0", - "title_fa": "بیشترین قیمت امروز", + "title_fa": "آدرس آپلود فایل چت", "can_edit": 0, "is_active": 1, - "description": null + "description": "URL کامل آپلود مدیا برای چت پشتیبانی.", + "type": "text" + }, + { + "key": "chat_webhook_base_url", + "value": "", + "title_fa": "آدرس پایه وب‌هوک چت", + "can_edit": 0, + "is_active": 1, + "description": "اگر APP_URL فرانت است (مثلاً https://niazitala.ir) اینجا آدرس لاراول را بگذار (مثلاً https://niazitala.ir/server). خالی = همان APP_URL.", + "type": "text" + }, + { + "key": "chat_webhook_secret", + "value": "", + "title_fa": "رمز وب‌هوک چت", + "can_edit": 0, + "is_active": 1, + "description": "رمز مشترک بین لاراول و سرور چت برای اعلان پیام پشتیبانی. فقط سرور؛ در صورت خالی بودن به‌صورت خودکار ساخته می‌شود.", + "type": "text" + }, + { + "key": "customer_direct_sms", + "value": "false", + "title_fa": "پیامک تکی به مشتری از لیست", + "can_edit": 0, + "is_active": 1, + "description": "روشن: در لیست مشتریان دکمه ارسال پیامک تکی نمایش داده می‌شود. خاموش: مخفی است. فقط از صفحه دیپلوی قابل تغییر است.", + "type": "switch" + }, + { + "key": "customer_special_pricing", + "value": "false", + "title_fa": "قیمت ویژه مشتری", + "can_edit": 0, + "is_active": 1, + "description": "روشن: زیرمنوی قیمت ویژه در مشتریان و اعمال سود خرید/فروش پکیج روی مظنه و سفارش برای کاربران انتخاب‌شده. خاموش: غیرفعال. فقط از دیپلوی قابل تغییر است.", + "type": "switch" }, { "key": "down_price", @@ -1226,50 +140,44 @@ "title_fa": "کمترین قیمت امروز", "can_edit": 0, "is_active": 1, - "description": null + "description": "", + "type": "price" }, { - "key": "price_extreme_date", - "value": "", - "title_fa": "تاریخ min/max روز", - "can_edit": 0, - "is_active": 1, - "description": null - }, - { - "key": "metal_type", - "value": "طلای آبشده", - "title_fa": "عنوان فلز در UI", - "can_edit": 0, - "is_active": 1, - "description": null - }, - { - "key": "main_metal", - "value": "طلا", - "title_fa": "نام فلز اصلی (ادمین)", - "can_edit": 0, - "is_active": 1, - "description": null, - "type": "text" - }, - { - "key": "external_payment_enabled", + "key": "enable_conditional_order", "value": "false", - "title_fa": "فعال‌سازی شارژ آنلاین", + "title_fa": "سفارش شرطی", "can_edit": 0, "is_active": 1, - "description": "روشن: در صورت کامل بودن BASE_URL و client_id و client_secret و gateway، دکمه پرداخت آنلاین در اپ نمایش داده می‌شود. خاموش: فقط واریز دستی.", + "description": "روشن: قابلیت سفارش شرطی سراسری فعال است. خاموش: منو و ارزیابی اجرا نمی‌شود. فقط از دیپلوی قابل تغییر است.", "type": "switch" }, { - "key": "wallet_payment_provider", - "value": "none", - "title_fa": "نوع درگاه شارژ آنلاین", + "key": "enable_customer_level_profit", + "value": "false", + "title_fa": "سود چندسطحی معرف", "can_edit": 0, "is_active": 1, - "description": "none=خاموش، montify=پارتنر Montify|none,montify", - "type": "select" + "description": "روشن: در لیست مشتریان آیکون تنظیم درصد سود هر سطح زیرمجموعه فعال است و پس از سفارش، سهم کارمزد طبق درصدهای هر معرف به کیف طلا واریز می‌شود. خاموش: آیکون و واریز سود چندسطحی غیرفعال است. فقط از دیپلوی قابل تغییر است.", + "type": "switch" + }, + { + "key": "enable_product_funds", + "value": "false", + "title_fa": "قلک محصول", + "can_edit": 0, + "is_active": 1, + "description": "روشن: ساخت و مدیریت قلک per-product در اپ مشتری فعال است (یک محصول در هر قلک؛ انتقال بدون سند حسابداری). خاموش: منو و API ساخت قلک جدید در دسترس نیست. فقط از دیپلوی قابل تغییر است.", + "type": "switch" + }, + { + "key": "external_order_api_enabled", + "value": "true", + "title_fa": "فعال‌سازی API سفارش خارجی (get_wallet / order)", + "can_edit": 0, + "is_active": 1, + "description": "true + node_client_id + node_shared_secret", + "type": "switch" }, { "key": "external_payment_base_url", @@ -1298,6 +206,15 @@ "description": "رمز مخفی partner (client_secret) — فقط سرور. در فرانت و Network تب کاربر دیده نمی‌شود.", "type": "text" }, + { + "key": "external_payment_enabled", + "value": "false", + "title_fa": "فعال‌سازی شارژ آنلاین", + "can_edit": 0, + "is_active": 1, + "description": "روشن: در صورت کامل بودن BASE_URL و client_id و client_secret و gateway، دکمه پرداخت آنلاین در اپ نمایش داده می‌شود. خاموش: فقط واریز دستی.", + "type": "switch" + }, { "key": "external_payment_gateway", "value": "zarinpall", @@ -1325,146 +242,6 @@ "description": "آدرس بک‌اند برای webhook (شارژ قطعی کیف). خالی = مسیر پیش‌فرض سرور.", "type": "text" }, - { - "key": "payping_fin_sync_enabled", - "value": "false", - "title_fa": "همگام‌سازی fin", - "can_edit": 0, - "is_active": 1, - "description": null - }, - { - "key": "payping_fin_url", - "value": "https://limangold.ir/kernel/admin/fin", - "title_fa": "URL fin", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "payping_fin_token", - "value": "", - "title_fa": "توکن fin", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "payping_fin_customer_id", - "value": "23", - "title_fa": "customer_id fin", - "can_edit": 1, - "is_active": 1, - "description": null - }, - { - "key": "payping_start_url", - "value": "https://payping.limangold.ir/payment/start", - "title_fa": "آدرس شروع درگاه", - "can_edit": 1, - "is_active": 1, - "description": null, - "type": "text" - }, - { - "key": "payping_verify_url", - "value": "https://payping.limangold.ir/payment/verify", - "title_fa": "آدرس تأیید درگاه", - "can_edit": 1, - "is_active": 1, - "description": null, - "type": "text" - }, - { - "key": "payping_gateway_name", - "value": "", - "title_fa": "نام gateway درگاه", - "can_edit": 1, - "is_active": 1, - "description": "sepal,", - "type": "select" - }, - { - "key": "payping_gateway_items", - "value": "", - "title_fa": "لیست item (چرخشی)", - "can_edit": 1, - "is_active": 1, - "description": null, - "type": "text" - }, - { - "key": "payping_round_toman", - "value": "false", - "title_fa": "گرد کردن مبلغ به تومان", - "can_edit": 1, - "is_active": 1, - "description": null, - "type": "switch" - }, - { - "key": "payping_strict_failed_withdraw", - "value": "false", - "title_fa": "اولویت تراکنش ناموفق", - "can_edit": 1, - "is_active": 1, - "description": null, - "type": "switch" - }, - { - "key": "open_register", - "value": "true", - "title_fa": "ثبت‌نام باز", - "can_edit": 1, - "is_active": 1, - "description": null, - "type": "switch" - }, - { - "key": "referral_id", - "value": "required", - "title_fa": "کد معرف اجباری", - "can_edit": 1, - "is_active": 1, - "description": "روشن = اجباری در ثبت‌نام | خاموش = اختیاری", - "type": "switch" - }, - { - "key": "referral_show", - "value": "true", - "title_fa": "نمایش فیلد کد معرف", - "can_edit": 1, - "is_active": 1, - "description": "روشن: فیلد کد معرف در صفحه ثبت‌نام اپ کاربر نمایش داده می‌شود. خاموش: فیلد مخفی است (لینک دعوت با ?id= همچنان اعمال می‌شود).", - "type": "switch" - }, - { - "key": "buy_fee", - "value": "", - "title_fa": "سود خرید نمایش", - "can_edit": 1, - "is_active": 1, - "description": null, - "type": "price" - }, - { - "key": "sell_fee", - "value": "", - "title_fa": "سود فروش نمایش", - "can_edit": 1, - "is_active": 1, - "description": null, - "type": "price" - }, - { - "key": "fee_service_charge_percentage", - "value": "0.005", - "title_fa": "درصد کارمزد سفارش", - "can_edit": 1, - "is_active": 1, - "description": null, - "type": "number" - }, { "key": "fee_deduct_from_gold", "value": "false", @@ -1475,167 +252,94 @@ "type": "switch" }, { - "key": "min_withdraw", - "value": "", - "title_fa": "حداقل برداشت (ریال)", - "can_edit": 1, - "is_active": 1, - "description": null, - "type": "price" - }, - { - "key": "min_deposit", - "value": "1000000", - "title_fa": "حداقل واریز کیف پول (ریال)", - "can_edit": 1, - "is_active": 1, - "description": "حداقل مبلغ واریز به کیف پول (ریال). مبالغ کمتر در اپ و درگاه پذیرفته نمی‌شوند.", - "type": "price" - }, - { - "key": "min_first_buy", - "value": "", - "title_fa": "حداقل اولین خرید (ریال)", - "can_edit": 1, - "is_active": 1, - "description": null, - "type": "price" - }, - { - "key": "minimum_sell", - "value": "", - "title_fa": "حداقل فروش", - "can_edit": 1, - "is_active": 1, - "description": null, - "type": "price" - }, - { - "key": "referral_bonus_amount", - "value": "", - "title_fa": "مقدار پاداش معرف", + "key": "group_sms", + "value": "false", + "title_fa": "پیامک گروهی", "can_edit": 0, "is_active": 1, - "description": null, - "type": "text" - }, - { - "key": "balance_report_exclude_customer_id", - "value": "", - "title_fa": "حذف مشتری از تراز", - "can_edit": 1, - "is_active": 1, - "description": null, - "type": "text" - }, - { - "key": "montzar_closed", - "value": "false", - "title_fa": "بسته بودن همکار", - "can_edit": 1, - "is_active": 1, - "description": null, + "description": "روشن: دکمه ارسال پیامک گروهی در پنل ادمین فعال است (ارسال ۱۰تایی). خاموش: ارسال پیامک گروهی غیرفعال است. فقط از تنظیمات پیشرفته.", "type": "switch" }, { - "key": "price_provider", - "title_fa": "منبع قیمت", - "value": "montzar", - "can_edit": 1, - "is_exist": 0, - "is_active": 1, - "type": "select", - "description": "منبع فعال قیمت|montzar,zaarbox,main_product" - }, - { - "key": "service_charge_percentage", - "title_fa": "کارمزد درصدی (alias)", - "value": "fee_service_charge_percentage", - "can_edit": 1, - "is_exist": "0.005", - "is_active": 0, - "type": 0 - }, - { - "key": "title_pwa", - "title_fa": "عنوان PWA", - "value": "", - "can_edit": 0, - "is_exist": 0, - "is_active": 0, - "type": "text" - }, - { - "key": "title_fav", - "title_fa": "عنوان favicon", - "value": "", - "can_edit": 0, - "is_exist": 0, - "is_active": 0, - "type": "text" - }, - { - "key": "text_rule", - "title_fa": "متن قوانین", - "value": "", - "can_edit": 1, - "is_exist": 0, - "is_active": 1, - "type": "longtext" - }, - { - "key": "text_about", - "title_fa": "متن درباره ما", - "value": "", - "can_edit": 1, - "is_exist": 0, - "is_active": 1, - "type": "longtext" - }, - { - "key": "zaarbox_api_url", - "title_fa": "آدرس API تابلو Zaarbox", - "value": "", - "can_edit": 1, - "is_exist": 0, - "is_active": 1, - "type": "text" - }, - { - "key": "zaarbox_price_key", - "title_fa": "شناسه ردیف تابلو (key یا UUID)", - "value": "", - "can_edit": 1, - "is_exist": 0, - "is_active": 1, - "type": "text" - }, - { - "key": "main_product", - "title_fa": "محصول آبشده", - "value": "", - "can_edit": 1, - "is_exist": 0, - "is_active": 1, - "type": "text" - }, - { - "key": "profit_gold_api_enabled", - "title_fa": "API سود طلا", + "key": "has_page_earn_money", "value": "false", - "can_edit": 1, - "is_exist": 0, - "is_active": 0, + "title_fa": "صفحه کسب درآمد", + "can_edit": 0, + "is_active": 1, + "description": "", "type": "switch" }, { - "key": "unit", - "value": "", - "title_fa": "واحد وزن دارایی و معاملات", - "can_edit": 1, + "key": "has_page_gift_card", + "value": "false", + "title_fa": "کارت هدیه", + "can_edit": 0, "is_active": 1, - "description": "واحد نمایش وزن فلز در دارایی‌ها، کیف پول و تراکنش‌ها|گرم,کیلوگرم", - "type": "select" + "description": "روشن: منوی «ثبت کارت هدیه» و صفحه ثبت کد در اپ کاربر فعال است. خاموش: منو و صفحه مخفی است و API استفاده از کد هدیه در دسترس نیست. فقط از دیپلوی قابل تغییر است.", + "type": "switch" + }, + { + "key": "has_page_notification", + "value": "true", + "title_fa": "اعلان داخل اپ", + "can_edit": 0, + "is_active": 1, + "description": "روشن: زنگوله اعلان در اپ کاربر و منوی ثبت اعلان در ادمین فعال است. خاموش: مخفی است. فقط از دیپلوی قابل تغییر است.", + "type": "switch" + }, + { + "key": "has_page_price_trigger", + "value": "true", + "title_fa": "هشدار قیمت", + "can_edit": 0, + "is_active": 1, + "description": "روشن: منوی هشدار قیمت در اپ کاربر نمایش داده می‌شود. خاموش: منو و صفحه مخفی است. فقط از دیپلوی قابل تغییر است.", + "type": "switch" + }, + { + "key": "is_store", + "value": "false", + "title_fa": "حالت فروشگاهی", + "can_edit": 0, + "is_active": 1, + "description": "فعال = سبد خرید، دسته‌بندی، حواله، تخفیف", + "type": "switch" + }, + { + "key": "jibit_enabled", + "value": "true", + "title_fa": "احراز هویت جیبیت", + "can_edit": 0, + "is_active": 1, + "description": "روشن: سرویس احراز هویت جیبیت (تطبیق کد ملی/موبایل و کارت بانکی) فعال است. خاموش: هیچ فراخوانی به جیبیت انجام نمی‌شود و احراز هویت خودکار کار نمی‌کند. فقط از صفحه دیپلوی قابل تغییر است.", + "type": "switch" + }, + { + "key": "logo_login", + "value": "", + "title_fa": "لوگوی صفحه ورود", + "can_edit": 0, + "is_active": 0, + "description": "", + "type": "text" + }, + { + "key": "logo_register", + "value": "", + "title_fa": "لوگوی ثبت‌نام", + "can_edit": 0, + "is_active": 0, + "description": "", + "type": "text" + }, + { + "key": "main_metal", + "value": "طلا", + "title_fa": "نام فلز اصلی (ادمین)", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" }, { "key": "main_wallet_products", @@ -1643,35 +347,270 @@ "title_fa": "محصولات متصل به کیف اصلی", "can_edit": 0, "is_active": 1, - "description": "محصولاتی که موجودی‌شان از کیف طلای اصلی خوانده می‌شود. چند محصول با کاما. پیش‌فرض: 13" + "description": "محصولاتی که موجودی‌شان از کیف طلای اصلی خوانده می‌شود. چند محصول با کاما. پیش‌فرض: 13", + "type": "" }, { - "key": "require_zero_main_gold_before_other_buy", - "value": "true", - "title_fa": "الزام صفر بودن کیف اصلی قبل از خرید محصول دیگر", - "can_edit": 1, - "is_active": 1, - "description": "روشن: اگر مانده طلای کیف اصلی صفر یا زیر صفر باشد (بدهی)، مشتری ابتدا باید آبشده/مانده کیف اصلی را صفر کند و بعد می‌تواند محصول دیگر بخرد. خاموش: بدون این محدودیت. معامله خودِ محصولات کیف اصلی همیشه مجاز است.", - "type": "switch" - }, - { - "key": "apk_download_link", - "value": "", - "title_fa": "لینک دانلود اپ اندروید", + "key": "metal_type", + "value": "طلای آبشده", + "title_fa": "عنوان فلز در UI", "can_edit": 0, "is_active": 1, - "description": "آدرس کامل فایل APK اندروید (https). اگر خالی باشد لینک دانلود در اپ نمایش داده نمی‌شود.", + "description": "", "type": "text" }, { - "key": "banner_enabled", - "value": "true", - "title_fa": "نمایش بنرها", - "can_edit": 1, + "key": "montify_name", + "value": "", + "title_fa": "نام فروشگاه در درگاه", + "can_edit": 0, "is_active": 1, - "description": "روشن: منوی «بنرها» در ادمین و اسلایدر صفحه اصلی اپ مشتری نمایش داده می‌شود. خاموش: مخفی است (محتوای بنر از همان منو مدیریت می‌شود).", + "description": "", + "type": "text" + }, + { + "key": "montzar_base_url", + "value": "https://limangold.ir", + "title_fa": "آدرس سرور همکار", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "montzar_closed", + "value": "false", + "title_fa": "بسته بودن همکار", + "can_edit": 0, + "is_active": 1, + "description": "", "type": "switch" }, + { + "key": "montzar_diff", + "value": "0", + "title_fa": "اختلاف قیمت همکار", + "can_edit": 0, + "is_active": 1, + "description": "سیستم پر می‌کند", + "type": "text" + }, + { + "key": "montzar_last_product_log", + "value": "{}", + "title_fa": "لاگ محصول همکار", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "longtext" + }, + { + "key": "montzar_live_price", + "value": "0", + "title_fa": "قیمت لحظه‌ای همکار", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "montzar_mazaneh", + "value": "0", + "title_fa": "مظنه همکار", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "montzar_order_response", + "value": "[]", + "title_fa": "پاسخ سفارش همکار", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "longtext" + }, + { + "key": "montzar_token", + "value": "", + "title_fa": "توکن همکار (خودکار)", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "montzar_truly_buy_mazaneh", + "value": "0", + "title_fa": "مظنه خرید همکار", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "montzar_truly_sell_mazaneh", + "value": "0", + "title_fa": "مظنه فروش همکار", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "node_client_id", + "value": "", + "title_fa": "شناسه کلاینت API خارجی (X-Client-Id)", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "node_shared_secret", + "value": "", + "title_fa": "کلید امضای HMAC API خارجی", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "pargar_api_token", + "value": "", + "title_fa": "توکن ثابت API پرگار", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "pargar_item_id_gold", + "value": "11", + "title_fa": "کد کالای طلا پرگار", + "can_edit": 0, + "is_active": 1, + "description": "کد کالای طلا (کیف اصلی / آبشده) در پرگار برای اسناد وزن‌دار.", + "type": "number" + }, + { + "key": "pargar_item_id_rial", + "value": "0", + "title_fa": "کد کالای ریال پرگار", + "can_edit": 0, + "is_active": 1, + "description": "کد کالا برای اسناد واریز/برداشت ریالی در پرگار. صفر = استفاده از کد طلا با وزن صفر.", + "type": "number" + }, + { + "key": "pargar_password", + "value": "", + "title_fa": "رمز عبور API پرگار", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "pargar_username", + "value": "", + "title_fa": "نام کاربری API پرگار", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "payping_fin_customer_id", + "value": "23", + "title_fa": "customer_id fin", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "number" + }, + { + "key": "payping_fin_sync_enabled", + "value": "false", + "title_fa": "همگام‌سازی fin", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "switch" + }, + { + "key": "payping_fin_token", + "value": "", + "title_fa": "توکن fin", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "payping_fin_url", + "value": "https://limangold.ir/kernel/admin/fin", + "title_fa": "URL fin", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "payping_gateway_items", + "value": "", + "title_fa": "لیست item (چرخشی)", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "payping_gateway_name", + "value": "", + "title_fa": "نام gateway درگاه", + "can_edit": 0, + "is_active": 1, + "description": "sepal,", + "type": "select" + }, + { + "key": "payping_round_toman", + "value": "false", + "title_fa": "گرد کردن مبلغ به تومان", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "switch" + }, + { + "key": "payping_start_url", + "value": "https://payping.limangold.ir/payment/start", + "title_fa": "آدرس شروع درگاه", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "payping_strict_failed_withdraw", + "value": "false", + "title_fa": "اولویت تراکنش ناموفق", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "switch" + }, + { + "key": "payping_verify_url", + "value": "https://payping.limangold.ir/payment/verify", + "title_fa": "آدرس تأیید درگاه", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, { "key": "permision__enabled", "value": "false", @@ -1680,5 +619,266 @@ "is_active": 1, "description": "روشن: آیتم «مجوز ها» در منوی تنظیمات اپ خانگی (تم جدید) نمایش داده می‌شود. خاموش: مخفی است.", "type": "switch" + }, + { + "key": "price_channel_access", + "value": "false", + "title_fa": "دسترسی ارسال قیمت به کانال", + "can_edit": 0, + "is_active": 1, + "description": "روشن: گروه «ارسال قیمت به کانال» در تنظیمات عادی پنل دیده می‌شود. خاموش: مخفی است (فقط از تنظیمات پیشرفته).", + "type": "switch" + }, + { + "key": "price_channel_bale_to", + "value": "", + "title_fa": "مقصد ارسال بله", + "can_edit": 0, + "is_active": 1, + "description": "آیدی چنل/گروه بله برای ارسال قیمت (چند مقصد با ویرگول).", + "type": "text" + }, + { + "key": "price_channel_rubika_to", + "value": "", + "title_fa": "مقصد ارسال روبیکا", + "can_edit": 0, + "is_active": 1, + "description": "آیدی چنل/گروه روبیکا برای ارسال قیمت (چند مقصد با ویرگول).", + "type": "text" + }, + { + "key": "price_channel_telegram_to", + "value": "", + "title_fa": "مقصد ارسال تلگرام", + "can_edit": 0, + "is_active": 1, + "description": "آیدی چنل/گروه تلگرام برای ارسال قیمت (چند مقصد با ویرگول).", + "type": "text" + }, + { + "key": "price_extreme_date", + "value": "", + "title_fa": "تاریخ min/max روز", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "referral_bonus_amount", + "value": "", + "title_fa": "مقدار پاداش معرف", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "rubika_bot_token", + "value": "", + "title_fa": "توکن بات روبیکا", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "" + }, + { + "key": "send_to_pargar", + "value": "false", + "title_fa": "ارسال به پرگار (قدیمی)", + "can_edit": 0, + "is_active": 0, + "description": "", + "type": "" + }, + { + "key": "send_to_tahesab", + "value": "false", + "title_fa": "ارسال به ته‌حساب (قدیمی)", + "can_edit": 0, + "is_active": 0, + "description": "", + "type": "" + }, + { + "key": "show_to_users_chat", + "value": "deactivate", + "title_fa": "نمایش چت به کاربران", + "can_edit": 0, + "is_active": 1, + "description": "روشن: قابلیت چت پشتیبانی برای این دیپلوی فعال است (منوی ادمین و پیش‌نیاز نمایش به کاربر). خاموش: چت در ادمین و اپ کاربر مخفی است و سوییچ «چت پشتیبانی» (has_chat) از تنظیمات عادی هم برداشته می‌شود.", + "type": "switch" + }, + { + "key": "sms_panel", + "value": "true", + "title_fa": "پنل پیامک (کیف پول)", + "can_edit": 0, + "is_active": 1, + "description": "روشن: پنل پیامک و کیف پول billing فعال است. خاموش: پیامک‌ها مثل قبل ارسال می‌شوند بدون billing.", + "type": "switch" + }, + { + "key": "sse_path", + "value": "/api/price/stream", + "title_fa": "مسیر SSE قیمت", + "can_edit": 0, + "is_active": 1, + "description": "GET لاراول — استریم event:last و event:list با payload کامل", + "type": "text" + }, + { + "key": "tahesab_bank_account", + "value": "", + "title_fa": "حساب بانکی ته‌حساب", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "tahesab_db", + "value": "", + "title_fa": "دیتابیس ته‌حساب", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "tahesab_host", + "value": "", + "title_fa": "آدرس IP سرور ته‌حساب", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "tahesab_port", + "value": "", + "title_fa": "پورت سرور ته‌حساب", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "number" + }, + { + "key": "tahesab_token", + "value": "", + "title_fa": "توکن ته‌حساب", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "telegram_bot_token", + "value": "", + "title_fa": "توکن بات تلگرام", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "text" + }, + { + "key": "unit", + "value": "", + "title_fa": "واحد وزن دارایی و معاملات", + "can_edit": 0, + "is_active": 1, + "description": "واحد نمایش وزن فلز در دارایی‌ها، کیف پول و تراکنش‌ها|گرم,کیلوگرم", + "type": "select" + }, + { + "key": "up_price", + "value": "0", + "title_fa": "بیشترین قیمت امروز", + "can_edit": 0, + "is_active": 1, + "description": "", + "type": "price" + }, + { + "key": "wallet_payment_provider", + "value": "none", + "title_fa": "نوع درگاه شارژ آنلاین", + "can_edit": 0, + "is_active": 1, + "description": "none=خاموش، montify=پارتنر Montify|none,montify", + "type": "select" + }, + { + "key": "wss_enabled", + "value": "true", + "title_fa": "قیمت لحظه‌ای (SSE)", + "can_edit": 0, + "is_active": 1, + "description": "روشن: اپ به sse_path وصل می‌شود؛ خاموش: polling /api/price/last و /customer/product/list", + "type": "switch" + }, + { + "key": "zarin_api", + "value": "", + "title_fa": "آدرس API زرین", + "can_edit": 0, + "is_active": 1, + "description": "آدرس پایه وب‌سرویس زرین (بدون /ZarrinService/accounting).", + "type": "text" + }, + { + "key": "zarin_gorohe_kar", + "value": "52", + "title_fa": "گروه کار پیش‌فرض زرین", + "can_edit": 0, + "is_active": 1, + "description": "کد gorooheKar هنگام ساخت/جستجوی کار در زرین. برای جداسازی چند پنل روی یک زرین، برای هر پنل مقدار متفاوت بگذارید.", + "type": "number" + }, + { + "key": "zarin_name_g", + "value": "مشتری نقدی (خریدار)", + "title_fa": "نام گروه حساب مشتریان زرین", + "can_edit": 0, + "is_active": 1, + "description": "پارامتر nameG در addhesab — گروه حساب زرین. برای اپ خانگی: مشتری نقدی (خریدار) = گروه ۶. گروه همکار استفاده نشود (مظنه متفاوت است).", + "type": "text" + }, + { + "key": "zarin_noe_kar", + "value": "16", + "title_fa": "نوع کار پیش‌فرض زرین", + "can_edit": 0, + "is_active": 1, + "description": "کد noeKar هنگام ساخت کار ساخته در زرین. مقدار ۱۶ معمولاً گرمی است و برای sabtSanadTedadi مناسب نیست.", + "type": "number" + }, + { + "key": "zarin_noe_kar_tedadi", + "value": "17", + "title_fa": "نوع کار تعدادی زرین", + "can_edit": 0, + "is_active": 1, + "description": "noeKar برای سند تعدادی (سکه/شمش/گوی/مکعب). روی زرین معمولاً ۱۷=سکه-تعدادی یا ۱۸=اجرت تعدادی. اگر خالی/نامعتبر باشد کد از ۱۷ استفاده می‌کند.", + "type": "number" + }, + { + "key": "zarin_token", + "value": "", + "title_fa": "توکن زرین", + "can_edit": 0, + "is_active": 1, + "description": "توکن مکمل API زرین که در نرم‌افزار زرین تعریف می‌شود.", + "type": "text" + }, + { + "key": "zarin_uid_prefix", + "value": "", + "title_fa": "پیشوند عددی uid زرین", + "can_edit": 0, + "is_active": 1, + "description": "فقط عدد. وقتی چند پنل یک زرین دارند باید متفاوت باشد تا uid تکراری نشود.", + "type": "text" } -] \ No newline at end of file +] diff --git a/scripts/sync-superadmin-setting-template.mjs b/scripts/sync-superadmin-setting-template.mjs new file mode 100644 index 0000000..d5c3be6 --- /dev/null +++ b/scripts/sync-superadmin-setting-template.mjs @@ -0,0 +1,124 @@ +#!/usr/bin/env node +/** + * منبع حقیقت: khaneghi_server/database/sql/deploy_settings_const.sql + * خروجی: + * - prisma/setting-templates-from-deploy.json (فقط can_edit=0 نهایی) + * - src/program-settings/deploy-setting-catalog-keys.ts + */ +import { readFileSync, writeFileSync } from 'fs'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const root = join(__dirname, '..'); +const sqlPath = join( + root, + '../khaneghi_server/database/sql/deploy_settings_const.sql', +); + +const sql = readFileSync(sqlPath, 'utf8'); + +/** @type {Map} */ +const rows = new Map(); + +function unquote(s) { + return String(s ?? '') + .trim() + .replace(/^'/, '') + .replace(/'$/, '') + .replace(/\\'/g, "'") + .replace(/\\n/g, '\n'); +} + +// INSERT … SELECT 'key', 'value', … +for (const m of sql.matchAll( + /SELECT\s+'([^']+)',\s+'((?:\\'|[^'])*)',\s+'((?:\\'|[^'])*)',\s+(\d+),\s+(\d+)(?:,\s+(NULL|'(?:\\'|[^'])*'))(?:,\s+'((?:\\'|[^'])*)')?/g, +)) { + const key = m[1]; + const value = unquote(`'${m[2]}'`); + const title_fa = unquote(`'${m[3]}'`); + const can_edit = Number(m[4]); + const is_active = Number(m[5]); + let description = ''; + if (m[6] && m[6] !== 'NULL') description = unquote(m[6]); + const type = m[7] ? unquote(`'${m[7]}'`) : ''; + rows.set(key, { key, value, title_fa, can_edit, is_active, description, type }); +} + +function applyCanEdit(block, value) { + const keys = [...block.matchAll(/'([^']+)'/g)].map((x) => x[1]); + for (const key of keys) { + const row = rows.get(key); + if (row) row.can_edit = value; + } +} + +// UPDATE can_edit = 0/1 batches +for (const m of sql.matchAll( + /UPDATE\s+`settings`\s+SET\s+`can_edit`\s*=\s*(\d)(?:,\s*`[^`]+`\s*=[^W]+)?\s+WHERE\s+`key`\s+IN\s*\(([\s\S]*?)\)\s*;/g, +)) { + applyCanEdit(m[2], Number(m[1])); +} + +for (const m of sql.matchAll( + /UPDATE\s+`settings`\s+SET\s+`can_edit`\s*=\s*(\d)\s+WHERE\s+`key`\s*=\s*'([^']+)'/g, +)) { + const row = rows.get(m[2]); + if (row) row.can_edit = Number(m[1]); +} + +// zarin_* wildcard +if (/UPDATE\s+`settings`\s+SET\s+`can_edit`\s*=\s*0,\s*`is_active`\s*=\s*1\s+WHERE\s+`key`\s+LIKE\s+'zarin_%'/.test(sql)) { + for (const [key, row] of rows) { + if (key.startsWith('zarin_')) row.can_edit = 0; + } +} + +// type-only UPDATE batches — enrich type when present +for (const m of sql.matchAll( + /UPDATE\s+`settings`\s+SET\s+`type`\s*=\s*'([^']+)'\s+WHERE\s+`key`\s+IN\s*\(([\s\S]*?)\)\s*;/g, +)) { + const type = m[1]; + for (const key of [...m[2].matchAll(/'([^']+)'/g)].map((x) => x[1])) { + const row = rows.get(key); + if (row && type) row.type = type; + } +} + +const systemRows = [...rows.values()] + .filter((r) => r.can_edit === 0) + .sort((a, b) => a.key.localeCompare(b.key)); + +const editableRows = [...rows.values()] + .filter((r) => r.can_edit === 1) + .sort((a, b) => a.key.localeCompare(b.key)); + +const jsonOut = systemRows.map((r) => ({ + key: r.key, + value: r.value ?? '', + title_fa: r.title_fa ?? r.key, + can_edit: 0, + is_active: r.is_active ?? 1, + description: r.description ?? '', + type: r.type ?? '', +})); + +const tsKeys = `/** + * کلیدهای can_edit=0 در deploy_settings_const.sql — فقط همین‌ها در قالب سوپرادمین. + * تولید خودکار: node scripts/sync-superadmin-setting-template.mjs + */ +export const DEPLOY_SETTING_CATALOG_KEYS = new Set([ +${systemRows.map((r) => ` ${JSON.stringify(r.key)},`).join('\n')} +]); + +/** کلیدهای can_edit=1 — نباید در قالب/جدول settings سوپرادمین باشند */ +export const DEPLOY_SETTING_EDITABLE_KEYS = new Set([ +${editableRows.map((r) => ` ${JSON.stringify(r.key)},`).join('\n')} +]); +`; + +writeFileSync(join(root, 'prisma/setting-templates-from-deploy.json'), `${JSON.stringify(jsonOut, null, 2)}\n`); +writeFileSync(join(root, 'src/program-settings/deploy-setting-catalog-keys.ts'), `${tsKeys}\n`); + +console.log(`system keys (can_edit=0): ${systemRows.length}`); +console.log(`editable keys (can_edit=1): ${editableRows.length}`); diff --git a/src/program-settings/deploy-setting-catalog-keys.ts b/src/program-settings/deploy-setting-catalog-keys.ts index 853a5fe..1d200b9 100644 --- a/src/program-settings/deploy-setting-catalog-keys.ts +++ b/src/program-settings/deploy-setting-catalog-keys.ts @@ -1,20 +1,13 @@ /** - * کلیدهای INSERT در khaneghi_server/database/sql/deploy_settings_const.sql - * منبع قالب ایمپورت سوپرادمین — داخل باندل تا به فایل prisma وابسته نباشد. - * این ۱۹۵ کلید با SQL فعلی هم‌تراز است؛ کلید داخل این مجموعه در مودال «تصمیم برای هر کلید» نمی‌آید. + * کلیدهای can_edit=0 در deploy_settings_const.sql — فقط همین‌ها در قالب سوپرادمین. + * تولید خودکار: node scripts/sync-superadmin-setting-template.mjs */ export const DEPLOY_SETTING_CATALOG_KEYS = new Set([ "accounting_provider", "admin_url", "apk_download_link", "app_url", - "auto_active_customer", - "balance_report_exclude_customer_id", "bale_bot_token", - "banner_enabled", - "buy_fee", - "change_mesghal", - "charge_notif", "chart_id", "chart_use_local_data", "chat_api", @@ -23,24 +16,12 @@ export const DEPLOY_SETTING_CATALOG_KEYS = new Set([ "chat_upload_url", "chat_webhook_base_url", "chat_webhook_secret", - "close_price", "customer_direct_sms", "customer_special_pricing", - "deposit_account_holder", - "deposit_account_number", - "deposit_card_number", - "deposit_method_card_to_card", - "deposit_method_gateway", - "deposit_method_identified", - "deposit_method_shetab", - "deposit_sheba", - "deposit_sms_support_number", - "deposit_support_message_text", "down_price", "enable_conditional_order", "enable_customer_level_profit", "enable_product_funds", - "endDay", "external_order_api_enabled", "external_payment_base_url", "external_payment_client_id", @@ -49,33 +30,19 @@ export const DEPLOY_SETTING_CATALOG_KEYS = new Set([ "external_payment_gateway", "external_payment_return_url", "external_payment_webhook_url", - "fee_conditional_order", "fee_deduct_from_gold", - "fee_free", - "fee_service_charge_percentage", - "gift_for_referral", - "gift_for_referrer", "group_sms", - "has_chat", "has_page_earn_money", "has_page_gift_card", "has_page_notification", "has_page_price_trigger", - "invite_link_text", "is_store", "jibit_enabled", - "jibit_verification", "logo_login", "logo_register", "main_metal", - "main_product", - "main_product_price", "main_wallet_products", "metal_type", - "min_deposit", - "min_first_buy", - "min_withdraw", - "minimum_sell", "montify_name", "montzar_base_url", "montzar_closed", @@ -83,19 +50,12 @@ export const DEPLOY_SETTING_CATALOG_KEYS = new Set([ "montzar_last_product_log", "montzar_live_price", "montzar_mazaneh", - "montzar_mobile", - "montzar_open_buy", - "montzar_open_sell", "montzar_order_response", - "montzar_password", - "montzar_product_id", "montzar_token", "montzar_truly_buy_mazaneh", "montzar_truly_sell_mazaneh", - "name_fa", "node_client_id", "node_shared_secret", - "open_register", "pargar_api_token", "pargar_item_id_gold", "pargar_item_id_rial", @@ -114,23 +74,83 @@ export const DEPLOY_SETTING_CATALOG_KEYS = new Set([ "permision__enabled", "price_channel_access", "price_channel_bale_to", + "price_channel_rubika_to", + "price_channel_telegram_to", + "price_extreme_date", + "referral_bonus_amount", + "rubika_bot_token", + "send_to_pargar", + "send_to_tahesab", + "show_to_users_chat", + "sms_panel", + "sse_path", + "tahesab_bank_account", + "tahesab_db", + "tahesab_host", + "tahesab_port", + "tahesab_token", + "telegram_bot_token", + "unit", + "up_price", + "wallet_payment_provider", + "wss_enabled", + "zarin_api", + "zarin_gorohe_kar", + "zarin_name_g", + "zarin_noe_kar", + "zarin_noe_kar_tedadi", + "zarin_token", + "zarin_uid_prefix", +]); + +/** کلیدهای can_edit=1 — نباید در قالب/جدول settings سوپرادمین باشند */ +export const DEPLOY_SETTING_EDITABLE_KEYS = new Set([ + "auto_active_customer", + "balance_report_exclude_customer_id", + "banner_enabled", + "buy_fee", + "change_mesghal", + "charge_notif", + "close_price", + "deposit_account_holder", + "deposit_account_number", + "deposit_card_number", + "deposit_method_card_to_card", + "deposit_method_gateway", + "deposit_method_identified", + "deposit_method_shetab", + "deposit_sheba", + "deposit_sms_support_number", + "endDay", + "fee_conditional_order", + "fee_free", + "fee_service_charge_percentage", + "gift_for_referral", + "gift_for_referrer", + "has_chat", + "jibit_verification", + "main_product_price", + "min_deposit", + "min_first_buy", + "min_withdraw", + "minimum_sell", + "montzar_mobile", + "montzar_open_buy", + "montzar_open_sell", + "montzar_password", + "montzar_product_id", + "name_fa", + "open_register", "price_channel_diff", "price_channel_enabled", "price_channel_footer_id", "price_channel_footer_link", "price_channel_footer_phone", - "price_channel_message_text", "price_channel_messengers", "price_channel_min_seconds", - "price_channel_rubika_to", - "price_channel_telegram_to", - "price_extreme_date", - "price_provider", "products_site_link_enabled", "products_site_link_text", "products_site_link_url", - "profit_gold_api_enabled", - "referral_bonus_amount", "referral_gift_buy_from_montzar", "referral_gift_on_first_order", "referral_id", @@ -140,18 +160,13 @@ export const DEPLOY_SETTING_CATALOG_KEYS = new Set([ "register_referral_bonus_message", "register_referral_sms_to_referrer", "require_zero_main_gold_before_other_buy", - "rubika_bot_token", "sell_fee", - "send_to_pargar", - "send_to_tahesab", - "service_charge_percentage", "show_gold_ounce_price", "show_home_price_when_closed", "show_last_price", "show_product_card_meta", "show_product_details", "show_product_trade_status", - "show_to_users_chat", "sms_notif_bank_card_admin", "sms_notif_bank_card_customer", "sms_notif_fund_withdraw_admin", @@ -168,36 +183,11 @@ export const DEPLOY_SETTING_CATALOG_KEYS = new Set([ "sms_notif_wallet_alert", "sms_notif_withdraw_admin", "sms_notif_withdraw_otp", - "sms_panel", - "sse_path", "startDay", "support_messenger", "support_messenger_id", "support_messenger_text", - "tahesab_bank_account", - "tahesab_db", - "tahesab_host", - "tahesab_port", - "tahesab_token", - "telegram_bot_token", - "text_about", - "text_rule", - "title_fav", - "title_pwa", "tracking_systems", "tracking_systems_enabled", - "unit", - "up_price", - "wallet_payment_provider", - "wss_enabled", - "zaarbox_api_url", - "zaarbox_price_key", - "zarin_api", - "zarin_gorohe_kar", - "zarin_name_g", - "zarin_noe_kar", - "zarin_noe_kar_tedadi", - "zarin_token", - "zarin_uid_prefix", ]); diff --git a/src/program-settings/program-settings.service.ts b/src/program-settings/program-settings.service.ts index 58a78f0..1d1de75 100644 --- a/src/program-settings/program-settings.service.ts +++ b/src/program-settings/program-settings.service.ts @@ -17,7 +17,10 @@ import { AuditService } from '../audit/audit.service'; import { ReplicaWatchService } from '../replica-watch/replica-watch.service'; import { isSecretOrServerOnlySettingKey } from './secret-setting-keys'; import { isRuntimeProgramSettingKey } from './runtime-setting-keys'; -import { DEPLOY_SETTING_CATALOG_KEYS } from './deploy-setting-catalog-keys'; +import { + DEPLOY_SETTING_CATALOG_KEYS, + DEPLOY_SETTING_EDITABLE_KEYS, +} from './deploy-setting-catalog-keys'; export type ProgramSettingRow = { id?: string | number; @@ -67,6 +70,12 @@ export class ProgramSettingsService implements OnModuleInit { if (result.seeded) { this.logger.log(`setting_templates seeded (${result.count} keys)`); } + const purged = await this.purgeEditableFromSuperAdmin(); + if (purged.templates || purged.settings) { + this.logger.log( + `purged can_edit=1 from superadmin (templates=${purged.templates}, settings=${purged.settings})`, + ); + } await this.seedEmptyKhanegiTenants(); } catch (error: any) { this.logger.warn(`setting_templates seed skipped: ${error?.message || error}`); @@ -113,7 +122,8 @@ export class ProgramSettingsService implements OnModuleInit { const tenant = await this.requireKhanegiTenant(tenantId); const incoming = this.asSettingsArray(itemsRaw) .map((row) => this.normalizeIncoming(row)) - .filter((row) => row.key && !isRuntimeProgramSettingKey(row.key)); + .filter((row) => row.key && !isRuntimeProgramSettingKey(row.key)) + .filter((row) => this.isSystemProgramSettingKey(row.key)); if (!incoming.length) { throw new BadRequestException('هیچ تنظیم معتبری برای ذخیره ارسال نشده'); @@ -292,6 +302,7 @@ export class ProgramSettingsService implements OnModuleInit { await this.ensureTemplateDefaultsForTenant(tx, tenantId); for (const [key, row] of remoteByKey) { + if (!this.isSystemProgramSettingKey(key)) continue; const inCatalog = catalogKeys.has(key); const scope = inCatalog ? 'this' : (decisionMap.get(key) ?? 'this'); if (scope === 'none') continue; @@ -346,7 +357,10 @@ export class ProgramSettingsService implements OnModuleInit { async listTemplates() { await this.ensureTemplatesSeeded(); const rows = await this.prisma.settingTemplate.findMany({ - where: { deletedAt: null }, + where: { + deletedAt: null, + key: { in: [...DEPLOY_SETTING_CATALOG_KEYS] }, + }, orderBy: { key: 'asc' }, }); return { @@ -359,7 +373,8 @@ export class ProgramSettingsService implements OnModuleInit { async addKeys(itemsRaw: Array>, userId?: string) { const incoming = this.asSettingsArray(itemsRaw) .map((row) => this.normalizeIncoming(row)) - .filter((row) => row.key && !isRuntimeProgramSettingKey(row.key)); + .filter((row) => row.key && !isRuntimeProgramSettingKey(row.key)) + .filter((row) => this.isSystemProgramSettingKey(row.key)); if (!incoming.length) { throw new BadRequestException('هیچ کلید معتبری ارسال نشده'); } @@ -440,7 +455,10 @@ export class ProgramSettingsService implements OnModuleInit { async seedTenantFromTemplates(tenantId: string) { await this.ensureTemplatesSeeded(); const templates = await this.prisma.settingTemplate.findMany({ - where: { deletedAt: null }, + where: { + deletedAt: null, + key: { in: [...DEPLOY_SETTING_CATALOG_KEYS] }, + }, }); if (!templates.length) return { count: 0 }; @@ -479,6 +497,8 @@ export class ProgramSettingsService implements OnModuleInit { for (const row of raw) { const key = String(row.key || '').trim(); if (!key || isRuntimeProgramSettingKey(key)) continue; + if (!this.isSystemProgramSettingKey(key)) continue; + if (this.toSmallInt(row.can_edit, 0) !== 0) continue; const existing = await tx.settingTemplate.findUnique({ where: { key } }); if (existing) { if (existing.deletedAt) { @@ -521,17 +541,37 @@ export class ProgramSettingsService implements OnModuleInit { } } - /** - * قالب ایمپورت = کلیدهای INSERT در deploy_settings_const.sql - * (باندل + JSON هم‌تراز همان فایل). جدول setting_templates منبع قالب نیست. - */ + /** فقط can_edit=0 نهایی deploy_settings_const — نه کل INSERTها */ private deployCatalogKeySet() { - const keys = new Set(DEPLOY_SETTING_CATALOG_KEYS); - for (const row of this.readSeedRows()) { - const key = String(row.key || '').trim(); - if (key) keys.add(key); + return new Set(DEPLOY_SETTING_CATALOG_KEYS); + } + + private isSystemProgramSettingKey(key: string) { + const k = String(key || '').trim(); + if (!k || isRuntimeProgramSettingKey(k)) return false; + if (DEPLOY_SETTING_EDITABLE_KEYS.has(k)) return false; + return DEPLOY_SETTING_CATALOG_KEYS.has(k); + } + + /** حذف can_edit=1 از قالب و همهٔ پروژه‌های خانگی در DB سوپرادمین */ + async purgeEditableFromSuperAdmin() { + const keys = [...DEPLOY_SETTING_EDITABLE_KEYS]; + if (!keys.length) return { templates: 0, settings: 0 }; + + const result = await this.prisma.$transaction(async (tx: any) => { + const templates = await tx.settingTemplate.deleteMany({ + where: { key: { in: keys } }, + }); + const settings = await tx.setting.deleteMany({ + where: { key: { in: keys } }, + }); + return { templates: templates.count, settings: settings.count }; + }); + + if (result.templates || result.settings) { + this.replicaWatch.kick(); } - return keys; + return result; } private deployCatalogCanEditMap() { @@ -571,6 +611,7 @@ export class ProgramSettingsService implements OnModuleInit { }); return rows .filter((row: Setting) => !isRuntimeProgramSettingKey(row.key)) + .filter((row: Setting) => this.isSystemProgramSettingKey(row.key)) .map((row: Setting) => this.toApiRow(row)); } @@ -656,7 +697,12 @@ export class ProgramSettingsService implements OnModuleInit { tx: any, tenantId: string, ) { - const templates = await tx.settingTemplate.findMany({ where: { deletedAt: null } }); + const templates = await tx.settingTemplate.findMany({ + where: { + deletedAt: null, + key: { in: [...DEPLOY_SETTING_CATALOG_KEYS] }, + }, + }); for (const tpl of templates) { const existing = await tx.setting.findUnique({ where: { tenantId_key: { tenantId, key: tpl.key } }, diff --git a/src/program-settings/setting-templates.controller.ts b/src/program-settings/setting-templates.controller.ts index ced5fd0..1911af8 100644 --- a/src/program-settings/setting-templates.controller.ts +++ b/src/program-settings/setting-templates.controller.ts @@ -36,7 +36,15 @@ export class SettingTemplatesController { @Post('seed') @RequirePermissions('khanegi:write') - seed() { - return this.programSettings.ensureTemplatesSeeded(); + async seed() { + const seeded = await this.programSettings.ensureTemplatesSeeded(); + const purged = await this.programSettings.purgeEditableFromSuperAdmin(); + return { ...seeded, purged }; + } + + @Post('purge-editable') + @RequirePermissions('khanegi:write') + purgeEditable() { + return this.programSettings.purgeEditableFromSuperAdmin(); } }